Dynamic adding / removing workflow triggers + actions

This commit is contained in:
shamoon 2023-12-26 22:20:02 -08:00
parent 220e9993a1
commit 2f61968e2e
5 changed files with 111 additions and 13 deletions

View File

@ -241,7 +241,7 @@
ngbPopover="Workflows" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" ngbPopover="Workflows" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim"> container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor"> <svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#boxes" /> <use xlink:href="assets/bootstrap-icons.svg#arrow-left-right" />
</svg><span>&nbsp;<ng-container i18n>Workflows</ng-container></span> </svg><span>&nbsp;<ng-container i18n>Workflows</ng-container></span>
</a> </a>
</li> </li>

View File

@ -21,13 +21,29 @@
<div ngbAccordionCollapse> <div ngbAccordionCollapse>
<div ngbAccordionBody> <div ngbAccordionBody>
<ng-template> <ng-template>
<p class="small" i18n>Trigger Workflow On:</p> <div class="d-flex">
<p class="p-2" i18n>Trigger Workflow On:</p>
<button type="button" class="btn btn-sm btn-outline-primary ms-auto mb-3" (click)="addTrigger()">
<svg class="sidebaricon me-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
</svg>
<ng-container i18n>Add Trigger</ng-container>
</button>
</div>
<div ngbAccordion [closeOthers]="true"> <div ngbAccordion [closeOthers]="true">
@for (trigger of object.triggers; track trigger; let i = $index){ @for (trigger of object.triggers; track trigger; let i = $index){
<div ngbAccordionItem [formGroup]="triggerFields.controls[i]"> <div ngbAccordionItem [formGroup]="triggerFields.controls[i]">
<h2 ngbAccordionHeader> <div ngbAccordionHeader>
<button ngbAccordionButton>{{getTypeOptionName(triggerFields.controls[i].value.type)}} ({{i + 1}})</button> <button ngbAccordionButton>{{i + 1}}. {{getTypeOptionName(triggerFields.controls[i].value.type)}}
</h2>
<button type="button" class="btn btn-link text-danger ms-3" (click)="removeTrigger(i)">
<svg class="sidebaricon me-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg>
<ng-container i18n>Delete</ng-container>
</button>
</button>
</div>
<div ngbAccordionCollapse> <div ngbAccordionCollapse>
<div ngbAccordionBody> <div ngbAccordionBody>
@ -64,13 +80,29 @@
<div ngbAccordionCollapse> <div ngbAccordionCollapse>
<div ngbAccordionBody> <div ngbAccordionBody>
<ng-template> <ng-template>
<p class="small" i18n>Apply Actions:</p> <div class="d-flex">
<p class="p-2" i18n>Apply Actions:</p>
<button type="button" class="btn btn-sm btn-outline-primary ms-auto mb-3" (click)="addAction()">
<svg class="sidebaricon me-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
</svg>
<ng-container i18n>Add Action</ng-container>
</button>
</div>
<div ngbAccordion [closeOthers]="true"> <div ngbAccordion [closeOthers]="true">
@for (action of object.actions; track action; let i = $index){ @for (action of object.actions; track action; let i = $index){
<div ngbAccordionItem [formGroup]="actionFields.controls[i]"> <div ngbAccordionItem [formGroup]="actionFields.controls[i]">
<h2 ngbAccordionHeader> <div ngbAccordionHeader>
<button ngbAccordionButton>{{i + 1}}</button> <button ngbAccordionButton><ng-container i18n>Action</ng-container> {{i + 1}}
</h2>
<button type="button" class="btn btn-link text-danger ms-3" (click)="removeAction(i)">
<svg class="sidebaricon me-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg>
<ng-container i18n>Delete</ng-container>
</button>
</button>
</div>
<div ngbAccordionCollapse> <div ngbAccordionCollapse>
<div ngbAccordionBody> <div ngbAccordionBody>

View File

@ -0,0 +1,5 @@
.btn.text-danger {
&:hover, &:focus {
color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important;
}
}

View File

@ -195,4 +195,47 @@ export class WorkflowEditDialogComponent
getTypeOptionName(type: WorkflowTriggerType): string { getTypeOptionName(type: WorkflowTriggerType): string {
return this.typeOptions.find((t) => t.id === type).name ?? '' return this.typeOptions.find((t) => t.id === type).name ?? ''
} }
addTrigger() {
this.object.triggers.push({
type: WorkflowTriggerType.Consumption,
sources: [],
filter_filename: null,
filter_path: null,
filter_mailrule: null,
filter_has_tags: [],
filter_has_correspondent: null,
filter_has_document_type: null,
})
this.updateTriggerActionFields()
}
addAction() {
this.object.actions.push({
assign_title: null,
assign_tags: [],
assign_document_type: null,
assign_correspondent: null,
assign_storage_path: null,
assign_owner: null,
assign_view_users: [],
assign_view_groups: [],
assign_change_users: [],
assign_change_groups: [],
assign_custom_fields: [],
})
this.updateTriggerActionFields()
}
removeTrigger(index: number) {
this.object.triggers.splice(index, 1)
this.updateTriggerActionFields()
}
removeAction(index: number) {
this.object.actions.splice(index, 1)
this.updateTriggerActionFields()
}
} }

View File

@ -1262,10 +1262,10 @@ class BulkEditObjectPermissionsSerializer(serializers.Serializer, SetPermissions
class WorkflowTriggerSerializer(serializers.ModelSerializer): class WorkflowTriggerSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False) id = serializers.IntegerField(required=False, allow_null=True)
sources = fields.MultipleChoiceField( sources = fields.MultipleChoiceField(
choices=WorkflowTrigger.DocumentSourceChoices.choices, choices=WorkflowTrigger.DocumentSourceChoices.choices,
allow_empty=False, allow_empty=True,
default={ default={
DocumentSource.ConsumeFolder, DocumentSource.ConsumeFolder,
DocumentSource.ApiUpload, DocumentSource.ApiUpload,
@ -1324,7 +1324,7 @@ class WorkflowTriggerSerializer(serializers.ModelSerializer):
class WorkflowActionSerializer(serializers.ModelSerializer): class WorkflowActionSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False) id = serializers.IntegerField(required=False, allow_null=True)
assign_correspondent = CorrespondentField(allow_null=True, required=False) assign_correspondent = CorrespondentField(allow_null=True, required=False)
assign_tags = TagsField(many=True, allow_null=True, required=False) assign_tags = TagsField(many=True, allow_null=True, required=False)
assign_document_type = DocumentTypeField(allow_null=True, required=False) assign_document_type = DocumentTypeField(allow_null=True, required=False)
@ -1381,10 +1381,13 @@ class WorkflowSerializer(serializers.ModelSerializer):
if triggers is not None: if triggers is not None:
for trigger in triggers: for trigger in triggers:
filter_has_tags = trigger.pop("filter_has_tags", None)
trigger_instance, _ = WorkflowTrigger.objects.update_or_create( trigger_instance, _ = WorkflowTrigger.objects.update_or_create(
id=trigger["id"], id=trigger["id"],
defaults=trigger, defaults=trigger,
) )
if filter_has_tags is not None:
trigger_instance.filter_has_tags.set(filter_has_tags)
set_triggers.append(trigger_instance) set_triggers.append(trigger_instance)
if actions is not None: if actions is not None:
@ -1396,7 +1399,7 @@ class WorkflowSerializer(serializers.ModelSerializer):
assign_change_groups = action.pop("assign_change_groups", None) assign_change_groups = action.pop("assign_change_groups", None)
assign_custom_fields = action.pop("assign_custom_fields", None) assign_custom_fields = action.pop("assign_custom_fields", None)
action_instance, _ = WorkflowAction.objects.update_or_create( action_instance, _ = WorkflowAction.objects.update_or_create(
id=trigger["id"], id=action["id"],
defaults=action, defaults=action,
) )
if assign_tags is not None: if assign_tags is not None:
@ -1417,6 +1420,19 @@ class WorkflowSerializer(serializers.ModelSerializer):
instance.actions.set(set_actions) instance.actions.set(set_actions)
instance.save() instance.save()
def prune_triggers_and_actions(self):
"""
ManyToMany fields dont support e.g. on_delete so we need to discard unattached
triggers and actionas manually
"""
for trigger in WorkflowTrigger.objects.all():
if trigger.workflows.all().count() == 0:
trigger.delete()
for action in WorkflowAction.objects.all():
if action.workflows.all().count() == 0:
action.delete()
def create(self, validated_data: Any) -> Workflow: def create(self, validated_data: Any) -> Workflow:
if "triggers" in validated_data: if "triggers" in validated_data:
triggers = validated_data.pop("triggers") triggers = validated_data.pop("triggers")
@ -1441,4 +1457,6 @@ class WorkflowSerializer(serializers.ModelSerializer):
self.update_triggers_and_actions(instance, triggers, actions) self.update_triggers_and_actions(instance, triggers, actions)
self.prune_triggers_and_actions()
return instance return instance