Refactor workflow trigger conditions to filters

This commit is contained in:
shamoon
2025-10-08 08:42:21 -07:00
parent e715a78b63
commit f3e749511e
4 changed files with 317 additions and 354 deletions

View File

@@ -176,58 +176,58 @@
@if (formGroup.get('type').value === WorkflowTriggerType.DocumentAdded || formGroup.get('type').value === WorkflowTriggerType.DocumentUpdated || formGroup.get('type').value === WorkflowTriggerType.Scheduled) { @if (formGroup.get('type').value === WorkflowTriggerType.DocumentAdded || formGroup.get('type').value === WorkflowTriggerType.DocumentUpdated || formGroup.get('type').value === WorkflowTriggerType.Scheduled) {
<div class="row mt-3"> <div class="row mt-3">
<div class="col"> <div class="col">
<div class="trigger-conditions mb-3"> <div class="trigger-filters mb-3">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<label class="form-label mb-0" i18n>Conditions</label> <label class="form-label mb-0" i18n>Advanced Filters</label>
<button <button
type="button" type="button"
class="btn btn-sm btn-outline-primary ms-auto" class="btn btn-sm btn-outline-primary ms-auto"
(click)="addCondition(formGroup)" (click)="addFilter(formGroup)"
[disabled]="!canAddCondition(formGroup)" [disabled]="!canAddFilter(formGroup)"
> >
<i-bs name="plus-circle"></i-bs>&nbsp;<span i18n>Add condition</span> <i-bs name="plus-circle"></i-bs>&nbsp;<span i18n>Add filter</span>
</button> </button>
</div> </div>
<ul class="mt-2 list-group conditions" formArrayName="conditions"> <ul class="mt-2 list-group filters" formArrayName="filters">
@if (getConditionsFormArray(formGroup).length === 0) { @if (getFiltersFormArray(formGroup).length === 0) {
<p class="text-muted small" i18n>No conditions added. Add one to define document filters.</p> <p class="text-muted small" i18n>No advanced workflow filters defined.</p>
} }
@for (condition of getConditionsFormArray(formGroup).controls; track condition; let conditionIndex = $index) { @for (filter of getFiltersFormArray(formGroup).controls; track filter; let filterIndex = $index) {
<li [formGroupName]="conditionIndex" class="list-group-item"> <li [formGroupName]="filterIndex" class="list-group-item">
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<div class="w-25"> <div class="w-25">
<pngx-input-select <pngx-input-select
i18n-title i18n-title
[items]="getConditionTypeOptions(formGroup, conditionIndex)" [items]="getFilterTypeOptions(formGroup, filterIndex)"
formControlName="type" formControlName="type"
[allowNull]="false" [allowNull]="false"
></pngx-input-select> ></pngx-input-select>
</div> </div>
<div class="flex-grow-1"> <div class="flex-grow-1">
@if (isTagsCondition(condition.get('type').value)) { @if (isTagsFilter(filter.get('type').value)) {
<pngx-input-tags <pngx-input-tags
[allowCreate]="false" [allowCreate]="false"
[title]="null" [title]="null"
formControlName="values" formControlName="values"
></pngx-input-tags> ></pngx-input-tags>
} @else if ( } @else if (
isCustomFieldQueryCondition(condition.get('type').value) isCustomFieldQueryFilter(filter.get('type').value)
) { ) {
<pngx-custom-fields-query-dropdown <pngx-custom-fields-query-dropdown
[selectionModel]="getCustomFieldQueryModel(condition)" [selectionModel]="getCustomFieldQueryModel(filter)"
(selectionModelChange)="onCustomFieldQuerySelectionChange(condition, $event)" (selectionModelChange)="onCustomFieldQuerySelectionChange(filter, $event)"
[useDropdown]="false" [useDropdown]="false"
></pngx-custom-fields-query-dropdown> ></pngx-custom-fields-query-dropdown>
@if (!isCustomFieldQueryValid(condition)) { @if (!isCustomFieldQueryValid(filter)) {
<div class="text-danger small" i18n> <div class="text-danger small" i18n>
Complete the custom field query configuration. Complete the custom field query configuration.
</div> </div>
} }
} @else { } @else {
<pngx-input-select <pngx-input-select
[items]="getConditionSelectItems(condition.get('type').value)" [items]="getFilterSelectItems(filter.get('type').value)"
[allowNull]="true" [allowNull]="true"
[multiple]="isSelectMultiple(condition.get('type').value)" [multiple]="isSelectMultiple(filter.get('type').value)"
formControlName="values" formControlName="values"
></pngx-input-select> ></pngx-input-select>
} }
@@ -235,7 +235,7 @@
<button <button
type="button" type="button"
class="btn btn-link text-danger p-0" class="btn btn-link text-danger p-0"
(click)="removeCondition(formGroup, conditionIndex)" (click)="removeFilter(formGroup, filterIndex)"
> >
<i-bs name="trash"></i-bs><span class="ms-1" i18n>Delete</span> <i-bs name="trash"></i-bs><span class="ms-1" i18n>Delete</span>
</button> </button>

View File

@@ -8,6 +8,6 @@
font-size: 1rem; font-size: 1rem;
} }
:host ::ng-deep .conditions .paperless-input-select.mb-3 { :host ::ng-deep .filters .paperless-input-select.mb-3 {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }

View File

@@ -50,7 +50,7 @@ import { EditDialogMode } from '../edit-dialog.component'
import { import {
DOCUMENT_SOURCE_OPTIONS, DOCUMENT_SOURCE_OPTIONS,
SCHEDULE_DATE_FIELD_OPTIONS, SCHEDULE_DATE_FIELD_OPTIONS,
TriggerConditionType, TriggerFilterType,
WORKFLOW_ACTION_OPTIONS, WORKFLOW_ACTION_OPTIONS,
WORKFLOW_TYPE_OPTIONS, WORKFLOW_TYPE_OPTIONS,
WorkflowEditDialogComponent, WorkflowEditDialogComponent,
@@ -395,62 +395,50 @@ describe('WorkflowEditDialogComponent', () => {
expect(component.matchingPatternRequired(triggerGroup)).toBe(false) expect(component.matchingPatternRequired(triggerGroup)).toBe(false)
}) })
it('should map condition builder values into trigger filters on save', () => { it('should map filter builder values into trigger filters on save', () => {
component.object = undefined component.object = undefined
component.addTrigger() component.addTrigger()
const triggerGroup = component.triggerFields.at(0) const triggerGroup = component.triggerFields.at(0)
component.addCondition(triggerGroup as FormGroup) component.addFilter(triggerGroup as FormGroup)
component.addCondition(triggerGroup as FormGroup) component.addFilter(triggerGroup as FormGroup)
component.addCondition(triggerGroup as FormGroup) component.addFilter(triggerGroup as FormGroup)
const conditions = component.getConditionsFormArray( const filters = component.getFiltersFormArray(triggerGroup as FormGroup)
triggerGroup as FormGroup expect(filters.length).toBe(3)
)
expect(conditions.length).toBe(3)
conditions.at(0).get('values').setValue([1]) filters.at(0).get('values').setValue([1])
conditions.at(1).get('values').setValue([2, 3]) filters.at(1).get('values').setValue([2, 3])
conditions.at(2).get('values').setValue([4]) filters.at(2).get('values').setValue([4])
const addConditionOfType = (type: TriggerConditionType) => { const addFilterOfType = (type: TriggerFilterType) => {
const newCondition = component.addCondition(triggerGroup as FormGroup) const newFilter = component.addFilter(triggerGroup as FormGroup)
newCondition.get('type').setValue(type) newFilter.get('type').setValue(type)
return newCondition return newFilter
} }
const correspondentIs = addConditionOfType( const correspondentIs = addFilterOfType(TriggerFilterType.CorrespondentIs)
TriggerConditionType.CorrespondentIs
)
correspondentIs.get('values').setValue(1) correspondentIs.get('values').setValue(1)
const correspondentNot = addConditionOfType( const correspondentNot = addFilterOfType(TriggerFilterType.CorrespondentNot)
TriggerConditionType.CorrespondentNot
)
correspondentNot.get('values').setValue([1]) correspondentNot.get('values').setValue([1])
const documentTypeIs = addConditionOfType( const documentTypeIs = addFilterOfType(TriggerFilterType.DocumentTypeIs)
TriggerConditionType.DocumentTypeIs
)
documentTypeIs.get('values').setValue(1) documentTypeIs.get('values').setValue(1)
const documentTypeNot = addConditionOfType( const documentTypeNot = addFilterOfType(TriggerFilterType.DocumentTypeNot)
TriggerConditionType.DocumentTypeNot
)
documentTypeNot.get('values').setValue([1]) documentTypeNot.get('values').setValue([1])
const storagePathIs = addConditionOfType(TriggerConditionType.StoragePathIs) const storagePathIs = addFilterOfType(TriggerFilterType.StoragePathIs)
storagePathIs.get('values').setValue(1) storagePathIs.get('values').setValue(1)
const storagePathNot = addConditionOfType( const storagePathNot = addFilterOfType(TriggerFilterType.StoragePathNot)
TriggerConditionType.StoragePathNot
)
storagePathNot.get('values').setValue([1]) storagePathNot.get('values').setValue([1])
const customFieldCondition = addConditionOfType( const customFieldFilter = addFilterOfType(
TriggerConditionType.CustomFieldQuery TriggerFilterType.CustomFieldQuery
) )
const customFieldQuery = JSON.stringify(['AND', [[1, 'exact', 'test']]]) const customFieldQuery = JSON.stringify(['AND', [[1, 'exact', 'test']]])
customFieldCondition.get('values').setValue(customFieldQuery) customFieldFilter.get('values').setValue(customFieldQuery)
const formValues = component['getFormValues']() const formValues = component['getFormValues']()
@@ -466,23 +454,21 @@ describe('WorkflowEditDialogComponent', () => {
expect(formValues.triggers[0].filter_custom_field_query).toEqual( expect(formValues.triggers[0].filter_custom_field_query).toEqual(
customFieldQuery customFieldQuery
) )
expect(formValues.triggers[0].conditions).toBeUndefined() expect(formValues.triggers[0].filters).toBeUndefined()
}) })
it('should ignore empty and null condition values when mapping filters', () => { it('should ignore empty and null filter values when mapping filters', () => {
component.object = undefined component.object = undefined
component.addTrigger() component.addTrigger()
const triggerGroup = component.triggerFields.at(0) as FormGroup const triggerGroup = component.triggerFields.at(0) as FormGroup
const tagsCondition = component.addCondition(triggerGroup) const tagsFilter = component.addFilter(triggerGroup)
tagsCondition.get('type').setValue(TriggerConditionType.TagsAny) tagsFilter.get('type').setValue(TriggerFilterType.TagsAny)
tagsCondition.get('values').setValue([]) tagsFilter.get('values').setValue([])
const correspondentCondition = component.addCondition(triggerGroup) const correspondentFilter = component.addFilter(triggerGroup)
correspondentCondition correspondentFilter.get('type').setValue(TriggerFilterType.CorrespondentIs)
.get('type') correspondentFilter.get('values').setValue(null)
.setValue(TriggerConditionType.CorrespondentIs)
correspondentCondition.get('values').setValue(null)
const formValues = component['getFormValues']() const formValues = component['getFormValues']()
@@ -495,15 +481,15 @@ describe('WorkflowEditDialogComponent', () => {
component.addTrigger() component.addTrigger()
const triggerGroup = component.triggerFields.at(0) as FormGroup const triggerGroup = component.triggerFields.at(0) as FormGroup
const addConditionOfType = (type: TriggerConditionType, value: any) => { const addFilterOfType = (type: TriggerFilterType, value: any) => {
const condition = component.addCondition(triggerGroup) const filter = component.addFilter(triggerGroup)
condition.get('type').setValue(type) filter.get('type').setValue(type)
condition.get('values').setValue(value) filter.get('values').setValue(value)
} }
addConditionOfType(TriggerConditionType.CorrespondentIs, [5]) addFilterOfType(TriggerFilterType.CorrespondentIs, [5])
addConditionOfType(TriggerConditionType.DocumentTypeIs, [6]) addFilterOfType(TriggerFilterType.DocumentTypeIs, [6])
addConditionOfType(TriggerConditionType.StoragePathIs, [7]) addFilterOfType(TriggerFilterType.StoragePathIs, [7])
const formValues = component['getFormValues']() const formValues = component['getFormValues']()
@@ -512,22 +498,22 @@ describe('WorkflowEditDialogComponent', () => {
expect(formValues.triggers[0].filter_has_storage_path).toEqual(7) expect(formValues.triggers[0].filter_has_storage_path).toEqual(7)
}) })
it('should convert multi-value condition values when aggregating filters', () => { it('should convert multi-value filter values when aggregating filters', () => {
component.object = undefined component.object = undefined
component.addTrigger() component.addTrigger()
const triggerGroup = component.triggerFields.at(0) as FormGroup const triggerGroup = component.triggerFields.at(0) as FormGroup
const setCondition = (type: TriggerConditionType, value: number): void => { const setFilter = (type: TriggerFilterType, value: number): void => {
const condition = component.addCondition(triggerGroup) as FormGroup const filter = component.addFilter(triggerGroup) as FormGroup
condition.get('type').setValue(type) filter.get('type').setValue(type)
condition.get('values').setValue(value) filter.get('values').setValue(value)
} }
setCondition(TriggerConditionType.TagsAll, 11) setFilter(TriggerFilterType.TagsAll, 11)
setCondition(TriggerConditionType.TagsNone, 12) setFilter(TriggerFilterType.TagsNone, 12)
setCondition(TriggerConditionType.CorrespondentNot, 13) setFilter(TriggerFilterType.CorrespondentNot, 13)
setCondition(TriggerConditionType.DocumentTypeNot, 14) setFilter(TriggerFilterType.DocumentTypeNot, 14)
setCondition(TriggerConditionType.StoragePathNot, 15) setFilter(TriggerFilterType.StoragePathNot, 15)
const formValues = component['getFormValues']() const formValues = component['getFormValues']()
@@ -538,55 +524,55 @@ describe('WorkflowEditDialogComponent', () => {
expect(formValues.triggers[0].filter_has_not_storage_paths).toEqual([15]) expect(formValues.triggers[0].filter_has_not_storage_paths).toEqual([15])
}) })
it('should reuse condition type options and update disabled state', () => { it('should reuse filter type options and update disabled state', () => {
component.object = undefined component.object = undefined
component.addTrigger() component.addTrigger()
const triggerGroup = component.triggerFields.at(0) as FormGroup const triggerGroup = component.triggerFields.at(0) as FormGroup
component.addCondition(triggerGroup) component.addFilter(triggerGroup)
const optionsFirst = component.getConditionTypeOptions(triggerGroup, 0) const optionsFirst = component.getFilterTypeOptions(triggerGroup, 0)
const optionsSecond = component.getConditionTypeOptions(triggerGroup, 0) const optionsSecond = component.getFilterTypeOptions(triggerGroup, 0)
expect(optionsFirst).toBe(optionsSecond) expect(optionsFirst).toBe(optionsSecond)
// to force disabled flag // to force disabled flag
component.addCondition(triggerGroup) component.addFilter(triggerGroup)
const conditionArray = component.getConditionsFormArray(triggerGroup) const filterArray = component.getFiltersFormArray(triggerGroup)
const firstCondition = conditionArray.at(0) const firstFilter = filterArray.at(0)
firstCondition.get('type').setValue(TriggerConditionType.CorrespondentIs) firstFilter.get('type').setValue(TriggerFilterType.CorrespondentIs)
component.addCondition(triggerGroup) component.addFilter(triggerGroup)
const updatedConditions = component.getConditionsFormArray(triggerGroup) const updatedFilters = component.getFiltersFormArray(triggerGroup)
const secondCondition = updatedConditions.at(1) const secondFilter = updatedFilters.at(1)
const options = component.getConditionTypeOptions(triggerGroup, 1) const options = component.getFilterTypeOptions(triggerGroup, 1)
const correspondentIsOption = options.find( const correspondentIsOption = options.find(
(option) => option.id === TriggerConditionType.CorrespondentIs (option) => option.id === TriggerFilterType.CorrespondentIs
) )
expect(correspondentIsOption.disabled).toBe(true) expect(correspondentIsOption.disabled).toBe(true)
firstCondition.get('type').setValue(TriggerConditionType.DocumentTypeNot) firstFilter.get('type').setValue(TriggerFilterType.DocumentTypeNot)
secondCondition.get('type').setValue(TriggerConditionType.TagsAll) secondFilter.get('type').setValue(TriggerFilterType.TagsAll)
const postChangeOptions = component.getConditionTypeOptions(triggerGroup, 1) const postChangeOptions = component.getFilterTypeOptions(triggerGroup, 1)
const correspondentOptionAfter = postChangeOptions.find( const correspondentOptionAfter = postChangeOptions.find(
(option) => option.id === TriggerConditionType.CorrespondentIs (option) => option.id === TriggerFilterType.CorrespondentIs
) )
expect(correspondentOptionAfter.disabled).toBe(false) expect(correspondentOptionAfter.disabled).toBe(false)
}) })
it('should keep multi-entry condition options enabled and allow duplicates', () => { it('should keep multi-entry filter options enabled and allow duplicates', () => {
component.object = undefined component.object = undefined
component.addTrigger() component.addTrigger()
const triggerGroup = component.triggerFields.at(0) as FormGroup const triggerGroup = component.triggerFields.at(0) as FormGroup
component.conditionDefinitions = [ component.filterDefinitions = [
{ {
id: TriggerConditionType.TagsAny, id: TriggerFilterType.TagsAny,
name: 'Any tags', name: 'Any tags',
inputType: 'tags', inputType: 'tags',
allowMultipleEntries: true, allowMultipleEntries: true,
allowMultipleValues: true, allowMultipleValues: true,
} as any, } as any,
{ {
id: TriggerConditionType.CorrespondentIs, id: TriggerFilterType.CorrespondentIs,
name: 'Correspondent is', name: 'Correspondent is',
inputType: 'select', inputType: 'select',
allowMultipleEntries: false, allowMultipleEntries: false,
@@ -595,36 +581,36 @@ describe('WorkflowEditDialogComponent', () => {
} as any, } as any,
] ]
const firstCondition = component.addCondition(triggerGroup) const firstFilter = component.addFilter(triggerGroup)
firstCondition.get('type').setValue(TriggerConditionType.TagsAny) firstFilter.get('type').setValue(TriggerFilterType.TagsAny)
const secondCondition = component.addCondition(triggerGroup) const secondFilter = component.addFilter(triggerGroup)
expect(secondCondition).not.toBeNull() expect(secondFilter).not.toBeNull()
const options = component.getConditionTypeOptions(triggerGroup, 1) const options = component.getFilterTypeOptions(triggerGroup, 1)
const multiEntryOption = options.find( const multiEntryOption = options.find(
(option) => option.id === TriggerConditionType.TagsAny (option) => option.id === TriggerFilterType.TagsAny
) )
expect(multiEntryOption.disabled).toBe(false) expect(multiEntryOption.disabled).toBe(false)
expect(component.canAddCondition(triggerGroup)).toBe(true) expect(component.canAddFilter(triggerGroup)).toBe(true)
}) })
it('should return null when no condition definitions remain available', () => { it('should return null when no filter definitions remain available', () => {
component.object = undefined component.object = undefined
component.addTrigger() component.addTrigger()
const triggerGroup = component.triggerFields.at(0) as FormGroup const triggerGroup = component.triggerFields.at(0) as FormGroup
component.conditionDefinitions = [ component.filterDefinitions = [
{ {
id: TriggerConditionType.TagsAny, id: TriggerFilterType.TagsAny,
name: 'Any tags', name: 'Any tags',
inputType: 'tags', inputType: 'tags',
allowMultipleEntries: false, allowMultipleEntries: false,
allowMultipleValues: true, allowMultipleValues: true,
} as any, } as any,
{ {
id: TriggerConditionType.CorrespondentIs, id: TriggerFilterType.CorrespondentIs,
name: 'Correspondent is', name: 'Correspondent is',
inputType: 'select', inputType: 'select',
allowMultipleEntries: false, allowMultipleEntries: false,
@@ -633,18 +619,18 @@ describe('WorkflowEditDialogComponent', () => {
} as any, } as any,
] ]
const firstCondition = component.addCondition(triggerGroup) const firstFilter = component.addFilter(triggerGroup)
firstCondition.get('type').setValue(TriggerConditionType.TagsAny) firstFilter.get('type').setValue(TriggerFilterType.TagsAny)
const secondCondition = component.addCondition(triggerGroup) const secondFilter = component.addFilter(triggerGroup)
secondCondition.get('type').setValue(TriggerConditionType.CorrespondentIs) secondFilter.get('type').setValue(TriggerFilterType.CorrespondentIs)
expect(component.canAddCondition(triggerGroup)).toBe(false) expect(component.canAddFilter(triggerGroup)).toBe(false)
expect(component.addCondition(triggerGroup)).toBeNull() expect(component.addFilter(triggerGroup)).toBeNull()
}) })
it('should skip condition definitions without handlers when building form array', () => { it('should skip filter definitions without handlers when building form array', () => {
const originalDefinitions = component.conditionDefinitions const originalDefinitions = component.filterDefinitions
component.conditionDefinitions = [ component.filterDefinitions = [
{ {
id: 999, id: 999,
name: 'Unsupported', name: 'Unsupported',
@@ -667,52 +653,52 @@ describe('WorkflowEditDialogComponent', () => {
filter_custom_field_query: null, filter_custom_field_query: null,
} as any } as any
const conditions = component['buildConditionFormArray'](trigger) const filters = component['buildFiltersFormArray'](trigger)
expect(conditions.length).toBe(0) expect(filters.length).toBe(0)
component.conditionDefinitions = originalDefinitions component.filterDefinitions = originalDefinitions
}) })
it('should return null when adding condition for unknown trigger form group', () => { it('should return null when adding filter for unknown trigger form group', () => {
expect(component.addCondition(new FormGroup({}) as any)).toBeNull() expect(component.addFilter(new FormGroup({}) as any)).toBeNull()
}) })
it('should ignore remove condition calls for unknown trigger form group', () => { it('should ignore remove filter calls for unknown trigger form group', () => {
expect(() => expect(() =>
component.removeCondition(new FormGroup({}) as any, 0) component.removeFilter(new FormGroup({}) as any, 0)
).not.toThrow() ).not.toThrow()
}) })
it('should teardown custom field query model when removing a custom field condition', () => { it('should teardown custom field query model when removing a custom field filter', () => {
component.object = undefined component.object = undefined
component.addTrigger() component.addTrigger()
const triggerGroup = component.triggerFields.at(0) as FormGroup const triggerGroup = component.triggerFields.at(0) as FormGroup
component.addCondition(triggerGroup) component.addFilter(triggerGroup)
const conditions = component.getConditionsFormArray(triggerGroup) const filters = component.getFiltersFormArray(triggerGroup)
const conditionGroup = conditions.at(0) as FormGroup const filterGroup = filters.at(0) as FormGroup
conditionGroup.get('type').setValue(TriggerConditionType.CustomFieldQuery) filterGroup.get('type').setValue(TriggerFilterType.CustomFieldQuery)
const model = component.getCustomFieldQueryModel(conditionGroup) const model = component.getCustomFieldQueryModel(filterGroup)
expect(model).toBeDefined() expect(model).toBeDefined()
expect( expect(
component['getStoredCustomFieldQueryModel'](conditionGroup as any) component['getStoredCustomFieldQueryModel'](filterGroup as any)
).toBe(model) ).toBe(model)
component.removeCondition(triggerGroup, 0) component.removeFilter(triggerGroup, 0)
expect( expect(
component['getStoredCustomFieldQueryModel'](conditionGroup as any) component['getStoredCustomFieldQueryModel'](filterGroup as any)
).toBeNull() ).toBeNull()
}) })
it('should return readable condition names', () => { it('should return readable filter names', () => {
expect(component.getConditionName(TriggerConditionType.TagsAny)).toBe( expect(component.getFilterName(TriggerFilterType.TagsAny)).toBe(
'Has any of these tags' 'Has any of these tags'
) )
expect(component.getConditionName(999 as any)).toBe('') expect(component.getFilterName(999 as any)).toBe('')
}) })
it('should build condition form array from existing trigger filters', () => { it('should build filter form array from existing trigger filters', () => {
const trigger = workflow.triggers[0] const trigger = workflow.triggers[0]
trigger.filter_has_tags = [1] trigger.filter_has_tags = [1]
trigger.filter_has_all_tags = [2, 3] trigger.filter_has_all_tags = [2, 3]
@@ -731,64 +717,62 @@ describe('WorkflowEditDialogComponent', () => {
component.object = workflow component.object = workflow
component.ngOnInit() component.ngOnInit()
const triggerGroup = component.triggerFields.at(0) as FormGroup const triggerGroup = component.triggerFields.at(0) as FormGroup
const conditions = component.getConditionsFormArray(triggerGroup) const filters = component.getFiltersFormArray(triggerGroup)
expect(conditions.length).toBe(10) expect(filters.length).toBe(10)
const customFieldCondition = conditions.at(9) as FormGroup const customFieldFilter = filters.at(9) as FormGroup
expect(customFieldCondition.get('type').value).toBe( expect(customFieldFilter.get('type').value).toBe(
TriggerConditionType.CustomFieldQuery TriggerFilterType.CustomFieldQuery
) )
const model = component.getCustomFieldQueryModel(customFieldCondition) const model = component.getCustomFieldQueryModel(customFieldFilter)
expect(model.isValid()).toBe(true) expect(model.isValid()).toBe(true)
}) })
it('should expose select metadata helpers', () => { it('should expose select metadata helpers', () => {
expect( expect(component.isSelectMultiple(TriggerFilterType.CorrespondentNot)).toBe(
component.isSelectMultiple(TriggerConditionType.CorrespondentNot) true
).toBe(true) )
expect( expect(component.isSelectMultiple(TriggerFilterType.CorrespondentIs)).toBe(
component.isSelectMultiple(TriggerConditionType.CorrespondentIs) false
).toBe(false) )
component.correspondents = [{ id: 1, name: 'C1' } as any] component.correspondents = [{ id: 1, name: 'C1' } as any]
component.documentTypes = [{ id: 2, name: 'DT' } as any] component.documentTypes = [{ id: 2, name: 'DT' } as any]
component.storagePaths = [{ id: 3, name: 'SP' } as any] component.storagePaths = [{ id: 3, name: 'SP' } as any]
expect( expect(
component.getConditionSelectItems(TriggerConditionType.CorrespondentIs) component.getFilterSelectItems(TriggerFilterType.CorrespondentIs)
).toEqual(component.correspondents) ).toEqual(component.correspondents)
expect( expect(
component.getConditionSelectItems(TriggerConditionType.DocumentTypeIs) component.getFilterSelectItems(TriggerFilterType.DocumentTypeIs)
).toEqual(component.documentTypes) ).toEqual(component.documentTypes)
expect( expect(
component.getConditionSelectItems(TriggerConditionType.StoragePathIs) component.getFilterSelectItems(TriggerFilterType.StoragePathIs)
).toEqual(component.storagePaths) ).toEqual(component.storagePaths)
expect( expect(component.getFilterSelectItems(TriggerFilterType.TagsAll)).toEqual(
component.getConditionSelectItems(TriggerConditionType.TagsAll) []
).toEqual([]) )
expect( expect(
component.isCustomFieldQueryCondition( component.isCustomFieldQueryFilter(TriggerFilterType.CustomFieldQuery)
TriggerConditionType.CustomFieldQuery
)
).toBe(true) ).toBe(true)
}) })
it('should return empty select items when definition is missing', () => { it('should return empty select items when definition is missing', () => {
const originalDefinitions = component.conditionDefinitions const originalDefinitions = component.filterDefinitions
component.conditionDefinitions = [] component.filterDefinitions = []
expect( expect(
component.getConditionSelectItems(TriggerConditionType.CorrespondentIs) component.getFilterSelectItems(TriggerFilterType.CorrespondentIs)
).toEqual([]) ).toEqual([])
component.conditionDefinitions = originalDefinitions component.filterDefinitions = originalDefinitions
}) })
it('should return empty select items when definition has unknown source', () => { it('should return empty select items when definition has unknown source', () => {
const originalDefinitions = component.conditionDefinitions const originalDefinitions = component.filterDefinitions
component.conditionDefinitions = [ component.filterDefinitions = [
{ {
id: TriggerConditionType.CorrespondentIs, id: TriggerFilterType.CorrespondentIs,
name: 'Correspondent is', name: 'Correspondent is',
inputType: 'select', inputType: 'select',
allowMultipleEntries: false, allowMultipleEntries: false,
@@ -798,10 +782,10 @@ describe('WorkflowEditDialogComponent', () => {
] ]
expect( expect(
component.getConditionSelectItems(TriggerConditionType.CorrespondentIs) component.getFilterSelectItems(TriggerFilterType.CorrespondentIs)
).toEqual([]) ).toEqual([])
component.conditionDefinitions = originalDefinitions component.filterDefinitions = originalDefinitions
}) })
it('should handle custom field query selection change and validation states', () => { it('should handle custom field query selection change and validation states', () => {
@@ -837,19 +821,19 @@ describe('WorkflowEditDialogComponent', () => {
}) })
it('should recover from invalid custom field query json and update control on changes', () => { it('should recover from invalid custom field query json and update control on changes', () => {
const conditionGroup = new FormGroup({ const filterGroup = new FormGroup({
values: new FormControl('not-json'), values: new FormControl('not-json'),
}) })
component['ensureCustomFieldQueryModel'](conditionGroup, 'not-json') component['ensureCustomFieldQueryModel'](filterGroup, 'not-json')
const model = component['getStoredCustomFieldQueryModel']( const model = component['getStoredCustomFieldQueryModel'](
conditionGroup as any filterGroup as any
) )
expect(model).toBeDefined() expect(model).toBeDefined()
expect(model.queries.length).toBeGreaterThan(0) expect(model.queries.length).toBeGreaterThan(0)
const valuesControl = conditionGroup.get('values') const valuesControl = filterGroup.get('values')
expect(valuesControl.value).toBeNull() expect(valuesControl.value).toBeNull()
const expression = new CustomFieldQueryExpression([ const expression = new CustomFieldQueryExpression([
@@ -865,7 +849,7 @@ describe('WorkflowEditDialogComponent', () => {
expect(valuesControl.value).toEqual(JSON.stringify(expression.serialize())) expect(valuesControl.value).toEqual(JSON.stringify(expression.serialize()))
component['clearCustomFieldQueryModel'](conditionGroup as any) component['clearCustomFieldQueryModel'](filterGroup as any)
}) })
it('should handle custom field query model change edge cases', () => { it('should handle custom field query model change edge cases', () => {
@@ -898,71 +882,61 @@ describe('WorkflowEditDialogComponent', () => {
expect(groupWithControl.get('values').value).toBeNull() expect(groupWithControl.get('values').value).toBeNull()
}) })
it('should normalize condition values for single and multi selects', () => { it('should normalize filter values for single and multi selects', () => {
expect( expect(
component['normalizeConditionValue'](TriggerConditionType.TagsAny) component['normalizeFilterValue'](TriggerFilterType.TagsAny)
).toEqual([]) ).toEqual([])
expect( expect(
component['normalizeConditionValue'](TriggerConditionType.TagsAny, 5) component['normalizeFilterValue'](TriggerFilterType.TagsAny, 5)
).toEqual([5]) ).toEqual([5])
expect( expect(
component['normalizeConditionValue'](TriggerConditionType.TagsAny, [5, 6]) component['normalizeFilterValue'](TriggerFilterType.TagsAny, [5, 6])
).toEqual([5, 6]) ).toEqual([5, 6])
expect( expect(
component['normalizeConditionValue']( component['normalizeFilterValue'](TriggerFilterType.CorrespondentIs, [7])
TriggerConditionType.CorrespondentIs,
[7]
)
).toEqual(7) ).toEqual(7)
expect( expect(
component['normalizeConditionValue']( component['normalizeFilterValue'](TriggerFilterType.CorrespondentIs, 8)
TriggerConditionType.CorrespondentIs,
8
)
).toEqual(8) ).toEqual(8)
const customFieldJson = JSON.stringify(['AND', [[1, 'exact', 'test']]]) const customFieldJson = JSON.stringify(['AND', [[1, 'exact', 'test']]])
expect( expect(
component['normalizeConditionValue']( component['normalizeFilterValue'](
TriggerConditionType.CustomFieldQuery, TriggerFilterType.CustomFieldQuery,
customFieldJson customFieldJson
) )
).toEqual(customFieldJson) ).toEqual(customFieldJson)
const customFieldObject = ['AND', [[1, 'exact', 'other']]] const customFieldObject = ['AND', [[1, 'exact', 'other']]]
expect( expect(
component['normalizeConditionValue']( component['normalizeFilterValue'](
TriggerConditionType.CustomFieldQuery, TriggerFilterType.CustomFieldQuery,
customFieldObject customFieldObject
) )
).toEqual(JSON.stringify(customFieldObject)) ).toEqual(JSON.stringify(customFieldObject))
expect( expect(
component['normalizeConditionValue']( component['normalizeFilterValue'](
TriggerConditionType.CustomFieldQuery, TriggerFilterType.CustomFieldQuery,
false false
) )
).toBeNull() ).toBeNull()
}) })
it('should add and remove condition form groups', () => { it('should add and remove filter form groups', () => {
component['changeDetector'] = { detectChanges: jest.fn() } as any component['changeDetector'] = { detectChanges: jest.fn() } as any
component.object = undefined component.object = undefined
component.addTrigger() component.addTrigger()
const triggerGroup = component.triggerFields.at(0) as FormGroup const triggerGroup = component.triggerFields.at(0) as FormGroup
component.addCondition(triggerGroup) component.addFilter(triggerGroup)
component.removeCondition(triggerGroup, 0) component.removeFilter(triggerGroup, 0)
expect(component.getConditionsFormArray(triggerGroup).length).toBe(0) expect(component.getFiltersFormArray(triggerGroup).length).toBe(0)
component.addCondition(triggerGroup) component.addFilter(triggerGroup)
const conditionArrayAfterAdd = const filterArrayAfterAdd = component.getFiltersFormArray(triggerGroup)
component.getConditionsFormArray(triggerGroup) filterArrayAfterAdd.at(0).get('type').setValue(TriggerFilterType.TagsAll)
conditionArrayAfterAdd expect(component.getFiltersFormArray(triggerGroup).length).toBe(1)
.at(0)
.get('type')
.setValue(TriggerConditionType.TagsAll)
expect(component.getConditionsFormArray(triggerGroup).length).toBe(1)
}) })
it('should remove selected custom field from the form group', () => { it('should remove selected custom field from the form group', () => {

View File

@@ -141,7 +141,7 @@ export const WORKFLOW_ACTION_OPTIONS = [
}, },
] ]
export enum TriggerConditionType { export enum TriggerFilterType {
TagsAny = 'tags_any', TagsAny = 'tags_any',
TagsAll = 'tags_all', TagsAll = 'tags_all',
TagsNone = 'tags_none', TagsNone = 'tags_none',
@@ -154,8 +154,8 @@ export enum TriggerConditionType {
CustomFieldQuery = 'custom_field_query', CustomFieldQuery = 'custom_field_query',
} }
interface TriggerConditionDefinition { interface TriggerFilterDefinition {
id: TriggerConditionType id: TriggerFilterType
name: string name: string
inputType: 'tags' | 'select' | 'customFieldQuery' inputType: 'tags' | 'select' | 'customFieldQuery'
allowMultipleEntries: boolean allowMultipleEntries: boolean
@@ -164,7 +164,7 @@ interface TriggerConditionDefinition {
disabled?: boolean disabled?: boolean
} }
type TriggerConditionOption = TriggerConditionDefinition & { type TriggerFilterOption = TriggerFilterDefinition & {
disabled?: boolean disabled?: boolean
} }
@@ -181,7 +181,7 @@ type TriggerFilterAggregate = {
filter_custom_field_query: string | null filter_custom_field_query: string | null
} }
interface ConditionFilterHandler { interface FilterHandler {
apply: (aggregate: TriggerFilterAggregate, values: any) => void apply: (aggregate: TriggerFilterAggregate, values: any) => void
extract: (trigger: WorkflowTrigger) => any extract: (trigger: WorkflowTrigger) => any
hasValue: (value: any) => boolean hasValue: (value: any) => boolean
@@ -192,35 +192,35 @@ const CUSTOM_FIELD_QUERY_SUBSCRIPTION_KEY = Symbol(
'customFieldQuerySubscription' 'customFieldQuerySubscription'
) )
type CustomFieldConditionGroup = FormGroup & { type CustomFieldFilterGroup = FormGroup & {
[CUSTOM_FIELD_QUERY_MODEL_KEY]?: CustomFieldQueriesModel [CUSTOM_FIELD_QUERY_MODEL_KEY]?: CustomFieldQueriesModel
[CUSTOM_FIELD_QUERY_SUBSCRIPTION_KEY]?: Subscription [CUSTOM_FIELD_QUERY_SUBSCRIPTION_KEY]?: Subscription
} }
const TRIGGER_CONDITION_DEFINITIONS: TriggerConditionDefinition[] = [ const TRIGGER_FILTER_DEFINITIONS: TriggerFilterDefinition[] = [
{ {
id: TriggerConditionType.TagsAny, id: TriggerFilterType.TagsAny,
name: $localize`Has any of these tags`, name: $localize`Has any of these tags`,
inputType: 'tags', inputType: 'tags',
allowMultipleEntries: false, allowMultipleEntries: false,
allowMultipleValues: true, allowMultipleValues: true,
}, },
{ {
id: TriggerConditionType.TagsAll, id: TriggerFilterType.TagsAll,
name: $localize`Has all of these tags`, name: $localize`Has all of these tags`,
inputType: 'tags', inputType: 'tags',
allowMultipleEntries: false, allowMultipleEntries: false,
allowMultipleValues: true, allowMultipleValues: true,
}, },
{ {
id: TriggerConditionType.TagsNone, id: TriggerFilterType.TagsNone,
name: $localize`Does not have these tags`, name: $localize`Does not have these tags`,
inputType: 'tags', inputType: 'tags',
allowMultipleEntries: false, allowMultipleEntries: false,
allowMultipleValues: true, allowMultipleValues: true,
}, },
{ {
id: TriggerConditionType.CorrespondentIs, id: TriggerFilterType.CorrespondentIs,
name: $localize`Has correspondent`, name: $localize`Has correspondent`,
inputType: 'select', inputType: 'select',
allowMultipleEntries: false, allowMultipleEntries: false,
@@ -228,7 +228,7 @@ const TRIGGER_CONDITION_DEFINITIONS: TriggerConditionDefinition[] = [
selectItems: 'correspondents', selectItems: 'correspondents',
}, },
{ {
id: TriggerConditionType.CorrespondentNot, id: TriggerFilterType.CorrespondentNot,
name: $localize`Does not have correspondents`, name: $localize`Does not have correspondents`,
inputType: 'select', inputType: 'select',
allowMultipleEntries: false, allowMultipleEntries: false,
@@ -236,7 +236,7 @@ const TRIGGER_CONDITION_DEFINITIONS: TriggerConditionDefinition[] = [
selectItems: 'correspondents', selectItems: 'correspondents',
}, },
{ {
id: TriggerConditionType.DocumentTypeIs, id: TriggerFilterType.DocumentTypeIs,
name: $localize`Has document type`, name: $localize`Has document type`,
inputType: 'select', inputType: 'select',
allowMultipleEntries: false, allowMultipleEntries: false,
@@ -244,7 +244,7 @@ const TRIGGER_CONDITION_DEFINITIONS: TriggerConditionDefinition[] = [
selectItems: 'documentTypes', selectItems: 'documentTypes',
}, },
{ {
id: TriggerConditionType.DocumentTypeNot, id: TriggerFilterType.DocumentTypeNot,
name: $localize`Does not have document types`, name: $localize`Does not have document types`,
inputType: 'select', inputType: 'select',
allowMultipleEntries: false, allowMultipleEntries: false,
@@ -252,7 +252,7 @@ const TRIGGER_CONDITION_DEFINITIONS: TriggerConditionDefinition[] = [
selectItems: 'documentTypes', selectItems: 'documentTypes',
}, },
{ {
id: TriggerConditionType.StoragePathIs, id: TriggerFilterType.StoragePathIs,
name: $localize`Has storage path`, name: $localize`Has storage path`,
inputType: 'select', inputType: 'select',
allowMultipleEntries: false, allowMultipleEntries: false,
@@ -260,7 +260,7 @@ const TRIGGER_CONDITION_DEFINITIONS: TriggerConditionDefinition[] = [
selectItems: 'storagePaths', selectItems: 'storagePaths',
}, },
{ {
id: TriggerConditionType.StoragePathNot, id: TriggerFilterType.StoragePathNot,
name: $localize`Does not have storage paths`, name: $localize`Does not have storage paths`,
inputType: 'select', inputType: 'select',
allowMultipleEntries: false, allowMultipleEntries: false,
@@ -268,7 +268,7 @@ const TRIGGER_CONDITION_DEFINITIONS: TriggerConditionDefinition[] = [
selectItems: 'storagePaths', selectItems: 'storagePaths',
}, },
{ {
id: TriggerConditionType.CustomFieldQuery, id: TriggerFilterType.CustomFieldQuery,
name: $localize`Matches custom field query`, name: $localize`Matches custom field query`,
inputType: 'customFieldQuery', inputType: 'customFieldQuery',
allowMultipleEntries: false, allowMultipleEntries: false,
@@ -280,18 +280,15 @@ const TRIGGER_MATCHING_ALGORITHMS = MATCHING_ALGORITHMS.filter(
(a) => a.id !== MATCH_AUTO (a) => a.id !== MATCH_AUTO
) )
const CONDITION_FILTER_HANDLERS: Record< const FILTER_HANDLERS: Record<TriggerFilterType, FilterHandler> = {
TriggerConditionType, [TriggerFilterType.TagsAny]: {
ConditionFilterHandler
> = {
[TriggerConditionType.TagsAny]: {
apply: (aggregate, values) => { apply: (aggregate, values) => {
aggregate.filter_has_tags = Array.isArray(values) ? [...values] : [values] aggregate.filter_has_tags = Array.isArray(values) ? [...values] : [values]
}, },
extract: (trigger) => trigger.filter_has_tags, extract: (trigger) => trigger.filter_has_tags,
hasValue: (value) => Array.isArray(value) && value.length > 0, hasValue: (value) => Array.isArray(value) && value.length > 0,
}, },
[TriggerConditionType.TagsAll]: { [TriggerFilterType.TagsAll]: {
apply: (aggregate, values) => { apply: (aggregate, values) => {
aggregate.filter_has_all_tags = Array.isArray(values) aggregate.filter_has_all_tags = Array.isArray(values)
? [...values] ? [...values]
@@ -300,7 +297,7 @@ const CONDITION_FILTER_HANDLERS: Record<
extract: (trigger) => trigger.filter_has_all_tags, extract: (trigger) => trigger.filter_has_all_tags,
hasValue: (value) => Array.isArray(value) && value.length > 0, hasValue: (value) => Array.isArray(value) && value.length > 0,
}, },
[TriggerConditionType.TagsNone]: { [TriggerFilterType.TagsNone]: {
apply: (aggregate, values) => { apply: (aggregate, values) => {
aggregate.filter_has_not_tags = Array.isArray(values) aggregate.filter_has_not_tags = Array.isArray(values)
? [...values] ? [...values]
@@ -309,7 +306,7 @@ const CONDITION_FILTER_HANDLERS: Record<
extract: (trigger) => trigger.filter_has_not_tags, extract: (trigger) => trigger.filter_has_not_tags,
hasValue: (value) => Array.isArray(value) && value.length > 0, hasValue: (value) => Array.isArray(value) && value.length > 0,
}, },
[TriggerConditionType.CorrespondentIs]: { [TriggerFilterType.CorrespondentIs]: {
apply: (aggregate, values) => { apply: (aggregate, values) => {
aggregate.filter_has_correspondent = Array.isArray(values) aggregate.filter_has_correspondent = Array.isArray(values)
? (values[0] ?? null) ? (values[0] ?? null)
@@ -318,7 +315,7 @@ const CONDITION_FILTER_HANDLERS: Record<
extract: (trigger) => trigger.filter_has_correspondent, extract: (trigger) => trigger.filter_has_correspondent,
hasValue: (value) => value !== null && value !== undefined, hasValue: (value) => value !== null && value !== undefined,
}, },
[TriggerConditionType.CorrespondentNot]: { [TriggerFilterType.CorrespondentNot]: {
apply: (aggregate, values) => { apply: (aggregate, values) => {
aggregate.filter_has_not_correspondents = Array.isArray(values) aggregate.filter_has_not_correspondents = Array.isArray(values)
? [...values] ? [...values]
@@ -327,7 +324,7 @@ const CONDITION_FILTER_HANDLERS: Record<
extract: (trigger) => trigger.filter_has_not_correspondents, extract: (trigger) => trigger.filter_has_not_correspondents,
hasValue: (value) => Array.isArray(value) && value.length > 0, hasValue: (value) => Array.isArray(value) && value.length > 0,
}, },
[TriggerConditionType.DocumentTypeIs]: { [TriggerFilterType.DocumentTypeIs]: {
apply: (aggregate, values) => { apply: (aggregate, values) => {
aggregate.filter_has_document_type = Array.isArray(values) aggregate.filter_has_document_type = Array.isArray(values)
? (values[0] ?? null) ? (values[0] ?? null)
@@ -336,7 +333,7 @@ const CONDITION_FILTER_HANDLERS: Record<
extract: (trigger) => trigger.filter_has_document_type, extract: (trigger) => trigger.filter_has_document_type,
hasValue: (value) => value !== null && value !== undefined, hasValue: (value) => value !== null && value !== undefined,
}, },
[TriggerConditionType.DocumentTypeNot]: { [TriggerFilterType.DocumentTypeNot]: {
apply: (aggregate, values) => { apply: (aggregate, values) => {
aggregate.filter_has_not_document_types = Array.isArray(values) aggregate.filter_has_not_document_types = Array.isArray(values)
? [...values] ? [...values]
@@ -345,7 +342,7 @@ const CONDITION_FILTER_HANDLERS: Record<
extract: (trigger) => trigger.filter_has_not_document_types, extract: (trigger) => trigger.filter_has_not_document_types,
hasValue: (value) => Array.isArray(value) && value.length > 0, hasValue: (value) => Array.isArray(value) && value.length > 0,
}, },
[TriggerConditionType.StoragePathIs]: { [TriggerFilterType.StoragePathIs]: {
apply: (aggregate, values) => { apply: (aggregate, values) => {
aggregate.filter_has_storage_path = Array.isArray(values) aggregate.filter_has_storage_path = Array.isArray(values)
? (values[0] ?? null) ? (values[0] ?? null)
@@ -354,7 +351,7 @@ const CONDITION_FILTER_HANDLERS: Record<
extract: (trigger) => trigger.filter_has_storage_path, extract: (trigger) => trigger.filter_has_storage_path,
hasValue: (value) => value !== null && value !== undefined, hasValue: (value) => value !== null && value !== undefined,
}, },
[TriggerConditionType.StoragePathNot]: { [TriggerFilterType.StoragePathNot]: {
apply: (aggregate, values) => { apply: (aggregate, values) => {
aggregate.filter_has_not_storage_paths = Array.isArray(values) aggregate.filter_has_not_storage_paths = Array.isArray(values)
? [...values] ? [...values]
@@ -363,7 +360,7 @@ const CONDITION_FILTER_HANDLERS: Record<
extract: (trigger) => trigger.filter_has_not_storage_paths, extract: (trigger) => trigger.filter_has_not_storage_paths,
hasValue: (value) => Array.isArray(value) && value.length > 0, hasValue: (value) => Array.isArray(value) && value.length > 0,
}, },
[TriggerConditionType.CustomFieldQuery]: { [TriggerFilterType.CustomFieldQuery]: {
apply: (aggregate, values) => { apply: (aggregate, values) => {
aggregate.filter_custom_field_query = values as string aggregate.filter_custom_field_query = values as string
}, },
@@ -405,8 +402,8 @@ export class WorkflowEditDialogComponent
{ {
public WorkflowTriggerType = WorkflowTriggerType public WorkflowTriggerType = WorkflowTriggerType
public WorkflowActionType = WorkflowActionType public WorkflowActionType = WorkflowActionType
public TriggerConditionType = TriggerConditionType public TriggerFilterType = TriggerFilterType
public conditionDefinitions = TRIGGER_CONDITION_DEFINITIONS public filterDefinitions = TRIGGER_FILTER_DEFINITIONS
private correspondentService: CorrespondentService private correspondentService: CorrespondentService
private documentTypeService: DocumentTypeService private documentTypeService: DocumentTypeService
@@ -426,9 +423,9 @@ export class WorkflowEditDialogComponent
private allowedActionTypes = [] private allowedActionTypes = []
private readonly triggerConditionOptionsMap = new WeakMap< private readonly triggerFilterOptionsMap = new WeakMap<
FormArray, FormArray,
TriggerConditionOption[] TriggerFilterOption[]
>() >()
constructor() { constructor() {
@@ -639,7 +636,7 @@ export class WorkflowEditDialogComponent
formValues.triggers = formValues.triggers.map( formValues.triggers = formValues.triggers.map(
(trigger: any, index: number) => { (trigger: any, index: number) => {
const triggerFormGroup = this.triggerFields.at(index) as FormGroup const triggerFormGroup = this.triggerFields.at(index) as FormGroup
const conditions = this.getConditionsFormArray(triggerFormGroup) const filters = this.getFiltersFormArray(triggerFormGroup)
const aggregate: TriggerFilterAggregate = { const aggregate: TriggerFilterAggregate = {
filter_has_tags: [], filter_has_tags: [],
@@ -654,8 +651,8 @@ export class WorkflowEditDialogComponent
filter_custom_field_query: null, filter_custom_field_query: null,
} }
for (const control of conditions.controls) { for (const control of filters.controls) {
const type = control.get('type').value as TriggerConditionType const type = control.get('type').value as TriggerFilterType
const values = control.get('values').value const values = control.get('values').value
if (values === null || values === undefined) { if (values === null || values === undefined) {
@@ -666,7 +663,7 @@ export class WorkflowEditDialogComponent
continue continue
} }
const handler = CONDITION_FILTER_HANDLERS[type] const handler = FILTER_HANDLERS[type]
handler?.apply(aggregate, values) handler?.apply(aggregate, values)
} }
@@ -688,7 +685,7 @@ export class WorkflowEditDialogComponent
trigger.filter_custom_field_query = trigger.filter_custom_field_query =
aggregate.filter_custom_field_query ?? null aggregate.filter_custom_field_query ?? null
delete trigger.conditions delete trigger.filters
return trigger return trigger
} }
@@ -702,40 +699,38 @@ export class WorkflowEditDialogComponent
return formGroup.get('matching_algorithm').value !== MATCH_NONE return formGroup.get('matching_algorithm').value !== MATCH_NONE
} }
private createConditionFormGroup( private createFilterFormGroup(
type: TriggerConditionType, type: TriggerFilterType,
initialValue?: any initialValue?: any
): FormGroup { ): FormGroup {
const group = new FormGroup({ const group = new FormGroup({
type: new FormControl(type), type: new FormControl(type),
values: new FormControl(this.normalizeConditionValue(type, initialValue)), values: new FormControl(this.normalizeFilterValue(type, initialValue)),
}) })
group group.get('type').valueChanges.subscribe((newType: TriggerFilterType) => {
.get('type') if (newType === TriggerFilterType.CustomFieldQuery) {
.valueChanges.subscribe((newType: TriggerConditionType) => { this.ensureCustomFieldQueryModel(group)
if (newType === TriggerConditionType.CustomFieldQuery) { } else {
this.ensureCustomFieldQueryModel(group) this.clearCustomFieldQueryModel(group)
} else { group.get('values').setValue(this.getDefaultFilterValue(newType), {
this.clearCustomFieldQueryModel(group) emitEvent: false,
group.get('values').setValue(this.getDefaultConditionValue(newType), { })
emitEvent: false, }
}) })
}
})
if (type === TriggerConditionType.CustomFieldQuery) { if (type === TriggerFilterType.CustomFieldQuery) {
this.ensureCustomFieldQueryModel(group, initialValue) this.ensureCustomFieldQueryModel(group, initialValue)
} }
return group return group
} }
private buildConditionFormArray(trigger: WorkflowTrigger): FormArray { private buildFiltersFormArray(trigger: WorkflowTrigger): FormArray {
const conditions = new FormArray([]) const filters = new FormArray([])
for (const definition of this.conditionDefinitions) { for (const definition of this.filterDefinitions) {
const handler = CONDITION_FILTER_HANDLERS[definition.id] const handler = FILTER_HANDLERS[definition.id]
if (!handler) { if (!handler) {
continue continue
} }
@@ -745,24 +740,24 @@ export class WorkflowEditDialogComponent
continue continue
} }
conditions.push(this.createConditionFormGroup(definition.id, value)) filters.push(this.createFilterFormGroup(definition.id, value))
} }
return conditions return filters
} }
getConditionsFormArray(formGroup: FormGroup): FormArray { getFiltersFormArray(formGroup: FormGroup): FormArray {
return formGroup.get('conditions') as FormArray return formGroup.get('filters') as FormArray
} }
getConditionTypeOptions(formGroup: FormGroup, conditionIndex: number) { getFilterTypeOptions(formGroup: FormGroup, filterIndex: number) {
const conditions = this.getConditionsFormArray(formGroup) const filters = this.getFiltersFormArray(formGroup)
const options = this.getConditionTypeOptionsForArray(conditions) const options = this.getFilterTypeOptionsForArray(filters)
const currentType = conditions.at(conditionIndex).get('type') const currentType = filters.at(filterIndex).get('type')
.value as TriggerConditionType .value as TriggerFilterType
const usedTypes = new Set( const usedTypes = new Set(
conditions.controls.map( filters.controls.map(
(control) => control.get('type').value as TriggerConditionType (control) => control.get('type').value as TriggerFilterType
) )
) )
@@ -778,15 +773,15 @@ export class WorkflowEditDialogComponent
return options return options
} }
canAddCondition(formGroup: FormGroup): boolean { canAddFilter(formGroup: FormGroup): boolean {
const conditions = this.getConditionsFormArray(formGroup) const filters = this.getFiltersFormArray(formGroup)
const usedTypes = new Set( const usedTypes = new Set(
conditions.controls.map( filters.controls.map(
(control) => control.get('type').value as TriggerConditionType (control) => control.get('type').value as TriggerFilterType
) )
) )
return this.conditionDefinitions.some((definition) => { return this.filterDefinitions.some((definition) => {
if (definition.allowMultipleEntries) { if (definition.allowMultipleEntries) {
return true return true
} }
@@ -794,19 +789,19 @@ export class WorkflowEditDialogComponent
}) })
} }
addCondition(triggerFormGroup: FormGroup): FormGroup | null { addFilter(triggerFormGroup: FormGroup): FormGroup | null {
const triggerIndex = this.triggerFields.controls.indexOf(triggerFormGroup) const triggerIndex = this.triggerFields.controls.indexOf(triggerFormGroup)
if (triggerIndex === -1) { if (triggerIndex === -1) {
return null return null
} }
const conditions = this.getConditionsFormArray(triggerFormGroup) const filters = this.getFiltersFormArray(triggerFormGroup)
const availableDefinition = this.conditionDefinitions.find((definition) => { const availableDefinition = this.filterDefinitions.find((definition) => {
if (definition.allowMultipleEntries) { if (definition.allowMultipleEntries) {
return true return true
} }
return !conditions.controls.some( return !filters.controls.some(
(control) => control.get('type').value === definition.id (control) => control.get('type').value === definition.id
) )
}) })
@@ -815,72 +810,67 @@ export class WorkflowEditDialogComponent
return null return null
} }
conditions.push(this.createConditionFormGroup(availableDefinition.id)) filters.push(this.createFilterFormGroup(availableDefinition.id))
triggerFormGroup.markAsDirty() triggerFormGroup.markAsDirty()
triggerFormGroup.markAsTouched() triggerFormGroup.markAsTouched()
return conditions.at(-1) as FormGroup return filters.at(-1) as FormGroup
} }
removeCondition(triggerFormGroup: FormGroup, conditionIndex: number) { removeFilter(triggerFormGroup: FormGroup, filterIndex: number) {
const triggerIndex = this.triggerFields.controls.indexOf(triggerFormGroup) const triggerIndex = this.triggerFields.controls.indexOf(triggerFormGroup)
if (triggerIndex === -1) { if (triggerIndex === -1) {
return return
} }
const conditions = this.getConditionsFormArray(triggerFormGroup) const filters = this.getFiltersFormArray(triggerFormGroup)
const conditionGroup = conditions.at(conditionIndex) as FormGroup const filterGroup = filters.at(filterIndex) as FormGroup
if ( if (filterGroup?.get('type').value === TriggerFilterType.CustomFieldQuery) {
conditionGroup?.get('type').value === this.clearCustomFieldQueryModel(filterGroup)
TriggerConditionType.CustomFieldQuery
) {
this.clearCustomFieldQueryModel(conditionGroup)
} }
conditions.removeAt(conditionIndex) filters.removeAt(filterIndex)
triggerFormGroup.markAsDirty() triggerFormGroup.markAsDirty()
triggerFormGroup.markAsTouched() triggerFormGroup.markAsTouched()
} }
getConditionDefinition( getFilterDefinition(
type: TriggerConditionType type: TriggerFilterType
): TriggerConditionDefinition | undefined { ): TriggerFilterDefinition | undefined {
return this.conditionDefinitions.find( return this.filterDefinitions.find((definition) => definition.id === type)
(definition) => definition.id === type
)
} }
getConditionName(type: TriggerConditionType): string { getFilterName(type: TriggerFilterType): string {
return this.getConditionDefinition(type)?.name ?? '' return this.getFilterDefinition(type)?.name ?? ''
} }
isTagsCondition(type: TriggerConditionType): boolean { isTagsFilter(type: TriggerFilterType): boolean {
return this.getConditionDefinition(type)?.inputType === 'tags' return this.getFilterDefinition(type)?.inputType === 'tags'
} }
isCustomFieldQueryCondition(type: TriggerConditionType): boolean { isCustomFieldQueryFilter(type: TriggerFilterType): boolean {
return this.getConditionDefinition(type)?.inputType === 'customFieldQuery' return this.getFilterDefinition(type)?.inputType === 'customFieldQuery'
} }
isMultiValueCondition(type: TriggerConditionType): boolean { isMultiValueFilter(type: TriggerFilterType): boolean {
switch (type) { switch (type) {
case TriggerConditionType.TagsAny: case TriggerFilterType.TagsAny:
case TriggerConditionType.TagsAll: case TriggerFilterType.TagsAll:
case TriggerConditionType.TagsNone: case TriggerFilterType.TagsNone:
case TriggerConditionType.CorrespondentNot: case TriggerFilterType.CorrespondentNot:
case TriggerConditionType.DocumentTypeNot: case TriggerFilterType.DocumentTypeNot:
case TriggerConditionType.StoragePathNot: case TriggerFilterType.StoragePathNot:
return true return true
default: default:
return false return false
} }
} }
isSelectMultiple(type: TriggerConditionType): boolean { isSelectMultiple(type: TriggerFilterType): boolean {
return !this.isTagsCondition(type) && this.isMultiValueCondition(type) return !this.isTagsFilter(type) && this.isMultiValueFilter(type)
} }
getConditionSelectItems(type: TriggerConditionType) { getFilterSelectItems(type: TriggerFilterType) {
const definition = this.getConditionDefinition(type) const definition = this.getFilterDefinition(type)
if (!definition || definition.inputType !== 'select') { if (!definition || definition.inputType !== 'select') {
return [] return []
} }
@@ -917,36 +907,36 @@ export class WorkflowEditDialogComponent
return model.isEmpty() || model.isValid() return model.isEmpty() || model.isValid()
} }
private getConditionTypeOptionsForArray( private getFilterTypeOptionsForArray(
conditions: FormArray filters: FormArray
): TriggerConditionOption[] { ): TriggerFilterOption[] {
let cached = this.triggerConditionOptionsMap.get(conditions) let cached = this.triggerFilterOptionsMap.get(filters)
if (!cached) { if (!cached) {
cached = this.conditionDefinitions.map((definition) => ({ cached = this.filterDefinitions.map((definition) => ({
...definition, ...definition,
disabled: false, disabled: false,
})) }))
this.triggerConditionOptionsMap.set(conditions, cached) this.triggerFilterOptionsMap.set(filters, cached)
} }
return cached return cached
} }
private ensureCustomFieldQueryModel( private ensureCustomFieldQueryModel(
conditionGroup: FormGroup, filterGroup: FormGroup,
initialValue?: any initialValue?: any
): CustomFieldQueriesModel { ): CustomFieldQueriesModel {
const existingModel = this.getStoredCustomFieldQueryModel(conditionGroup) const existingModel = this.getStoredCustomFieldQueryModel(filterGroup)
if (existingModel) { if (existingModel) {
return existingModel return existingModel
} }
const model = new CustomFieldQueriesModel() const model = new CustomFieldQueriesModel()
this.setCustomFieldQueryModel(conditionGroup, model) this.setCustomFieldQueryModel(filterGroup, model)
const rawValue = const rawValue =
typeof initialValue === 'string' typeof initialValue === 'string'
? initialValue ? initialValue
: (conditionGroup.get('values').value as string) : (filterGroup.get('values').value as string)
if (rawValue) { if (rawValue) {
try { try {
@@ -962,46 +952,45 @@ export class WorkflowEditDialogComponent
const subscription = model.changed const subscription = model.changed
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => { .subscribe(() => {
this.onCustomFieldQueryModelChanged(conditionGroup, model) this.onCustomFieldQueryModelChanged(filterGroup, model)
}) })
conditionGroup[CUSTOM_FIELD_QUERY_SUBSCRIPTION_KEY]?.unsubscribe() filterGroup[CUSTOM_FIELD_QUERY_SUBSCRIPTION_KEY]?.unsubscribe()
conditionGroup[CUSTOM_FIELD_QUERY_SUBSCRIPTION_KEY] = subscription filterGroup[CUSTOM_FIELD_QUERY_SUBSCRIPTION_KEY] = subscription
this.onCustomFieldQueryModelChanged(conditionGroup, model) this.onCustomFieldQueryModelChanged(filterGroup, model)
return model return model
} }
private clearCustomFieldQueryModel(conditionGroup: FormGroup) { private clearCustomFieldQueryModel(filterGroup: FormGroup) {
const group = conditionGroup as CustomFieldConditionGroup const group = filterGroup as CustomFieldFilterGroup
group[CUSTOM_FIELD_QUERY_SUBSCRIPTION_KEY]?.unsubscribe() group[CUSTOM_FIELD_QUERY_SUBSCRIPTION_KEY]?.unsubscribe()
delete group[CUSTOM_FIELD_QUERY_SUBSCRIPTION_KEY] delete group[CUSTOM_FIELD_QUERY_SUBSCRIPTION_KEY]
delete group[CUSTOM_FIELD_QUERY_MODEL_KEY] delete group[CUSTOM_FIELD_QUERY_MODEL_KEY]
} }
private getStoredCustomFieldQueryModel( private getStoredCustomFieldQueryModel(
conditionGroup: FormGroup filterGroup: FormGroup
): CustomFieldQueriesModel | null { ): CustomFieldQueriesModel | null {
return ( return (
(conditionGroup as CustomFieldConditionGroup)[ (filterGroup as CustomFieldFilterGroup)[CUSTOM_FIELD_QUERY_MODEL_KEY] ??
CUSTOM_FIELD_QUERY_MODEL_KEY null
] ?? null
) )
} }
private setCustomFieldQueryModel( private setCustomFieldQueryModel(
conditionGroup: FormGroup, filterGroup: FormGroup,
model: CustomFieldQueriesModel model: CustomFieldQueriesModel
) { ) {
const group = conditionGroup as CustomFieldConditionGroup const group = filterGroup as CustomFieldFilterGroup
group[CUSTOM_FIELD_QUERY_MODEL_KEY] = model group[CUSTOM_FIELD_QUERY_MODEL_KEY] = model
} }
private onCustomFieldQueryModelChanged( private onCustomFieldQueryModelChanged(
conditionGroup: FormGroup, filterGroup: FormGroup,
model: CustomFieldQueriesModel model: CustomFieldQueriesModel
) { ) {
const control = conditionGroup.get('values') const control = filterGroup.get('values')
if (!control) { if (!control) {
return return
} }
@@ -1020,26 +1009,26 @@ export class WorkflowEditDialogComponent
control.setValue(serialized, { emitEvent: false }) control.setValue(serialized, { emitEvent: false })
} }
private getDefaultConditionValue(type: TriggerConditionType) { private getDefaultFilterValue(type: TriggerFilterType) {
if (type === TriggerConditionType.CustomFieldQuery) { if (type === TriggerFilterType.CustomFieldQuery) {
return null return null
} }
return this.isMultiValueCondition(type) ? [] : null return this.isMultiValueFilter(type) ? [] : null
} }
private normalizeConditionValue(type: TriggerConditionType, value?: any) { private normalizeFilterValue(type: TriggerFilterType, value?: any) {
if (value === undefined || value === null) { if (value === undefined || value === null) {
return this.getDefaultConditionValue(type) return this.getDefaultFilterValue(type)
} }
if (type === TriggerConditionType.CustomFieldQuery) { if (type === TriggerFilterType.CustomFieldQuery) {
if (typeof value === 'string') { if (typeof value === 'string') {
return value return value
} }
return value ? JSON.stringify(value) : null return value ? JSON.stringify(value) : null
} }
if (this.isMultiValueCondition(type)) { if (this.isMultiValueFilter(type)) {
return Array.isArray(value) ? [...value] : [value] return Array.isArray(value) ? [...value] : [value]
} }
@@ -1065,7 +1054,7 @@ export class WorkflowEditDialogComponent
matching_algorithm: new FormControl(trigger.matching_algorithm), matching_algorithm: new FormControl(trigger.matching_algorithm),
match: new FormControl(trigger.match), match: new FormControl(trigger.match),
is_insensitive: new FormControl(trigger.is_insensitive), is_insensitive: new FormControl(trigger.is_insensitive),
conditions: this.buildConditionFormArray(trigger), filters: this.buildFiltersFormArray(trigger),
schedule_offset_days: new FormControl(trigger.schedule_offset_days), schedule_offset_days: new FormControl(trigger.schedule_offset_days),
schedule_is_recurring: new FormControl(trigger.schedule_is_recurring), schedule_is_recurring: new FormControl(trigger.schedule_is_recurring),
schedule_recurring_interval_days: new FormControl( schedule_recurring_interval_days: new FormControl(