Update frontend for new custom_fields API spec

This commit is contained in:
shamoon 2023-11-03 02:19:25 -07:00
parent 99d1de543c
commit 59a08ff2a2
6 changed files with 95 additions and 34 deletions

View File

@ -92,7 +92,7 @@ describe('CustomFieldsDropdownComponent', () => {
CustomFieldsDropdownComponent.prototype as any,
'updateUnusedFields'
)
component.existingFields = [{ field: fields[1] } as any]
component.existingFields = [{ field: fields[1].id } as any]
component.onOpenClose()
expect(updateSpy).toHaveBeenCalled()
expect(component.unusedFields).toEqual([fields[0]])

View File

@ -34,7 +34,10 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
existingFields: PaperlessCustomFieldInstance[] = []
@Output()
added = new EventEmitter()
added: EventEmitter<PaperlessCustomField> = new EventEmitter()
@Output()
created: EventEmitter<PaperlessCustomField> = new EventEmitter()
private customFields: PaperlessCustomField[] = []
public unusedFields: PaperlessCustomField[]
@ -84,9 +87,18 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
})
}
public getCustomFieldFromInstance(
instance: PaperlessCustomFieldInstance
): PaperlessCustomField {
return this.customFields.find((f) => f.id === instance.field)
}
private updateUnusedFields() {
this.unusedFields = this.customFields.filter(
(f) => !this.existingFields?.find((e) => e.field.id === f.id)
(f) =>
!this.existingFields?.find(
(e) => this.getCustomFieldFromInstance(e)?.id === f.id
)
)
}
@ -108,6 +120,7 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
this.toastService.showInfo($localize`Saved field "${newField.name}".`)
this.customFieldsService.clearCache()
this.getFields()
this.created.emit(newField)
})
modal.componentInstance.failed
.pipe(takeUntil(this.unsubscribeNotifier))

View File

@ -54,6 +54,7 @@
[documentId]="documentId"
[disabled]="!userIsOwner"
[existingFields]="document?.custom_fields"
(created)="refreshCustomFields()"
(added)="addField($event)">
</pngx-custom-fields-dropdown>
@ -94,7 +95,7 @@
</div>
</div>
<ul ngbNav #nav="ngbNav" class="nav-underline flex-nowrap flex-md-wrap overflow-x-scroll" (navChange)="onNavChange($event)" [(activeId)]="activeNavID">
<ul ngbNav #nav="ngbNav" class="nav-underline flex-nowrap flex-md-wrap overflow-auto" (navChange)="onNavChange($event)" [(activeId)]="activeNavID">
<li [ngbNavItem]="DocumentDetailNavIDs.Details">
<a ngbNavLink i18n>Details</a>
<ng-template ngbNavContent>
@ -113,11 +114,11 @@
<ng-container *ngFor="let fieldInstance of document?.custom_fields; let i = index">
<div [formGroup]="customFieldFormFields.controls[i]">
<!-- TODO: boolean + URL -->
<pngx-input-text formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.String" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-text>
<pngx-input-date formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.Date" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-date>
<pngx-input-number formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.Integer" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false"></pngx-input-number>
<pngx-input-check formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.Boolean" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-check>
<pngx-input-url formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.Url" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-url>
<pngx-input-text formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.String" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-text>
<pngx-input-date formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Date" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-date>
<pngx-input-number formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Integer" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false"></pngx-input-number>
<pngx-input-check formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Boolean" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-check>
<pngx-input-url formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Url" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-url>
</div>
</ng-container>
</div>

View File

@ -69,6 +69,7 @@ import { DocumentDetailComponent } from './document-detail.component'
import { ShareLinksDropdownComponent } from '../common/share-links-dropdown/share-links-dropdown.component'
import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component'
import { PaperlessCustomFieldDataType } from 'src/app/data/paperless-custom-field'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
const doc: PaperlessDocument = {
id: 3,
@ -98,12 +99,7 @@ const doc: PaperlessDocument = {
],
custom_fields: [
{
field: {
id: 0,
name: 'Field 1',
data_type: PaperlessCustomFieldDataType.String,
created: new Date(),
},
field: 0,
document: 3,
created: new Date(),
value: 'custom foo bar',
@ -111,6 +107,21 @@ const doc: PaperlessDocument = {
],
}
const customFields = [
{
id: 0,
name: 'Field 1',
data_type: PaperlessCustomFieldDataType.String,
created: new Date(),
},
{
id: 1,
name: 'Custom Field 2',
data_type: PaperlessCustomFieldDataType.Integer,
created: new Date(),
},
]
describe('DocumentDetailComponent', () => {
let component: DocumentDetailComponent
let fixture: ComponentFixture<DocumentDetailComponent>
@ -122,6 +133,7 @@ describe('DocumentDetailComponent', () => {
let toastService: ToastService
let documentListViewService: DocumentListViewService
let settingsService: SettingsService
let customFieldsService: CustomFieldsService
let currentUserCan = true
let currentUserHasObjectPermissions = true
@ -215,6 +227,7 @@ describe('DocumentDetailComponent', () => {
}),
},
},
CustomFieldsService,
{
provide: PermissionsService,
useValue: {
@ -250,6 +263,7 @@ describe('DocumentDetailComponent', () => {
toastService = TestBed.inject(ToastService)
documentListViewService = TestBed.inject(DocumentListViewService)
settingsService = TestBed.inject(SettingsService)
customFieldsService = TestBed.inject(CustomFieldsService)
fixture = TestBed.createComponent(DocumentDetailComponent)
component = fixture.componentInstance
})
@ -306,6 +320,13 @@ describe('DocumentDetailComponent', () => {
it('should load already-opened document via param', () => {
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
jest.spyOn(openDocumentsService, 'getOpenDocument').mockReturnValue(doc)
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
of({
count: customFields.length,
all: customFields.map((f) => f.id),
results: customFields,
})
)
fixture.detectChanges() // calls ngOnInit
expect(component.document).toEqual(doc)
})
@ -816,29 +837,26 @@ describe('DocumentDetailComponent', () => {
it('should display custom fields', () => {
initNormally()
expect(fixture.debugElement.nativeElement.textContent).toContain(
doc.custom_fields[0].field.name
customFields[0].name
)
})
it('should support add custom field, correctly send via post', () => {
const field = {
id: 1,
name: 'Custom Field 2',
data_type: PaperlessCustomFieldDataType.Integer,
}
initNormally()
const initialLength = doc.custom_fields.length
expect(component.customFieldFormFields).toHaveLength(initialLength)
component.addField(field)
component.addField(customFields[1])
fixture.detectChanges()
expect(component.document.custom_fields).toHaveLength(initialLength + 1)
expect(component.customFieldFormFields).toHaveLength(initialLength + 1)
expect(fixture.debugElement.nativeElement.textContent).toContain(field.name)
expect(fixture.debugElement.nativeElement.textContent).toContain(
customFields[1].name
)
const updateSpy = jest.spyOn(documentService, 'update')
component.save(true)
expect(updateSpy.mock.lastCall[0].custom_fields).toHaveLength(2)
expect(updateSpy.mock.lastCall[0].custom_fields[1]).toEqual({
field,
field: customFields[1].id,
value: null,
})
})
@ -869,6 +887,13 @@ describe('DocumentDetailComponent', () => {
jest
.spyOn(openDocumentsService, 'openDocument')
.mockReturnValueOnce(of(true))
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
of({
count: customFields.length,
all: customFields.map((f) => f.id),
results: customFields,
})
)
fixture.detectChanges()
}
})

View File

@ -68,6 +68,7 @@ import {
PaperlessCustomFieldDataType,
} from 'src/app/data/paperless-custom-field'
import { PaperlessCustomFieldInstance } from 'src/app/data/paperless-custom-field-instance'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
enum DocumentDetailNavIDs {
Details = 1,
@ -140,6 +141,7 @@ export class DocumentDetailComponent
ogDate: Date
customFields: PaperlessCustomField[]
public readonly PaperlessCustomFieldDataType = PaperlessCustomFieldDataType
@ViewChild('nav') nav: NgbNav
@ -173,6 +175,7 @@ export class DocumentDetailComponent
private storagePathService: StoragePathService,
private permissionsService: PermissionsService,
private userService: UserService,
private customFieldsService: CustomFieldsService,
private http: HttpClient
) {
super()
@ -239,6 +242,8 @@ export class DocumentDetailComponent
.pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe((result) => (this.users = result.results))
this.getCustomFields()
this.route.paramMap
.pipe(
takeUntil(this.unsubscribeNotifier),
@ -836,16 +841,32 @@ export class DocumentDetailComponent
this.documentListViewService.quickFilter(filterRules)
}
updateFormForCustomFields(emitEvent: boolean = false) {
private getCustomFields() {
this.customFieldsService
.listAll()
.pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe((result) => (this.customFields = result.results))
}
public refreshCustomFields() {
this.customFieldsService.clearCache()
this.getCustomFields()
}
public getCustomFieldFromInstance(
instance: PaperlessCustomFieldInstance
): PaperlessCustomField {
return this.customFields.find((f) => f.id === instance.field)
}
private updateFormForCustomFields(emitEvent: boolean = false) {
this.customFieldFormFields.clear({ emitEvent: false })
this.document.custom_fields?.forEach((fieldInstance) => {
this.customFieldFormFields.push(
new FormGroup({
field: new FormGroup({
id: new FormControl(fieldInstance.field.id),
name: new FormControl(fieldInstance.field.name),
data_type: new FormControl(fieldInstance.field.data_type),
}),
field: new FormControl(
this.getCustomFieldFromInstance(fieldInstance)?.id
),
value: new FormControl(fieldInstance.value),
}),
{ emitEvent }
@ -853,9 +874,9 @@ export class DocumentDetailComponent
})
}
addField(field: PaperlessCustomField) {
public addField(field: PaperlessCustomField) {
this.document.custom_fields.push({
field,
field: field.id,
value: null,
document: this.documentId,
created: new Date(),
@ -863,11 +884,12 @@ export class DocumentDetailComponent
this.updateFormForCustomFields(true)
}
removeField(fieldInstance: PaperlessCustomFieldInstance) {
public removeField(fieldInstance: PaperlessCustomFieldInstance) {
this.document.custom_fields.splice(
this.document.custom_fields.indexOf(fieldInstance),
1
)
this.updateFormForCustomFields(true)
this.documentForm.updateValueAndValidity()
}
}

View File

@ -3,7 +3,7 @@ import { PaperlessCustomField } from './paperless-custom-field'
export interface PaperlessCustomFieldInstance extends ObjectWithId {
document: number // PaperlessDocument
field: PaperlessCustomField
field: number // PaperlessCustomField
created: Date
value?: any
}