Chorehancement: update to Angular v22, 'zoneless' / 'reactive' (#13114)

This commit is contained in:
shamoon
2026-07-10 00:42:16 -07:00
committed by GitHub
parent f244442c65
commit 106b41a15c
213 changed files with 5363 additions and 5842 deletions
@@ -140,7 +140,7 @@
</div>
@if (formGroup.get('schedule_date_field').value === 'custom_field') {
<div class="col-4">
<pngx-input-select i18n-title title="Custom field" formControlName="schedule_date_custom_field" [items]="dateCustomFields" i18n-hint hint="Custom field to use for date." [error]="error?.schedule_date_custom_field"></pngx-input-select>
<pngx-input-select i18n-title title="Custom field" formControlName="schedule_date_custom_field" [items]="dateCustomFields()" i18n-hint hint="Custom field to use for date." [error]="error?.schedule_date_custom_field"></pngx-input-select>
</div>
}
</div>
@@ -162,7 +162,7 @@
@if (formGroup.get('type').value === WorkflowTriggerType.Consumption) {
<pngx-input-select i18n-title title="Filter sources" [items]="sourceOptions" horizontal="true" [multiple]="true" formControlName="sources" [error]="error?.sources"></pngx-input-select>
<pngx-input-text i18n-title title="Filter path" formControlName="filter_path" horizontal="true" i18n-hint hint="Apply to documents that match this path. Wildcards specified as * are allowed. Case-normalized." [error]="error?.filter_path"></pngx-input-text>
<pngx-input-select i18n-title title="Filter mail rule" [items]="mailRules" horizontal="true" [allowNull]="true" formControlName="filter_mailrule" i18n-hint hint="Apply to documents consumed via this mail rule." [error]="error?.filter_mailrule"></pngx-input-select>
<pngx-input-select i18n-title title="Filter mail rule" [items]="mailRules()" horizontal="true" [allowNull]="true" formControlName="filter_mailrule" i18n-hint hint="Apply to documents consumed via this mail rule." [error]="error?.filter_mailrule"></pngx-input-select>
}
@if (formGroup.get('type').value === WorkflowTriggerType.DocumentAdded || formGroup.get('type').value === WorkflowTriggerType.DocumentUpdated || formGroup.get('type').value === WorkflowTriggerType.Scheduled) {
<pngx-input-select i18n-title title="Content matching algorithm" horizontal="true" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
@@ -262,10 +262,10 @@
<div class="col">
<pngx-input-text i18n-title title="Assign title" formControlName="assign_title" i18n-hint hint="Can include some placeholders, see <a target='_blank' href='https://docs.paperless-ngx.com/usage/#workflows'>documentation</a>." [error]="error?.actions?.[i]?.assign_title"></pngx-input-text>
<pngx-input-tags [allowCreate]="false" i18n-title title="Assign tags" formControlName="assign_tags"></pngx-input-tags>
<pngx-input-select i18n-title title="Assign document type" [items]="documentTypes" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
<pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
<pngx-input-select i18n-title title="Assign storage path" [items]="storagePaths" [allowNull]="true" formControlName="assign_storage_path"></pngx-input-select>
<pngx-input-select i18n-title title="Assign custom fields" multiple="true" [items]="customFields" [allowNull]="true" formControlName="assign_custom_fields"></pngx-input-select>
<pngx-input-select i18n-title title="Assign document type" [items]="documentTypes()" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
<pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents()" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
<pngx-input-select i18n-title title="Assign storage path" [items]="storagePaths()" [allowNull]="true" formControlName="assign_storage_path"></pngx-input-select>
<pngx-input-select i18n-title title="Assign custom fields" multiple="true" [items]="customFields()" [allowNull]="true" formControlName="assign_custom_fields"></pngx-input-select>
<pngx-input-custom-fields-values formControlName="assign_custom_fields_values" [selectedFields]="formGroup.get('assign_custom_fields').value" (removeSelectedField)="removeSelectedCustomField($event, formGroup)"></pngx-input-custom-fields-values>
</div>
<div class="col">
@@ -326,25 +326,25 @@
<h6 class="form-label" i18n>Remove correspondents</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_correspondents"></pngx-input-switch>
<div class="mt-n3">
<pngx-input-select i18n-title title="" multiple="true" [items]="correspondents" formControlName="remove_correspondents"></pngx-input-select>
<pngx-input-select i18n-title title="" multiple="true" [items]="correspondents()" formControlName="remove_correspondents"></pngx-input-select>
</div>
<h6 class="form-label" i18n>Remove document types</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_document_types"></pngx-input-switch>
<div class="mt-n3">
<pngx-input-select i18n-title title="" multiple="true" [items]="documentTypes" formControlName="remove_document_types"></pngx-input-select>
<pngx-input-select i18n-title title="" multiple="true" [items]="documentTypes()" formControlName="remove_document_types"></pngx-input-select>
</div>
<h6 class="form-label" i18n>Remove storage paths</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_storage_paths"></pngx-input-switch>
<div class="mt-n3">
<pngx-input-select i18n-title title="" multiple="true" [items]="storagePaths" formControlName="remove_storage_paths"></pngx-input-select>
<pngx-input-select i18n-title title="" multiple="true" [items]="storagePaths()" formControlName="remove_storage_paths"></pngx-input-select>
</div>
<h6 class="form-label" i18n>Remove custom fields</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_custom_fields"></pngx-input-switch>
<div class="mt-n3">
<pngx-input-select i18n-title title="" multiple="true" [items]="customFields" formControlName="remove_custom_fields"></pngx-input-select>
<pngx-input-select i18n-title title="" multiple="true" [items]="customFields()" formControlName="remove_custom_fields"></pngx-input-select>
</div>
</div>
<div class="col">
@@ -187,14 +187,14 @@ describe('WorkflowEditDialogComponent', () => {
fixture = TestBed.createComponent(WorkflowEditDialogComponent)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 99, username: 'user99' }
settingsService.currentUser.set({ id: 99, username: 'user99' })
component = fixture.componentInstance
fixture.detectChanges()
})
it('should support create and edit modes, support adding triggers and actions on new workflow', () => {
component.dialogMode = EditDialogMode.CREATE
component.dialogMode.set(EditDialogMode.CREATE)
const createTitleSpy = jest.spyOn(component, 'getCreateTitle')
const editTitleSpy = jest.spyOn(component, 'getEditTitle')
fixture.detectChanges()
@@ -209,7 +209,7 @@ describe('WorkflowEditDialogComponent', () => {
expect(component.object).not.toBeUndefined()
expect(component.object.triggers).toHaveLength(1)
component.dialogMode = EditDialogMode.EDIT
component.dialogMode.set(EditDialogMode.EDIT)
fixture.detectChanges()
expect(editTitleSpy).toHaveBeenCalled()
})
@@ -771,25 +771,21 @@ describe('WorkflowEditDialogComponent', () => {
false
)
component.correspondents = [{ id: 1, name: 'C1' } as any]
component.documentTypes = [{ id: 2, name: 'DT' } as any]
component.storagePaths = [{ id: 3, name: 'SP' } as any]
expect(
component.getFilterSelectItems(TriggerFilterType.CorrespondentIs)
).toEqual(component.correspondents)
).toEqual(component.correspondents())
expect(
component.getFilterSelectItems(TriggerFilterType.DocumentTypeIs)
).toEqual(component.documentTypes)
).toEqual(component.documentTypes())
expect(
component.getFilterSelectItems(TriggerFilterType.DocumentTypeAny)
).toEqual(component.documentTypes)
).toEqual(component.documentTypes())
expect(
component.getFilterSelectItems(TriggerFilterType.StoragePathIs)
).toEqual(component.storagePaths)
).toEqual(component.storagePaths())
expect(
component.getFilterSelectItems(TriggerFilterType.StoragePathAny)
).toEqual(component.storagePaths)
).toEqual(component.storagePaths())
expect(component.getFilterSelectItems(TriggerFilterType.TagsAll)).toEqual(
[]
)
@@ -4,7 +4,8 @@ import {
moveItemInArray,
} from '@angular/cdk/drag-drop'
import { NgTemplateOutlet } from '@angular/common'
import { Component, OnInit, inject } from '@angular/core'
import { Component, OnInit, computed, inject, signal } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import {
AbstractControl,
FormArray,
@@ -15,7 +16,7 @@ import {
} from '@angular/forms'
import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Subscription, first, takeUntil } from 'rxjs'
import { Subscription, map, takeUntil } from 'rxjs'
import { Correspondent } from 'src/app/data/correspondent'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import { DocumentType } from 'src/app/data/document-type'
@@ -470,23 +471,40 @@ export class WorkflowEditDialogComponent
public TriggerFilterType = TriggerFilterType
public filterDefinitions = TRIGGER_FILTER_DEFINITIONS
private correspondentService: CorrespondentService
private documentTypeService: DocumentTypeService
private storagePathService: StoragePathService
private mailRuleService: MailRuleService
private customFieldsService: CustomFieldsService
private readonly correspondentService = inject(CorrespondentService)
private readonly documentTypeService = inject(DocumentTypeService)
private readonly storagePathService = inject(StoragePathService)
private readonly mailRuleService = inject(MailRuleService)
private readonly customFieldsService = inject(CustomFieldsService)
templates: Workflow[]
correspondents: Correspondent[]
documentTypes: DocumentType[]
storagePaths: StoragePath[]
mailRules: MailRule[]
customFields: CustomField[]
dateCustomFields: CustomField[]
readonly templates = signal<Workflow[]>(undefined)
readonly correspondents = toSignal(
this.correspondentService.listAll().pipe(map((result) => result.results)),
{ initialValue: undefined as Correspondent[] }
)
readonly documentTypes = toSignal(
this.documentTypeService.listAll().pipe(map((result) => result.results)),
{ initialValue: undefined as DocumentType[] }
)
readonly storagePaths = toSignal(
this.storagePathService.listAll().pipe(map((result) => result.results)),
{ initialValue: undefined as StoragePath[] }
)
readonly mailRules = toSignal(
this.mailRuleService.listAll().pipe(map((result) => result.results)),
{ initialValue: undefined as MailRule[] }
)
readonly customFields = toSignal(
this.customFieldsService.listAll().pipe(map((result) => result.results)),
{ initialValue: undefined as CustomField[] }
)
readonly dateCustomFields = computed(() =>
this.customFields()?.filter((f) => f.data_type === CustomFieldDataType.Date)
)
expandedItem: number = null
private allowedActionTypes = []
readonly allowedActionTypes = signal([])
private readonly triggerFilterOptionsMap = new WeakMap<
FormArray,
@@ -496,43 +514,8 @@ export class WorkflowEditDialogComponent
constructor() {
super()
this.service = inject(WorkflowService)
this.correspondentService = inject(CorrespondentService)
this.documentTypeService = inject(DocumentTypeService)
this.storagePathService = inject(StoragePathService)
this.mailRuleService = inject(MailRuleService)
this.userService = inject(UserService)
this.settingsService = inject(SettingsService)
this.customFieldsService = inject(CustomFieldsService)
this.correspondentService
.listAll()
.pipe(first())
.subscribe((result) => (this.correspondents = result.results))
this.documentTypeService
.listAll()
.pipe(first())
.subscribe((result) => (this.documentTypes = result.results))
this.storagePathService
.listAll()
.pipe(first())
.subscribe((result) => (this.storagePaths = result.results))
this.mailRuleService
.listAll()
.pipe(first())
.subscribe((result) => (this.mailRules = result.results))
this.customFieldsService
.listAll()
.pipe(first())
.subscribe((result) => {
this.customFields = result.results
this.dateCustomFields = this.customFields?.filter(
(f) => f.data_type === CustomFieldDataType.Date
)
})
}
getCreateTitle() {
@@ -565,11 +548,13 @@ export class WorkflowEditDialogComponent
this.checkRemovalActionFields.bind(this)
)
this.checkRemovalActionFields(this.objectForm.value)
this.allowedActionTypes = this.settingsService.get(
SETTINGS_KEYS.EMAIL_ENABLED
this.allowedActionTypes.set(
this.settingsService.get(SETTINGS_KEYS.EMAIL_ENABLED)
? WORKFLOW_ACTION_OPTIONS
: WORKFLOW_ACTION_OPTIONS.filter(
(a) => a.id !== WorkflowActionType.Email
)
)
? WORKFLOW_ACTION_OPTIONS
: WORKFLOW_ACTION_OPTIONS.filter((a) => a.id !== WorkflowActionType.Email)
}
private checkRemovalActionFields(formWorkflow: Workflow) {
@@ -954,11 +939,11 @@ export class WorkflowEditDialogComponent
switch (definition.selectItems) {
case 'correspondents':
return this.correspondents
return this.correspondents()
case 'documentTypes':
return this.documentTypes
return this.documentTypes()
case 'storagePaths':
return this.storagePaths
return this.storagePaths()
default:
return []
}
@@ -1294,7 +1279,8 @@ export class WorkflowEditDialogComponent
}
get actionTypeOptions() {
return this.allowedActionTypes
this.settingsService.trackChanges()
return this.allowedActionTypes()
}
getActionTypeOptionName(type: WorkflowActionType): string {