Feature: workflow removal action (#5928)

---------

Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
This commit is contained in:
shamoon
2024-03-04 09:37:42 -08:00
committed by GitHub
parent f6084acfc8
commit f07441a408
19 changed files with 1920 additions and 354 deletions

View File

@@ -91,63 +91,7 @@
</div>
<div ngbAccordionCollapse>
<div ngbAccordionBody>
<pngx-input-select i18n-title title="Action type" [horizontal]="true" [items]="actionTypeOptions" formControlName="type"></pngx-input-select>
<input type="hidden" formControlName="id" />
<div class="row">
<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>
</div>
<div class="col">
<pngx-input-select i18n-title title="Assign owner" [items]="users" bindLabel="username" formControlName="assign_owner" [allowNull]="true"></pngx-input-select>
<div>
<label class="form-label" i18n>Assign view permissions</label>
<div class="mb-2">
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="view" formControlName="assign_view_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="view" formControlName="assign_view_groups"></pngx-permissions-group>
</div>
</div>
</div>
<label class="form-label" i18n>Assign edit permissions</label>
<div>
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="change" formControlName="assign_change_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="change" formControlName="assign_change_groups"></pngx-permissions-group>
</div>
</div>
<small class="form-text text-muted text-end d-block" i18n>Edit permissions also grant viewing permissions</small>
</div>
</div>
</div>
</div>
<ng-template [ngTemplateOutlet]="actionForm" [ngTemplateOutletContext]="{ formGroup: actionFields.controls[i], action: action }"></ng-template>
</div>
</div>
</div>
@@ -201,3 +145,154 @@
</div>
</div>
</ng-template>
<ng-template #actionForm let-formGroup="formGroup" let action="action">
<div [formGroup]="formGroup">
<input type="hidden" formControlName="id" />
<pngx-input-select i18n-title title="Action type" [horizontal]="true" [items]="actionTypeOptions" formControlName="type"></pngx-input-select>
@switch(formGroup.get('type').value) {
@case ( WorkflowActionType.Assignment) {
<div class="row">
<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>
</div>
<div class="col">
<pngx-input-select i18n-title title="Assign owner" [items]="users" bindLabel="username" formControlName="assign_owner" [allowNull]="true"></pngx-input-select>
<div>
<label class="form-label" i18n>Assign view permissions</label>
<div class="mb-2">
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="view" formControlName="assign_view_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="view" formControlName="assign_view_groups"></pngx-permissions-group>
</div>
</div>
</div>
<label class="form-label" i18n>Assign edit permissions</label>
<div>
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="change" formControlName="assign_change_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="change" formControlName="assign_change_groups"></pngx-permissions-group>
</div>
</div>
<small class="form-text text-muted text-end d-block" i18n>Edit permissions also grant viewing permissions</small>
</div>
</div>
</div>
</div>
}
@case (WorkflowActionType.Removal) {
<div class="row">
<div class="col">
<h6 class="form-label" i18n>Remove tags</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_tags"></pngx-input-switch>
<div class="mt-n3">
<pngx-input-tags [allowCreate]="false" title="" formControlName="remove_tags"></pngx-input-tags>
</div>
<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>
</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>
</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>
</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>
</div>
</div>
<div class="col">
<h6 class="form-label" i18n>Remove owners</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_owners"></pngx-input-switch>
<div class="mt-n3">
<pngx-input-select i18n-title title="" multiple="true" [items]="users" bindLabel="username" formControlName="remove_owners"></pngx-input-select>
</div>
<h6 class="form-label" i18n>Remove permissions</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_permissions"></pngx-input-switch>
<div>
<label class="form-label" i18n>View permissions</label>
<div class="mb-2">
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="view" formControlName="remove_view_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="view" formControlName="remove_view_groups"></pngx-permissions-group>
</div>
</div>
</div>
<label class="form-label" i18n>Edit permissions</label>
<div>
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="change" formControlName="remove_change_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="change" formControlName="remove_change_groups"></pngx-permissions-group>
</div>
</div>
<small class="form-text text-muted text-end d-block" i18n>Edit permissions also grant viewing permissions</small>
</div>
</div>
</div>
</div>
}
}
</div>
</ng-template>

View File

@@ -235,4 +235,103 @@ describe('WorkflowEditDialogComponent', () => {
MATCHING_ALGORITHMS.find((a) => a.id === MATCH_AUTO)
)
})
it('should disable or enable action fields based on removal action type', () => {
const workflow: Workflow = {
name: 'Workflow 1',
id: 1,
order: 1,
enabled: true,
triggers: [],
actions: [
{
id: 1,
type: WorkflowActionType.Removal,
remove_all_tags: true,
remove_all_document_types: true,
remove_all_correspondents: true,
remove_all_storage_paths: true,
remove_all_custom_fields: true,
remove_all_owners: true,
remove_all_permissions: true,
},
],
}
component.object = workflow
component.ngOnInit()
component['checkRemovalActionFields'](workflow)
// Assert that the action fields are disabled or enabled correctly
expect(
component.actionFields.at(0).get('remove_tags').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_document_types').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_correspondents').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_storage_paths').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_custom_fields').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_owners').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_view_users').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_view_groups').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_change_users').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_change_groups').disabled
).toBeTruthy()
workflow.actions[0].remove_all_tags = false
workflow.actions[0].remove_all_document_types = false
workflow.actions[0].remove_all_correspondents = false
workflow.actions[0].remove_all_storage_paths = false
workflow.actions[0].remove_all_custom_fields = false
workflow.actions[0].remove_all_owners = false
workflow.actions[0].remove_all_permissions = false
component['checkRemovalActionFields'](workflow)
// Assert that the action fields are disabled or enabled correctly
expect(component.actionFields.at(0).get('remove_tags').disabled).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_document_types').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_correspondents').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_storage_paths').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_custom_fields').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_owners').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_view_users').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_view_groups').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_change_users').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_change_groups').disabled
).toBeFalsy()
})
})

View File

@@ -68,6 +68,10 @@ export const WORKFLOW_ACTION_OPTIONS = [
id: WorkflowActionType.Assignment,
name: $localize`Assignment`,
},
{
id: WorkflowActionType.Removal,
name: $localize`Removal`,
},
]
const TRIGGER_MATCHING_ALGORITHMS = MATCHING_ALGORITHMS.filter(
@@ -84,6 +88,7 @@ export class WorkflowEditDialogComponent
implements OnInit
{
public WorkflowTriggerType = WorkflowTriggerType
public WorkflowActionType = WorkflowActionType
templates: Workflow[]
correspondents: Correspondent[]
@@ -159,6 +164,124 @@ export class WorkflowEditDialogComponent
ngOnInit(): void {
super.ngOnInit()
this.updateAllTriggerActionFields()
this.objectForm.valueChanges.subscribe(
this.checkRemovalActionFields.bind(this)
)
this.checkRemovalActionFields(this.objectForm.value)
}
private checkRemovalActionFields(formWorkflow: Workflow) {
formWorkflow.actions
.filter((action) => action.type === WorkflowActionType.Removal)
.forEach((action, i) => {
if (action.remove_all_tags) {
this.actionFields
.at(i)
.get('remove_tags')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_tags')
.enable({ emitEvent: false })
}
if (action.remove_all_document_types) {
this.actionFields
.at(i)
.get('remove_document_types')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_document_types')
.enable({ emitEvent: false })
}
if (action.remove_all_correspondents) {
this.actionFields
.at(i)
.get('remove_correspondents')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_correspondents')
.enable({ emitEvent: false })
}
if (action.remove_all_storage_paths) {
this.actionFields
.at(i)
.get('remove_storage_paths')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_storage_paths')
.enable({ emitEvent: false })
}
if (action.remove_all_custom_fields) {
this.actionFields
.at(i)
.get('remove_custom_fields')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_custom_fields')
.enable({ emitEvent: false })
}
if (action.remove_all_owners) {
this.actionFields
.at(i)
.get('remove_owners')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_owners')
.enable({ emitEvent: false })
}
if (action.remove_all_permissions) {
this.actionFields
.at(i)
.get('remove_view_users')
.disable({ emitEvent: false })
this.actionFields
.at(i)
.get('remove_view_groups')
.disable({ emitEvent: false })
this.actionFields
.at(i)
.get('remove_change_users')
.disable({ emitEvent: false })
this.actionFields
.at(i)
.get('remove_change_groups')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_view_users')
.enable({ emitEvent: false })
this.actionFields
.at(i)
.get('remove_view_groups')
.enable({ emitEvent: false })
this.actionFields
.at(i)
.get('remove_change_users')
.enable({ emitEvent: false })
this.actionFields
.at(i)
.get('remove_change_groups')
.enable({ emitEvent: false })
}
})
}
get triggerFields(): FormArray {
@@ -215,6 +338,31 @@ export class WorkflowEditDialogComponent
assign_change_users: new FormControl(action.assign_change_users),
assign_change_groups: new FormControl(action.assign_change_groups),
assign_custom_fields: new FormControl(action.assign_custom_fields),
remove_tags: new FormControl(action.remove_tags),
remove_all_tags: new FormControl(action.remove_all_tags),
remove_document_types: new FormControl(action.remove_document_types),
remove_all_document_types: new FormControl(
action.remove_all_document_types
),
remove_correspondents: new FormControl(action.remove_correspondents),
remove_all_correspondents: new FormControl(
action.remove_all_correspondents
),
remove_storage_paths: new FormControl(action.remove_storage_paths),
remove_all_storage_paths: new FormControl(
action.remove_all_storage_paths
),
remove_owners: new FormControl(action.remove_owners),
remove_all_owners: new FormControl(action.remove_all_owners),
remove_view_users: new FormControl(action.remove_view_users),
remove_view_groups: new FormControl(action.remove_view_groups),
remove_change_users: new FormControl(action.remove_change_users),
remove_change_groups: new FormControl(action.remove_change_groups),
remove_all_permissions: new FormControl(action.remove_all_permissions),
remove_custom_fields: new FormControl(action.remove_custom_fields),
remove_all_custom_fields: new FormControl(
action.remove_all_custom_fields
),
}),
{ emitEvent }
)
@@ -290,6 +438,23 @@ export class WorkflowEditDialogComponent
assign_change_users: [],
assign_change_groups: [],
assign_custom_fields: [],
remove_tags: [],
remove_all_tags: false,
remove_document_types: [],
remove_all_document_types: false,
remove_correspondents: [],
remove_all_correspondents: false,
remove_storage_paths: [],
remove_all_storage_paths: false,
remove_owners: [],
remove_all_owners: false,
remove_view_users: [],
remove_view_groups: [],
remove_change_users: [],
remove_change_groups: [],
remove_all_permissions: false,
remove_custom_fields: [],
remove_all_custom_fields: false,
}
this.object.actions.push(action)
this.createActionField(action)

View File

@@ -1,4 +1,4 @@
<div class="paperless-input-select">
<div class="paperless-input-select" [class.disabled]="disabled">
<div>
<ng-select name="inputId" [(ngModel)]="value"
[disabled]="disabled"

View File

@@ -0,0 +1,11 @@
.paperless-input-select.disabled {
cursor: not-allowed;
::ng-deep ng-select {
pointer-events: none;
.ng-select-container {
background-color: var(--pngx-bg-alt) !important;
}
}
}

View File

@@ -1,4 +1,4 @@
<div class="paperless-input-select">
<div class="paperless-input-select" [class.disabled]="disabled">
<div>
<ng-select name="inputId" [(ngModel)]="value"
[disabled]="disabled"

View File

@@ -0,0 +1,11 @@
.paperless-input-select.disabled {
cursor: not-allowed;
::ng-deep ng-select {
pointer-events: none;
.ng-select-container {
background-color: var(--pngx-bg-alt) !important;
}
}
}

View File

@@ -1,6 +1,7 @@
// styles for ng-select child are in styles.scss
.paperless-input-select.disabled {
.input-group {
.input-group,
div > div {
cursor: not-allowed;
}