Initial frontend implementation of workflows
This commit is contained in:
parent
e328d5aa95
commit
b4023d3aae
@ -21,7 +21,7 @@ import {
|
|||||||
PermissionAction,
|
PermissionAction,
|
||||||
PermissionType,
|
PermissionType,
|
||||||
} from './services/permissions.service'
|
} from './services/permissions.service'
|
||||||
import { ConsumptionTemplatesComponent } from './components/manage/consumption-templates/consumption-templates.component'
|
import { WorkflowsComponent } from './components/manage/workflows/workflows.component'
|
||||||
import { MailComponent } from './components/manage/mail/mail.component'
|
import { MailComponent } from './components/manage/mail/mail.component'
|
||||||
import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
|
import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
|
||||||
import { CustomFieldsComponent } from './components/manage/custom-fields/custom-fields.component'
|
import { CustomFieldsComponent } from './components/manage/custom-fields/custom-fields.component'
|
||||||
@ -202,13 +202,13 @@ export const routes: Routes = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'templates',
|
path: 'workflows',
|
||||||
component: ConsumptionTemplatesComponent,
|
component: WorkflowsComponent,
|
||||||
canActivate: [PermissionsGuard],
|
canActivate: [PermissionsGuard],
|
||||||
data: {
|
data: {
|
||||||
requiredPermission: {
|
requiredPermission: {
|
||||||
action: PermissionAction.View,
|
action: PermissionAction.View,
|
||||||
type: PermissionType.ConsumptionTemplate,
|
type: PermissionType.Workflow,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -176,9 +176,9 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
anchorId: 'tour.consumption-templates',
|
anchorId: 'tour.workflows',
|
||||||
content: $localize`Consumption templates give you finer control over the document ingestion process.`,
|
content: $localize`Workflows give you finer control over the document ingestion process.`,
|
||||||
route: '/templates',
|
route: '/workflows',
|
||||||
backdropConfig: {
|
backdropConfig: {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
},
|
},
|
||||||
|
@ -95,8 +95,8 @@ import { UsernamePipe } from './pipes/username.pipe'
|
|||||||
import { LogoComponent } from './components/common/logo/logo.component'
|
import { LogoComponent } from './components/common/logo/logo.component'
|
||||||
import { IsNumberPipe } from './pipes/is-number.pipe'
|
import { IsNumberPipe } from './pipes/is-number.pipe'
|
||||||
import { ShareLinksDropdownComponent } from './components/common/share-links-dropdown/share-links-dropdown.component'
|
import { ShareLinksDropdownComponent } from './components/common/share-links-dropdown/share-links-dropdown.component'
|
||||||
import { ConsumptionTemplatesComponent } from './components/manage/consumption-templates/consumption-templates.component'
|
import { WorkflowsComponent } from './components/manage/workflows/workflows.component'
|
||||||
import { ConsumptionTemplateEditDialogComponent } from './components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component'
|
import { WorkflowEditDialogComponent } from './components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component'
|
||||||
import { MailComponent } from './components/manage/mail/mail.component'
|
import { MailComponent } from './components/manage/mail/mail.component'
|
||||||
import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
|
import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
|
||||||
import { DragDropModule } from '@angular/cdk/drag-drop'
|
import { DragDropModule } from '@angular/cdk/drag-drop'
|
||||||
@ -251,8 +251,8 @@ function initializeApp(settings: SettingsService) {
|
|||||||
LogoComponent,
|
LogoComponent,
|
||||||
IsNumberPipe,
|
IsNumberPipe,
|
||||||
ShareLinksDropdownComponent,
|
ShareLinksDropdownComponent,
|
||||||
ConsumptionTemplatesComponent,
|
WorkflowsComponent,
|
||||||
ConsumptionTemplateEditDialogComponent,
|
WorkflowEditDialogComponent,
|
||||||
MailComponent,
|
MailComponent,
|
||||||
UsersAndGroupsComponent,
|
UsersAndGroupsComponent,
|
||||||
FileDropComponent,
|
FileDropComponent,
|
||||||
|
@ -235,14 +235,14 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item"
|
<li class="nav-item"
|
||||||
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.ConsumptionTemplate }"
|
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Workflow }"
|
||||||
tourAnchor="tour.consumption-templates">
|
tourAnchor="tour.workflows">
|
||||||
<a class="nav-link" routerLink="templates" routerLinkActive="active" (click)="closeMenu()"
|
<a class="nav-link" routerLink="workflows" routerLinkActive="active" (click)="closeMenu()"
|
||||||
ngbPopover="Consumption templates" 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#file-earmark-ruled" />
|
<use xlink:href="assets/bootstrap-icons.svg#boxes" />
|
||||||
</svg><span> <ng-container i18n>Templates</ng-container></span>
|
</svg><span> <ng-container i18n>Workflows</ng-container></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }"
|
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }"
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
|
|
||||||
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-8">
|
|
||||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<pngx-input-number i18n-title title="Sort order" formControlName="order" [showAdd]="false" [error]="error?.order"></pngx-input-number>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<h5 class="border-bottom pb-2" i18n>Filters</h5>
|
|
||||||
<p class="small" i18n>Process documents that match <em>all</em> filters specified below.</p>
|
|
||||||
<pngx-input-select i18n-title title="Filter sources" [items]="sourceOptions" [multiple]="true" formControlName="sources" [error]="error?.sources"></pngx-input-select>
|
|
||||||
<pngx-input-text i18n-title title="Filter filename" formControlName="filter_filename" i18n-hint hint="Apply to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." [error]="error?.filter_filename"></pngx-input-text>
|
|
||||||
<pngx-input-text i18n-title title="Filter path" formControlName="filter_path" i18n-hint hint="Apply to documents that match this path. Wildcards specified as * are allowed. Case insensitive.</a>" [error]="error?.filter_path"></pngx-input-text>
|
|
||||||
<pngx-input-select i18n-title title="Filter mail rule" [items]="mailRules" [allowNull]="true" formControlName="filter_mailrule" i18n-hint hint="Apply to documents consumed via this mail rule." [error]="error?.filter_mailrule"></pngx-input-select>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h5 class="border-bottom pb-2" i18n>Assignments</h5>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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/#consumption-templates'>documentation</a>." [error]="error?.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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
@if (error?.non_field_errors) {
|
|
||||||
<span class="text-danger"><ng-container i18n>Error</ng-container>: {{error.non_field_errors}}</span>
|
|
||||||
}
|
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
|
||||||
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
@ -1,125 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import { FormGroup, FormControl } from '@angular/forms'
|
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { first } from 'rxjs'
|
|
||||||
import {
|
|
||||||
DocumentSource,
|
|
||||||
ConsumptionTemplate,
|
|
||||||
} from 'src/app/data/consumption-template'
|
|
||||||
import { Correspondent } from 'src/app/data/correspondent'
|
|
||||||
import { DocumentType } from 'src/app/data/document-type'
|
|
||||||
import { StoragePath } from 'src/app/data/storage-path'
|
|
||||||
import { ConsumptionTemplateService } from 'src/app/services/rest/consumption-template.service'
|
|
||||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
|
||||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
|
||||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
|
||||||
import { UserService } from 'src/app/services/rest/user.service'
|
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
|
||||||
import { EditDialogComponent } from '../edit-dialog.component'
|
|
||||||
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
|
||||||
import { MailRule } from 'src/app/data/mail-rule'
|
|
||||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
|
||||||
import { CustomField } from 'src/app/data/custom-field'
|
|
||||||
|
|
||||||
export const DOCUMENT_SOURCE_OPTIONS = [
|
|
||||||
{
|
|
||||||
id: DocumentSource.ConsumeFolder,
|
|
||||||
name: $localize`Consume Folder`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: DocumentSource.ApiUpload,
|
|
||||||
name: $localize`API Upload`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: DocumentSource.MailFetch,
|
|
||||||
name: $localize`Mail Fetch`,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'pngx-consumption-template-edit-dialog',
|
|
||||||
templateUrl: './consumption-template-edit-dialog.component.html',
|
|
||||||
styleUrls: ['./consumption-template-edit-dialog.component.scss'],
|
|
||||||
})
|
|
||||||
export class ConsumptionTemplateEditDialogComponent extends EditDialogComponent<ConsumptionTemplate> {
|
|
||||||
templates: ConsumptionTemplate[]
|
|
||||||
correspondents: Correspondent[]
|
|
||||||
documentTypes: DocumentType[]
|
|
||||||
storagePaths: StoragePath[]
|
|
||||||
mailRules: MailRule[]
|
|
||||||
customFields: CustomField[]
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
service: ConsumptionTemplateService,
|
|
||||||
activeModal: NgbActiveModal,
|
|
||||||
correspondentService: CorrespondentService,
|
|
||||||
documentTypeService: DocumentTypeService,
|
|
||||||
storagePathService: StoragePathService,
|
|
||||||
mailRuleService: MailRuleService,
|
|
||||||
userService: UserService,
|
|
||||||
settingsService: SettingsService,
|
|
||||||
customFieldsService: CustomFieldsService
|
|
||||||
) {
|
|
||||||
super(service, activeModal, userService, settingsService)
|
|
||||||
|
|
||||||
correspondentService
|
|
||||||
.listAll()
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((result) => (this.correspondents = result.results))
|
|
||||||
|
|
||||||
documentTypeService
|
|
||||||
.listAll()
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((result) => (this.documentTypes = result.results))
|
|
||||||
|
|
||||||
storagePathService
|
|
||||||
.listAll()
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((result) => (this.storagePaths = result.results))
|
|
||||||
|
|
||||||
mailRuleService
|
|
||||||
.listAll()
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((result) => (this.mailRules = result.results))
|
|
||||||
|
|
||||||
customFieldsService
|
|
||||||
.listAll()
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((result) => (this.customFields = result.results))
|
|
||||||
}
|
|
||||||
|
|
||||||
getCreateTitle() {
|
|
||||||
return $localize`Create new consumption template`
|
|
||||||
}
|
|
||||||
|
|
||||||
getEditTitle() {
|
|
||||||
return $localize`Edit consumption template`
|
|
||||||
}
|
|
||||||
|
|
||||||
getForm(): FormGroup {
|
|
||||||
return new FormGroup({
|
|
||||||
name: new FormControl(null),
|
|
||||||
account: new FormControl(null),
|
|
||||||
filter_filename: new FormControl(null),
|
|
||||||
filter_path: new FormControl(null),
|
|
||||||
filter_mailrule: new FormControl(null),
|
|
||||||
order: new FormControl(null),
|
|
||||||
sources: new FormControl([]),
|
|
||||||
assign_title: new FormControl(null),
|
|
||||||
assign_tags: new FormControl([]),
|
|
||||||
assign_owner: new FormControl(null),
|
|
||||||
assign_document_type: new FormControl(null),
|
|
||||||
assign_correspondent: new FormControl(null),
|
|
||||||
assign_storage_path: new FormControl(null),
|
|
||||||
assign_view_users: new FormControl([]),
|
|
||||||
assign_view_groups: new FormControl([]),
|
|
||||||
assign_change_users: new FormControl([]),
|
|
||||||
assign_change_groups: new FormControl([]),
|
|
||||||
assign_custom_fields: new FormControl([]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
get sourceOptions() {
|
|
||||||
return DOCUMENT_SOURCE_OPTIONS
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,148 @@
|
|||||||
|
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
|
||||||
|
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<pngx-input-number i18n-title title="Sort order" formControlName="order" [showAdd]="false" [error]="error?.order"></pngx-input-number>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ngbAccordion>
|
||||||
|
<div ngbAccordionItem>
|
||||||
|
<h2 ngbAccordionHeader>
|
||||||
|
<button ngbAccordionButton i18n>Triggers</button>
|
||||||
|
</h2>
|
||||||
|
<div ngbAccordionCollapse>
|
||||||
|
<div ngbAccordionBody>
|
||||||
|
<ng-template>
|
||||||
|
<p class="small" i18n>Trigger Workflow On:</p>
|
||||||
|
<div ngbAccordion [closeOthers]="true">
|
||||||
|
@for (trigger of object.triggers; track trigger; let i = $index){
|
||||||
|
<div ngbAccordionItem [formGroup]="triggerFields.controls[i]">
|
||||||
|
<h2 ngbAccordionHeader>
|
||||||
|
<button ngbAccordionButton>{{getTypeOptionName(triggerFields.controls[i].value.type)}} ({{i + 1}})</button>
|
||||||
|
</h2>
|
||||||
|
<div ngbAccordionCollapse>
|
||||||
|
<div ngbAccordionBody>
|
||||||
|
|
||||||
|
<input type="hidden" formControlName="id" />
|
||||||
|
<pngx-input-select i18n-title title="Trigger type" [horizontal]="true" [items]="typeOptions" formControlName="type" [error]="error?.type"></pngx-input-select>
|
||||||
|
<p class="small" i18n>Trigger for documents that match <em>all</em> filters specified below.</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<pngx-input-select i18n-title title="Filter sources" [items]="sourceOptions" [multiple]="true" formControlName="sources" [error]="error?.sources"></pngx-input-select>
|
||||||
|
<pngx-input-text i18n-title title="Filter filename" formControlName="filter_filename" i18n-hint hint="Apply to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." [error]="error?.filter_filename"></pngx-input-text>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<pngx-input-text i18n-title title="Filter path" formControlName="filter_path" i18n-hint hint="Apply to documents that match this path. Wildcards specified as * are allowed. Case insensitive.</a>" [error]="error?.filter_path"></pngx-input-text>
|
||||||
|
<pngx-input-select i18n-title title="Filter mail rule" [items]="mailRules" [allowNull]="true" formControlName="filter_mailrule" i18n-hint hint="Apply to documents consumed via this mail rule." [error]="error?.filter_mailrule"></pngx-input-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ngbAccordionItem>
|
||||||
|
<h2 ngbAccordionHeader>
|
||||||
|
<button class="btn-lg" ngbAccordionButton i18n>Actions</button>
|
||||||
|
</h2>
|
||||||
|
<div ngbAccordionCollapse>
|
||||||
|
<div ngbAccordionBody>
|
||||||
|
<ng-template>
|
||||||
|
<p class="small" i18n>Apply Actions:</p>
|
||||||
|
<div ngbAccordion [closeOthers]="true">
|
||||||
|
@for (action of object.actions; track action; let i = $index){
|
||||||
|
<div ngbAccordionItem [formGroup]="actionFields.controls[i]">
|
||||||
|
<h2 ngbAccordionHeader>
|
||||||
|
<button ngbAccordionButton>{{i + 1}}</button>
|
||||||
|
</h2>
|
||||||
|
<div ngbAccordionCollapse>
|
||||||
|
<div ngbAccordionBody>
|
||||||
|
|
||||||
|
<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/#consumption-templates'>documentation</a>." [error]="error?.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>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
@if (error?.non_field_errors) {
|
||||||
|
<span class="text-danger"><ng-container i18n>Error</ng-container>: {{error.non_field_errors}}</span>
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
@ -19,18 +19,18 @@ import { SelectComponent } from '../../input/select/select.component'
|
|||||||
import { TagsComponent } from '../../input/tags/tags.component'
|
import { TagsComponent } from '../../input/tags/tags.component'
|
||||||
import { TextComponent } from '../../input/text/text.component'
|
import { TextComponent } from '../../input/text/text.component'
|
||||||
import { EditDialogMode } from '../edit-dialog.component'
|
import { EditDialogMode } from '../edit-dialog.component'
|
||||||
import { ConsumptionTemplateEditDialogComponent } from './consumption-template-edit-dialog.component'
|
import { WorkflowEditDialogComponent } from './workflow-edit-dialog.component'
|
||||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
|
|
||||||
describe('ConsumptionTemplateEditDialogComponent', () => {
|
describe('ConsumptionTemplateEditDialogComponent', () => {
|
||||||
let component: ConsumptionTemplateEditDialogComponent
|
let component: WorkflowEditDialogComponent
|
||||||
let settingsService: SettingsService
|
let settingsService: SettingsService
|
||||||
let fixture: ComponentFixture<ConsumptionTemplateEditDialogComponent>
|
let fixture: ComponentFixture<WorkflowEditDialogComponent>
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
ConsumptionTemplateEditDialogComponent,
|
WorkflowEditDialogComponent,
|
||||||
IfPermissionsDirective,
|
IfPermissionsDirective,
|
||||||
IfOwnerDirective,
|
IfOwnerDirective,
|
||||||
SelectComponent,
|
SelectComponent,
|
||||||
@ -113,7 +113,7 @@ describe('ConsumptionTemplateEditDialogComponent', () => {
|
|||||||
],
|
],
|
||||||
}).compileComponents()
|
}).compileComponents()
|
||||||
|
|
||||||
fixture = TestBed.createComponent(ConsumptionTemplateEditDialogComponent)
|
fixture = TestBed.createComponent(WorkflowEditDialogComponent)
|
||||||
settingsService = TestBed.inject(SettingsService)
|
settingsService = TestBed.inject(SettingsService)
|
||||||
settingsService.currentUser = { id: 99, username: 'user99' }
|
settingsService.currentUser = { id: 99, username: 'user99' }
|
||||||
component = fixture.componentInstance
|
component = fixture.componentInstance
|
@ -0,0 +1,191 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core'
|
||||||
|
import { FormGroup, FormControl, FormArray } from '@angular/forms'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { first } from 'rxjs'
|
||||||
|
import { Workflow } from 'src/app/data/workflow'
|
||||||
|
import { Correspondent } from 'src/app/data/correspondent'
|
||||||
|
import { DocumentType } from 'src/app/data/document-type'
|
||||||
|
import { StoragePath } from 'src/app/data/storage-path'
|
||||||
|
import { WorkflowService } from 'src/app/services/rest/workflow.service'
|
||||||
|
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
||||||
|
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||||
|
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
|
import { EditDialogComponent } from '../edit-dialog.component'
|
||||||
|
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
||||||
|
import { MailRule } from 'src/app/data/mail-rule'
|
||||||
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
|
import { CustomField } from 'src/app/data/custom-field'
|
||||||
|
import {
|
||||||
|
DocumentSource,
|
||||||
|
WorkflowTriggerType,
|
||||||
|
} from 'src/app/data/workflow-trigger'
|
||||||
|
|
||||||
|
export const DOCUMENT_SOURCE_OPTIONS = [
|
||||||
|
{
|
||||||
|
id: DocumentSource.ConsumeFolder,
|
||||||
|
name: $localize`Consume Folder`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: DocumentSource.ApiUpload,
|
||||||
|
name: $localize`API Upload`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: DocumentSource.MailFetch,
|
||||||
|
name: $localize`Mail Fetch`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const WORKFLOW_TYPE_OPTIONS = [
|
||||||
|
{
|
||||||
|
id: WorkflowTriggerType.Consumption,
|
||||||
|
name: $localize`Consumption`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: WorkflowTriggerType.DocumentAdded,
|
||||||
|
name: $localize`Document Added`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: WorkflowTriggerType.DocumentUpdated,
|
||||||
|
name: $localize`Document Updated`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'pngx-workflow-edit-dialog',
|
||||||
|
templateUrl: './workflow-edit-dialog.component.html',
|
||||||
|
styleUrls: ['./workflow-edit-dialog.component.scss'],
|
||||||
|
})
|
||||||
|
export class WorkflowEditDialogComponent
|
||||||
|
extends EditDialogComponent<Workflow>
|
||||||
|
implements OnInit
|
||||||
|
{
|
||||||
|
templates: Workflow[]
|
||||||
|
correspondents: Correspondent[]
|
||||||
|
documentTypes: DocumentType[]
|
||||||
|
storagePaths: StoragePath[]
|
||||||
|
mailRules: MailRule[]
|
||||||
|
customFields: CustomField[]
|
||||||
|
|
||||||
|
expandedItem: number = null
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
service: WorkflowService,
|
||||||
|
activeModal: NgbActiveModal,
|
||||||
|
correspondentService: CorrespondentService,
|
||||||
|
documentTypeService: DocumentTypeService,
|
||||||
|
storagePathService: StoragePathService,
|
||||||
|
mailRuleService: MailRuleService,
|
||||||
|
userService: UserService,
|
||||||
|
settingsService: SettingsService,
|
||||||
|
customFieldsService: CustomFieldsService
|
||||||
|
) {
|
||||||
|
super(service, activeModal, userService, settingsService)
|
||||||
|
|
||||||
|
correspondentService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((result) => (this.correspondents = result.results))
|
||||||
|
|
||||||
|
documentTypeService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((result) => (this.documentTypes = result.results))
|
||||||
|
|
||||||
|
storagePathService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((result) => (this.storagePaths = result.results))
|
||||||
|
|
||||||
|
mailRuleService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((result) => (this.mailRules = result.results))
|
||||||
|
|
||||||
|
customFieldsService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((result) => (this.customFields = result.results))
|
||||||
|
}
|
||||||
|
|
||||||
|
getCreateTitle() {
|
||||||
|
return $localize`Create new workflow`
|
||||||
|
}
|
||||||
|
|
||||||
|
getEditTitle() {
|
||||||
|
return $localize`Edit workflow`
|
||||||
|
}
|
||||||
|
|
||||||
|
getForm(): FormGroup {
|
||||||
|
return new FormGroup({
|
||||||
|
name: new FormControl(null),
|
||||||
|
order: new FormControl(null),
|
||||||
|
triggers: new FormArray([]),
|
||||||
|
actions: new FormArray([]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit()
|
||||||
|
this.updateTriggerActionFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
get triggerFields(): FormArray {
|
||||||
|
return this.objectForm.get('triggers') as FormArray
|
||||||
|
}
|
||||||
|
|
||||||
|
get actionFields(): FormArray {
|
||||||
|
return this.objectForm.get('actions') as FormArray
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateTriggerActionFields(emitEvent: boolean = false) {
|
||||||
|
this.triggerFields.clear({ emitEvent: false })
|
||||||
|
this.object?.triggers.forEach((trigger) => {
|
||||||
|
this.triggerFields.push(
|
||||||
|
new FormGroup({
|
||||||
|
id: new FormControl(trigger.id),
|
||||||
|
type: new FormControl(trigger.type),
|
||||||
|
sources: new FormControl(trigger.sources),
|
||||||
|
filter_filename: new FormControl(trigger.filter_filename),
|
||||||
|
filter_path: new FormControl(trigger.filter_path),
|
||||||
|
filter_mailrule: new FormControl(trigger.filter_mailrule),
|
||||||
|
}),
|
||||||
|
{ emitEvent }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.actionFields.clear({ emitEvent: false })
|
||||||
|
this.object?.actions.forEach((action) => {
|
||||||
|
this.actionFields.push(
|
||||||
|
new FormGroup({
|
||||||
|
id: new FormControl(action.id),
|
||||||
|
assign_title: new FormControl(action.assign_title),
|
||||||
|
assign_tags: new FormControl(action.assign_tags),
|
||||||
|
assign_owner: new FormControl(action.assign_owner),
|
||||||
|
assign_document_type: new FormControl(action.assign_document_type),
|
||||||
|
assign_correspondent: new FormControl(action.assign_correspondent),
|
||||||
|
assign_storage_path: new FormControl(action.assign_storage_path),
|
||||||
|
assign_view_users: new FormControl(action.assign_view_users),
|
||||||
|
assign_view_groups: new FormControl(action.assign_view_groups),
|
||||||
|
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),
|
||||||
|
}),
|
||||||
|
{ emitEvent }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get sourceOptions() {
|
||||||
|
return DOCUMENT_SOURCE_OPTIONS
|
||||||
|
}
|
||||||
|
|
||||||
|
get typeOptions() {
|
||||||
|
return WORKFLOW_TYPE_OPTIONS
|
||||||
|
}
|
||||||
|
|
||||||
|
getTypeOptionName(type: WorkflowTriggerType): string {
|
||||||
|
return this.typeOptions.find((t) => t.id === type).name ?? ''
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
.accordion {
|
||||||
|
--bs-accordion-btn-padding-x: 0.75rem;
|
||||||
|
--bs-accordion-btn-padding-y: 0.375rem;
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
<pngx-page-header title="Consumption Templates" i18n-title>
|
<pngx-page-header title="Workflows" i18n-title>
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editTemplate()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ConsumptionTemplate }">
|
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editWorkflow()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Workflow }">
|
||||||
<svg class="sidebaricon me-1" fill="currentColor">
|
<svg class="sidebaricon me-1" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||||
</svg>
|
</svg>
|
||||||
<ng-container i18n>Add Template</ng-container>
|
<ng-container i18n>Add Workflow</ng-container>
|
||||||
</button>
|
</button>
|
||||||
</pngx-page-header>
|
</pngx-page-header>
|
||||||
|
|
||||||
@ -13,25 +13,25 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col" i18n>Name</div>
|
<div class="col" i18n>Name</div>
|
||||||
<div class="col" i18n>Sort order</div>
|
<div class="col" i18n>Sort order</div>
|
||||||
<div class="col" i18n>Document Sources</div>
|
<div class="col" i18n>Triggers</div>
|
||||||
<div class="col" i18n>Actions</div>
|
<div class="col" i18n>Actions</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@for (template of templates; track template) {
|
@for (workflow of workflows; track workflow.id) {
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editTemplate(template)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.ConsumptionTemplate)">{{template.name}}</button></div>
|
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editWorkflow(workflow)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.Workflow)">{{workflow.name}}</button></div>
|
||||||
<div class="col d-flex align-items-center"><code>{{template.order}}</code></div>
|
<div class="col d-flex align-items-center"><code>{{workflow.order}}</code></div>
|
||||||
<div class="col d-flex align-items-center">{{getSourceList(template)}}</div>
|
<div class="col d-flex align-items-center">{{getTypesList(workflow)}}</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.ConsumptionTemplate }" class="btn btn-sm btn-outline-secondary" type="button" (click)="editTemplate(template)">
|
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Workflow }" class="btn btn-sm btn-outline-secondary" type="button" (click)="editWorkflow(workflow)">
|
||||||
<svg class="buttonicon-sm" fill="currentColor">
|
<svg class="buttonicon-sm" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
|
||||||
</svg> <ng-container i18n>Edit</ng-container>
|
</svg> <ng-container i18n>Edit</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.ConsumptionTemplate }" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteTemplate(template)">
|
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Workflow }" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteWorkflow(workflow)">
|
||||||
<svg class="buttonicon-sm" fill="currentColor">
|
<svg class="buttonicon-sm" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||||
</svg> <ng-container i18n>Delete</ng-container>
|
</svg> <ng-container i18n>Delete</ng-container>
|
||||||
@ -41,7 +41,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
@if (templates.length === 0) {
|
@if (workflows.length === 0) {
|
||||||
<li class="list-group-item" i18n>No templates defined.</li>
|
<li class="list-group-item" i18n>No workflows defined.</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
@ -9,55 +9,71 @@ import {
|
|||||||
NgbModalModule,
|
NgbModalModule,
|
||||||
} from '@ng-bootstrap/ng-bootstrap'
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { of, throwError } from 'rxjs'
|
import { of, throwError } from 'rxjs'
|
||||||
import {
|
import { Workflow } from 'src/app/data/workflow'
|
||||||
DocumentSource,
|
|
||||||
ConsumptionTemplate,
|
|
||||||
} from 'src/app/data/consumption-template'
|
|
||||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||||
import { ConsumptionTemplateService } from 'src/app/services/rest/consumption-template.service'
|
import { WorkflowService } from 'src/app/services/rest/workflow.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||||
import { ConsumptionTemplatesComponent } from './consumption-templates.component'
|
import { WorkflowsComponent } from './workflows.component'
|
||||||
import { ConsumptionTemplateEditDialogComponent } from '../../common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component'
|
import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component'
|
||||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||||
|
import {
|
||||||
|
DocumentSource,
|
||||||
|
WorkflowTriggerType,
|
||||||
|
} from 'src/app/data/workflow-trigger'
|
||||||
|
|
||||||
const templates: ConsumptionTemplate[] = [
|
const workflows: Workflow[] = [
|
||||||
{
|
{
|
||||||
id: 0,
|
name: 'Workflow 1',
|
||||||
name: 'Template 1',
|
id: 1,
|
||||||
order: 0,
|
order: 1,
|
||||||
sources: [
|
triggers: [
|
||||||
DocumentSource.ConsumeFolder,
|
{
|
||||||
DocumentSource.ApiUpload,
|
id: 1,
|
||||||
DocumentSource.MailFetch,
|
type: WorkflowTriggerType.Consumption,
|
||||||
|
sources: [DocumentSource.ConsumeFolder],
|
||||||
|
filter_filename: '*',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
assign_title: 'foo',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
filter_filename: 'foo',
|
|
||||||
filter_path: 'bar',
|
|
||||||
assign_tags: [1, 2, 3],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
name: 'Workflow 2',
|
||||||
name: 'Template 2',
|
id: 2,
|
||||||
order: 1,
|
order: 2,
|
||||||
sources: [DocumentSource.MailFetch],
|
triggers: [
|
||||||
filter_filename: null,
|
{
|
||||||
filter_path: 'foo/bar',
|
id: 2,
|
||||||
assign_owner: 1,
|
type: WorkflowTriggerType.DocumentAdded,
|
||||||
|
filter_filename: 'foo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
assign_title: 'bar',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
describe('ConsumptionTemplatesComponent', () => {
|
describe('WorkflowsComponent', () => {
|
||||||
let component: ConsumptionTemplatesComponent
|
let component: WorkflowsComponent
|
||||||
let fixture: ComponentFixture<ConsumptionTemplatesComponent>
|
let fixture: ComponentFixture<WorkflowsComponent>
|
||||||
let consumptionTemplateService: ConsumptionTemplateService
|
let workflowService: WorkflowService
|
||||||
let modalService: NgbModal
|
let modalService: NgbModal
|
||||||
let toastService: ToastService
|
let toastService: ToastService
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
ConsumptionTemplatesComponent,
|
WorkflowsComponent,
|
||||||
IfPermissionsDirective,
|
IfPermissionsDirective,
|
||||||
PageHeaderComponent,
|
PageHeaderComponent,
|
||||||
ConfirmDialogComponent,
|
ConfirmDialogComponent,
|
||||||
@ -81,18 +97,18 @@ describe('ConsumptionTemplatesComponent', () => {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
consumptionTemplateService = TestBed.inject(ConsumptionTemplateService)
|
workflowService = TestBed.inject(WorkflowService)
|
||||||
jest.spyOn(consumptionTemplateService, 'listAll').mockReturnValue(
|
jest.spyOn(workflowService, 'listAll').mockReturnValue(
|
||||||
of({
|
of({
|
||||||
count: templates.length,
|
count: workflows.length,
|
||||||
all: templates.map((o) => o.id),
|
all: workflows.map((o) => o.id),
|
||||||
results: templates,
|
results: workflows,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
modalService = TestBed.inject(NgbModal)
|
modalService = TestBed.inject(NgbModal)
|
||||||
toastService = TestBed.inject(ToastService)
|
toastService = TestBed.inject(ToastService)
|
||||||
|
|
||||||
fixture = TestBed.createComponent(ConsumptionTemplatesComponent)
|
fixture = TestBed.createComponent(WorkflowsComponent)
|
||||||
component = fixture.componentInstance
|
component = fixture.componentInstance
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
})
|
})
|
||||||
@ -108,8 +124,7 @@ describe('ConsumptionTemplatesComponent', () => {
|
|||||||
createButton.triggerEventHandler('click')
|
createButton.triggerEventHandler('click')
|
||||||
|
|
||||||
expect(modal).not.toBeUndefined()
|
expect(modal).not.toBeUndefined()
|
||||||
const editDialog =
|
const editDialog = modal.componentInstance as WorkflowEditDialogComponent
|
||||||
modal.componentInstance as ConsumptionTemplateEditDialogComponent
|
|
||||||
|
|
||||||
// fail first
|
// fail first
|
||||||
editDialog.failed.emit({ error: 'error creating item' })
|
editDialog.failed.emit({ error: 'error creating item' })
|
||||||
@ -117,7 +132,7 @@ describe('ConsumptionTemplatesComponent', () => {
|
|||||||
expect(reloadSpy).not.toHaveBeenCalled()
|
expect(reloadSpy).not.toHaveBeenCalled()
|
||||||
|
|
||||||
// succeed
|
// succeed
|
||||||
editDialog.succeeded.emit(templates[0])
|
editDialog.succeeded.emit(workflows[0])
|
||||||
expect(toastInfoSpy).toHaveBeenCalled()
|
expect(toastInfoSpy).toHaveBeenCalled()
|
||||||
expect(reloadSpy).toHaveBeenCalled()
|
expect(reloadSpy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@ -133,9 +148,8 @@ describe('ConsumptionTemplatesComponent', () => {
|
|||||||
editButton.triggerEventHandler('click')
|
editButton.triggerEventHandler('click')
|
||||||
|
|
||||||
expect(modal).not.toBeUndefined()
|
expect(modal).not.toBeUndefined()
|
||||||
const editDialog =
|
const editDialog = modal.componentInstance as WorkflowEditDialogComponent
|
||||||
modal.componentInstance as ConsumptionTemplateEditDialogComponent
|
expect(editDialog.object).toEqual(workflows[0])
|
||||||
expect(editDialog.object).toEqual(templates[0])
|
|
||||||
|
|
||||||
// fail first
|
// fail first
|
||||||
editDialog.failed.emit({ error: 'error editing item' })
|
editDialog.failed.emit({ error: 'error editing item' })
|
||||||
@ -143,7 +157,7 @@ describe('ConsumptionTemplatesComponent', () => {
|
|||||||
expect(reloadSpy).not.toHaveBeenCalled()
|
expect(reloadSpy).not.toHaveBeenCalled()
|
||||||
|
|
||||||
// succeed
|
// succeed
|
||||||
editDialog.succeeded.emit(templates[0])
|
editDialog.succeeded.emit(workflows[0])
|
||||||
expect(toastInfoSpy).toHaveBeenCalled()
|
expect(toastInfoSpy).toHaveBeenCalled()
|
||||||
expect(reloadSpy).toHaveBeenCalled()
|
expect(reloadSpy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@ -152,7 +166,7 @@ describe('ConsumptionTemplatesComponent', () => {
|
|||||||
let modal: NgbModalRef
|
let modal: NgbModalRef
|
||||||
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
|
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
const deleteSpy = jest.spyOn(consumptionTemplateService, 'delete')
|
const deleteSpy = jest.spyOn(workflowService, 'delete')
|
||||||
const reloadSpy = jest.spyOn(component, 'reload')
|
const reloadSpy = jest.spyOn(component, 'reload')
|
||||||
|
|
||||||
const deleteButton = fixture.debugElement.queryAll(By.css('button'))[3]
|
const deleteButton = fixture.debugElement.queryAll(By.css('button'))[3]
|
@ -1,33 +1,34 @@
|
|||||||
import { Component, OnInit } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
import { ConsumptionTemplateService } from 'src/app/services/rest/consumption-template.service'
|
import { WorkflowService } from 'src/app/services/rest/workflow.service'
|
||||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
import { Subject, takeUntil } from 'rxjs'
|
import { Subject, takeUntil } from 'rxjs'
|
||||||
import { ConsumptionTemplate } from 'src/app/data/consumption-template'
|
import { Workflow } from 'src/app/data/workflow'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||||
import {
|
import {
|
||||||
ConsumptionTemplateEditDialogComponent,
|
WorkflowEditDialogComponent,
|
||||||
DOCUMENT_SOURCE_OPTIONS,
|
DOCUMENT_SOURCE_OPTIONS,
|
||||||
} from '../../common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component'
|
WORKFLOW_TYPE_OPTIONS,
|
||||||
|
} from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component'
|
||||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||||
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
|
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pngx-consumption-templates',
|
selector: 'pngx-workflows',
|
||||||
templateUrl: './consumption-templates.component.html',
|
templateUrl: './workflows.component.html',
|
||||||
styleUrls: ['./consumption-templates.component.scss'],
|
styleUrls: ['./workflows.component.scss'],
|
||||||
})
|
})
|
||||||
export class ConsumptionTemplatesComponent
|
export class WorkflowsComponent
|
||||||
extends ComponentWithPermissions
|
extends ComponentWithPermissions
|
||||||
implements OnInit
|
implements OnInit
|
||||||
{
|
{
|
||||||
public templates: ConsumptionTemplate[] = []
|
public workflows: Workflow[] = []
|
||||||
|
|
||||||
private unsubscribeNotifier: Subject<any> = new Subject()
|
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private consumptionTemplateService: ConsumptionTemplateService,
|
private workflowService: WorkflowService,
|
||||||
public permissionsService: PermissionsService,
|
public permissionsService: PermissionsService,
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private toastService: ToastService
|
private toastService: ToastService
|
||||||
@ -40,68 +41,68 @@ export class ConsumptionTemplatesComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
reload() {
|
reload() {
|
||||||
this.consumptionTemplateService
|
this.workflowService
|
||||||
.listAll()
|
.listAll()
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe((r) => {
|
.subscribe((r) => {
|
||||||
this.templates = r.results
|
this.workflows = r.results
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getSourceList(template: ConsumptionTemplate): string {
|
getTypesList(template: Workflow): string {
|
||||||
return template.sources
|
return template.triggers
|
||||||
.map((id) => DOCUMENT_SOURCE_OPTIONS.find((s) => s.id === id).name)
|
.map(
|
||||||
|
(trigger) =>
|
||||||
|
WORKFLOW_TYPE_OPTIONS.find((t) => t.id === trigger.type).name
|
||||||
|
)
|
||||||
.join(', ')
|
.join(', ')
|
||||||
}
|
}
|
||||||
|
|
||||||
editTemplate(rule: ConsumptionTemplate) {
|
editWorkflow(workflow: Workflow) {
|
||||||
const modal = this.modalService.open(
|
const modal = this.modalService.open(WorkflowEditDialogComponent, {
|
||||||
ConsumptionTemplateEditDialogComponent,
|
backdrop: 'static',
|
||||||
{
|
size: 'xl',
|
||||||
backdrop: 'static',
|
})
|
||||||
size: 'xl',
|
modal.componentInstance.dialogMode = workflow
|
||||||
}
|
|
||||||
)
|
|
||||||
modal.componentInstance.dialogMode = rule
|
|
||||||
? EditDialogMode.EDIT
|
? EditDialogMode.EDIT
|
||||||
: EditDialogMode.CREATE
|
: EditDialogMode.CREATE
|
||||||
modal.componentInstance.object = rule
|
modal.componentInstance.object = workflow
|
||||||
modal.componentInstance.succeeded
|
modal.componentInstance.succeeded
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe((newTemplate) => {
|
.subscribe((newWorkflow) => {
|
||||||
this.toastService.showInfo(
|
this.toastService.showInfo(
|
||||||
$localize`Saved template "${newTemplate.name}".`
|
$localize`Saved workflow "${newWorkflow.name}".`
|
||||||
)
|
)
|
||||||
this.consumptionTemplateService.clearCache()
|
this.workflowService.clearCache()
|
||||||
this.reload()
|
this.reload()
|
||||||
})
|
})
|
||||||
modal.componentInstance.failed
|
modal.componentInstance.failed
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe((e) => {
|
.subscribe((e) => {
|
||||||
this.toastService.showError($localize`Error saving template.`, e)
|
this.toastService.showError($localize`Error saving workflow.`, e)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTemplate(rule: ConsumptionTemplate) {
|
deleteWorkflow(workflow: Workflow) {
|
||||||
const modal = this.modalService.open(ConfirmDialogComponent, {
|
const modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
})
|
})
|
||||||
modal.componentInstance.title = $localize`Confirm delete template`
|
modal.componentInstance.title = $localize`Confirm delete workflow`
|
||||||
modal.componentInstance.messageBold = $localize`This operation will permanently delete this template.`
|
modal.componentInstance.messageBold = $localize`This operation will permanently delete this workflow.`
|
||||||
modal.componentInstance.message = $localize`This operation cannot be undone.`
|
modal.componentInstance.message = $localize`This operation cannot be undone.`
|
||||||
modal.componentInstance.btnClass = 'btn-danger'
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
modal.componentInstance.btnCaption = $localize`Proceed`
|
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled = false
|
||||||
this.consumptionTemplateService.delete(rule).subscribe({
|
this.workflowService.delete(workflow).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
modal.close()
|
modal.close()
|
||||||
this.toastService.showInfo($localize`Deleted template`)
|
this.toastService.showInfo($localize`Deleted workflow`)
|
||||||
this.consumptionTemplateService.clearCache()
|
this.workflowService.clearCache()
|
||||||
this.reload()
|
this.reload()
|
||||||
},
|
},
|
||||||
error: (e) => {
|
error: (e) => {
|
||||||
this.toastService.showError($localize`Error deleting template.`, e)
|
this.toastService.showError($localize`Error deleting workflow.`, e)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -1,24 +1,6 @@
|
|||||||
import { ObjectWithId } from './object-with-id'
|
import { ObjectWithId } from './object-with-id'
|
||||||
|
|
||||||
export enum DocumentSource {
|
export interface WorkflowAction extends ObjectWithId {
|
||||||
ConsumeFolder = 1,
|
|
||||||
ApiUpload = 2,
|
|
||||||
MailFetch = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConsumptionTemplate extends ObjectWithId {
|
|
||||||
name: string
|
|
||||||
|
|
||||||
order: number
|
|
||||||
|
|
||||||
sources: DocumentSource[]
|
|
||||||
|
|
||||||
filter_filename: string
|
|
||||||
|
|
||||||
filter_path?: string
|
|
||||||
|
|
||||||
filter_mailrule?: number // MailRule.id
|
|
||||||
|
|
||||||
assign_title?: string
|
assign_title?: string
|
||||||
|
|
||||||
assign_tags?: number[] // Tag.id
|
assign_tags?: number[] // Tag.id
|
25
src-ui/src/app/data/workflow-trigger.ts
Normal file
25
src-ui/src/app/data/workflow-trigger.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ObjectWithId } from './object-with-id'
|
||||||
|
|
||||||
|
export enum DocumentSource {
|
||||||
|
ConsumeFolder = 1,
|
||||||
|
ApiUpload = 2,
|
||||||
|
MailFetch = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WorkflowTriggerType {
|
||||||
|
Consumption = 1,
|
||||||
|
DocumentAdded = 2,
|
||||||
|
DocumentUpdated = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkflowTrigger extends ObjectWithId {
|
||||||
|
type: WorkflowTriggerType
|
||||||
|
|
||||||
|
sources?: DocumentSource[]
|
||||||
|
|
||||||
|
filter_filename?: string
|
||||||
|
|
||||||
|
filter_path?: string
|
||||||
|
|
||||||
|
filter_mailrule?: number // MailRule.id
|
||||||
|
}
|
13
src-ui/src/app/data/workflow.ts
Normal file
13
src-ui/src/app/data/workflow.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { ObjectWithId } from './object-with-id'
|
||||||
|
import { WorkflowAction } from './workflow-action'
|
||||||
|
import { WorkflowTrigger } from './workflow-trigger'
|
||||||
|
|
||||||
|
export interface Workflow extends ObjectWithId {
|
||||||
|
name: string
|
||||||
|
|
||||||
|
order: number
|
||||||
|
|
||||||
|
triggers: WorkflowTrigger[]
|
||||||
|
|
||||||
|
actions: WorkflowAction[]
|
||||||
|
}
|
@ -252,10 +252,18 @@ describe('PermissionsService', () => {
|
|||||||
'view_sharelink',
|
'view_sharelink',
|
||||||
'change_sharelink',
|
'change_sharelink',
|
||||||
'delete_sharelink',
|
'delete_sharelink',
|
||||||
'add_consumptiontemplate',
|
'add_workflow',
|
||||||
'view_consumptiontemplate',
|
'view_workflow',
|
||||||
'change_consumptiontemplate',
|
'change_workflow',
|
||||||
'delete_consumptiontemplate',
|
'delete_workflow',
|
||||||
|
'add_workflowtrigger',
|
||||||
|
'view_workflowtrigger',
|
||||||
|
'change_workflowtrigger',
|
||||||
|
'delete_workflowtrigger',
|
||||||
|
'add_workflowaction',
|
||||||
|
'view_workflowaction',
|
||||||
|
'change_workflowaction',
|
||||||
|
'delete_workflowaction',
|
||||||
'add_customfield',
|
'add_customfield',
|
||||||
'view_customfield',
|
'view_customfield',
|
||||||
'change_customfield',
|
'change_customfield',
|
||||||
|
@ -25,8 +25,10 @@ export enum PermissionType {
|
|||||||
Group = '%s_group',
|
Group = '%s_group',
|
||||||
Admin = '%s_logentry',
|
Admin = '%s_logentry',
|
||||||
ShareLink = '%s_sharelink',
|
ShareLink = '%s_sharelink',
|
||||||
ConsumptionTemplate = '%s_consumptiontemplate',
|
|
||||||
CustomField = '%s_customfield',
|
CustomField = '%s_customfield',
|
||||||
|
Workflow = '%s_workflow',
|
||||||
|
WorkflowTrigger = '%s_workflowtrigger',
|
||||||
|
WorkflowAction = '%s_workflowaction',
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
48
src-ui/src/app/services/rest/workflow-action.service.spec.ts
Normal file
48
src-ui/src/app/services/rest/workflow-action.service.spec.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { HttpTestingController } from '@angular/common/http/testing'
|
||||||
|
import { TestBed } from '@angular/core/testing'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
import { environment } from 'src/environments/environment'
|
||||||
|
import { commonAbstractPaperlessServiceTests } from './abstract-paperless-service.spec'
|
||||||
|
import { WorkflowActionService } from './workflow-action.service'
|
||||||
|
import { WorkflowAction } from 'src/app/data/workflow-action'
|
||||||
|
|
||||||
|
let httpTestingController: HttpTestingController
|
||||||
|
let service: WorkflowActionService
|
||||||
|
const endpoint = 'workflow_actions'
|
||||||
|
const actions: WorkflowAction[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
assign_correspondent: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
assign_document_type: 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// run common tests
|
||||||
|
commonAbstractPaperlessServiceTests(endpoint, WorkflowActionService)
|
||||||
|
|
||||||
|
describe(`Additional service tests for WorkflowActionService`, () => {
|
||||||
|
it('should reload', () => {
|
||||||
|
service.reload()
|
||||||
|
const req = httpTestingController.expectOne(
|
||||||
|
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=100000`
|
||||||
|
)
|
||||||
|
req.flush({
|
||||||
|
results: actions,
|
||||||
|
})
|
||||||
|
expect(service.allActions).toEqual(actions)
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Dont need to setup again
|
||||||
|
|
||||||
|
httpTestingController = TestBed.inject(HttpTestingController)
|
||||||
|
service = TestBed.inject(WorkflowActionService)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
httpTestingController.verify()
|
||||||
|
})
|
||||||
|
})
|
@ -1,42 +1,43 @@
|
|||||||
import { HttpClient } from '@angular/common/http'
|
import { HttpClient } from '@angular/common/http'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { tap } from 'rxjs'
|
import { tap } from 'rxjs'
|
||||||
import { ConsumptionTemplate } from 'src/app/data/consumption-template'
|
import { Workflow } from 'src/app/data/workflow'
|
||||||
import { AbstractPaperlessService } from './abstract-paperless-service'
|
import { AbstractPaperlessService } from './abstract-paperless-service'
|
||||||
|
import { WorkflowAction } from 'src/app/data/workflow-action'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ConsumptionTemplateService extends AbstractPaperlessService<ConsumptionTemplate> {
|
export class WorkflowActionService extends AbstractPaperlessService<WorkflowAction> {
|
||||||
loading: boolean
|
loading: boolean
|
||||||
|
|
||||||
constructor(http: HttpClient) {
|
constructor(http: HttpClient) {
|
||||||
super(http, 'consumption_templates')
|
super(http, 'workflow_actions')
|
||||||
}
|
}
|
||||||
|
|
||||||
public reload() {
|
public reload() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.listAll().subscribe((r) => {
|
this.listAll().subscribe((r) => {
|
||||||
this.templates = r.results
|
this.actions = r.results
|
||||||
this.loading = false
|
this.loading = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private templates: ConsumptionTemplate[] = []
|
private actions: WorkflowAction[] = []
|
||||||
|
|
||||||
public get allTemplates(): ConsumptionTemplate[] {
|
public get allActions(): WorkflowAction[] {
|
||||||
return this.templates
|
return this.actions
|
||||||
}
|
}
|
||||||
|
|
||||||
create(o: ConsumptionTemplate) {
|
create(o: WorkflowAction) {
|
||||||
return super.create(o).pipe(tap(() => this.reload()))
|
return super.create(o).pipe(tap(() => this.reload()))
|
||||||
}
|
}
|
||||||
|
|
||||||
update(o: ConsumptionTemplate) {
|
update(o: WorkflowAction) {
|
||||||
return super.update(o).pipe(tap(() => this.reload()))
|
return super.update(o).pipe(tap(() => this.reload()))
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(o: ConsumptionTemplate) {
|
delete(o: WorkflowAction) {
|
||||||
return super.delete(o).pipe(tap(() => this.reload()))
|
return super.delete(o).pipe(tap(() => this.reload()))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,61 +1,54 @@
|
|||||||
import { HttpTestingController } from '@angular/common/http/testing'
|
import { HttpTestingController } from '@angular/common/http/testing'
|
||||||
import { TestBed } from '@angular/core/testing'
|
import { TestBed } from '@angular/core/testing'
|
||||||
import { Subscription } from 'rxjs'
|
|
||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
import { commonAbstractPaperlessServiceTests } from './abstract-paperless-service.spec'
|
import { commonAbstractPaperlessServiceTests } from './abstract-paperless-service.spec'
|
||||||
import { ConsumptionTemplateService } from './consumption-template.service'
|
import { WorkflowTriggerService } from './workflow-trigger.service'
|
||||||
import {
|
import {
|
||||||
DocumentSource,
|
DocumentSource,
|
||||||
ConsumptionTemplate,
|
WorkflowTrigger,
|
||||||
} from 'src/app/data/consumption-template'
|
WorkflowTriggerType,
|
||||||
|
} from 'src/app/data/workflow-trigger'
|
||||||
|
|
||||||
let httpTestingController: HttpTestingController
|
let httpTestingController: HttpTestingController
|
||||||
let service: ConsumptionTemplateService
|
let service: WorkflowTriggerService
|
||||||
const endpoint = 'consumption_templates'
|
const endpoint = 'workflow_triggers'
|
||||||
const templates: ConsumptionTemplate[] = [
|
const triggers: WorkflowTrigger[] = [
|
||||||
{
|
{
|
||||||
name: 'Template 1',
|
|
||||||
id: 1,
|
id: 1,
|
||||||
order: 1,
|
type: WorkflowTriggerType.Consumption,
|
||||||
filter_filename: '*test*',
|
filter_filename: '*test*',
|
||||||
filter_path: null,
|
filter_path: null,
|
||||||
sources: [DocumentSource.ApiUpload],
|
sources: [DocumentSource.ApiUpload],
|
||||||
assign_correspondent: 2,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Template 2',
|
|
||||||
id: 2,
|
id: 2,
|
||||||
order: 2,
|
type: WorkflowTriggerType.DocumentAdded,
|
||||||
filter_filename: null,
|
filter_filename: null,
|
||||||
filter_path: '/test/',
|
filter_path: '/test/',
|
||||||
sources: [DocumentSource.ConsumeFolder, DocumentSource.ApiUpload],
|
sources: [DocumentSource.ConsumeFolder, DocumentSource.ApiUpload],
|
||||||
assign_document_type: 1,
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// run common tests
|
// run common tests
|
||||||
commonAbstractPaperlessServiceTests(
|
commonAbstractPaperlessServiceTests(endpoint, WorkflowTriggerService)
|
||||||
'consumption_templates',
|
|
||||||
ConsumptionTemplateService
|
|
||||||
)
|
|
||||||
|
|
||||||
describe(`Additional service tests for ConsumptionTemplateService`, () => {
|
describe(`Additional service tests for WorkflowTriggerService`, () => {
|
||||||
it('should reload', () => {
|
it('should reload', () => {
|
||||||
service.reload()
|
service.reload()
|
||||||
const req = httpTestingController.expectOne(
|
const req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=100000`
|
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=100000`
|
||||||
)
|
)
|
||||||
req.flush({
|
req.flush({
|
||||||
results: templates,
|
results: triggers,
|
||||||
})
|
})
|
||||||
expect(service.allTemplates).toEqual(templates)
|
expect(service.allWorkflows).toEqual(triggers)
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Dont need to setup again
|
// Dont need to setup again
|
||||||
|
|
||||||
httpTestingController = TestBed.inject(HttpTestingController)
|
httpTestingController = TestBed.inject(HttpTestingController)
|
||||||
service = TestBed.inject(ConsumptionTemplateService)
|
service = TestBed.inject(WorkflowTriggerService)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
42
src-ui/src/app/services/rest/workflow-trigger.service.ts
Normal file
42
src-ui/src/app/services/rest/workflow-trigger.service.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { tap } from 'rxjs'
|
||||||
|
import { AbstractPaperlessService } from './abstract-paperless-service'
|
||||||
|
import { WorkflowTrigger } from 'src/app/data/workflow-trigger'
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class WorkflowTriggerService extends AbstractPaperlessService<WorkflowTrigger> {
|
||||||
|
loading: boolean
|
||||||
|
|
||||||
|
constructor(http: HttpClient) {
|
||||||
|
super(http, 'workflow_triggers')
|
||||||
|
}
|
||||||
|
|
||||||
|
public reload() {
|
||||||
|
this.loading = true
|
||||||
|
this.listAll().subscribe((r) => {
|
||||||
|
this.triggers = r.results
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private triggers: WorkflowTrigger[] = []
|
||||||
|
|
||||||
|
public get allWorkflows(): WorkflowTrigger[] {
|
||||||
|
return this.triggers
|
||||||
|
}
|
||||||
|
|
||||||
|
create(o: WorkflowTrigger) {
|
||||||
|
return super.create(o).pipe(tap(() => this.reload()))
|
||||||
|
}
|
||||||
|
|
||||||
|
update(o: WorkflowTrigger) {
|
||||||
|
return super.update(o).pipe(tap(() => this.reload()))
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(o: WorkflowTrigger) {
|
||||||
|
return super.delete(o).pipe(tap(() => this.reload()))
|
||||||
|
}
|
||||||
|
}
|
80
src-ui/src/app/services/rest/workflow.service.spec.ts
Normal file
80
src-ui/src/app/services/rest/workflow.service.spec.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { HttpTestingController } from '@angular/common/http/testing'
|
||||||
|
import { TestBed } from '@angular/core/testing'
|
||||||
|
import { environment } from 'src/environments/environment'
|
||||||
|
import { commonAbstractPaperlessServiceTests } from './abstract-paperless-service.spec'
|
||||||
|
import { WorkflowService } from './workflow.service'
|
||||||
|
import { Workflow } from 'src/app/data/workflow'
|
||||||
|
import {
|
||||||
|
DocumentSource,
|
||||||
|
WorkflowTriggerType,
|
||||||
|
} from 'src/app/data/workflow-trigger'
|
||||||
|
|
||||||
|
let httpTestingController: HttpTestingController
|
||||||
|
let service: WorkflowService
|
||||||
|
const endpoint = 'workflows'
|
||||||
|
const workflows: Workflow[] = [
|
||||||
|
{
|
||||||
|
name: 'Workflow 1',
|
||||||
|
id: 1,
|
||||||
|
order: 1,
|
||||||
|
triggers: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: WorkflowTriggerType.Consumption,
|
||||||
|
sources: [DocumentSource.ConsumeFolder],
|
||||||
|
filter_filename: '*',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
assign_title: 'foo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Workflow 2',
|
||||||
|
id: 2,
|
||||||
|
order: 2,
|
||||||
|
triggers: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
type: WorkflowTriggerType.DocumentAdded,
|
||||||
|
filter_filename: 'foo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
assign_title: 'bar',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// run common tests
|
||||||
|
commonAbstractPaperlessServiceTests(endpoint, WorkflowService)
|
||||||
|
|
||||||
|
describe(`Additional service tests for WorkflowService`, () => {
|
||||||
|
it('should reload', () => {
|
||||||
|
service.reload()
|
||||||
|
const req = httpTestingController.expectOne(
|
||||||
|
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=100000`
|
||||||
|
)
|
||||||
|
req.flush({
|
||||||
|
results: workflows,
|
||||||
|
})
|
||||||
|
expect(service.allWorkflows).toEqual(workflows)
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Dont need to setup again
|
||||||
|
|
||||||
|
httpTestingController = TestBed.inject(HttpTestingController)
|
||||||
|
service = TestBed.inject(WorkflowService)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
httpTestingController.verify()
|
||||||
|
})
|
||||||
|
})
|
42
src-ui/src/app/services/rest/workflow.service.ts
Normal file
42
src-ui/src/app/services/rest/workflow.service.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http'
|
||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { tap } from 'rxjs'
|
||||||
|
import { Workflow } from 'src/app/data/workflow'
|
||||||
|
import { AbstractPaperlessService } from './abstract-paperless-service'
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class WorkflowService extends AbstractPaperlessService<Workflow> {
|
||||||
|
loading: boolean
|
||||||
|
|
||||||
|
constructor(http: HttpClient) {
|
||||||
|
super(http, 'workflows')
|
||||||
|
}
|
||||||
|
|
||||||
|
public reload() {
|
||||||
|
this.loading = true
|
||||||
|
this.listAll().subscribe((r) => {
|
||||||
|
this.workflows = r.results
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private workflows: Workflow[] = []
|
||||||
|
|
||||||
|
public get allWorkflows(): Workflow[] {
|
||||||
|
return this.workflows
|
||||||
|
}
|
||||||
|
|
||||||
|
create(o: Workflow) {
|
||||||
|
return super.create(o).pipe(tap(() => this.reload()))
|
||||||
|
}
|
||||||
|
|
||||||
|
update(o: Workflow) {
|
||||||
|
return super.update(o).pipe(tap(() => this.reload()))
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(o: Workflow) {
|
||||||
|
return super.delete(o).pipe(tap(() => this.reload()))
|
||||||
|
}
|
||||||
|
}
|
@ -647,8 +647,6 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.accordion {
|
.accordion {
|
||||||
--bs-accordion-btn-padding-x: 0.75rem;
|
|
||||||
--bs-accordion-btn-padding-y: 0.375rem;
|
|
||||||
--bs-accordion-btn-bg: var(--bs-light);
|
--bs-accordion-btn-bg: var(--bs-light);
|
||||||
--bs-accordion-btn-color: var(--bs-primary);
|
--bs-accordion-btn-color: var(--bs-primary);
|
||||||
--bs-accordion-color: var(--bs-body-color);
|
--bs-accordion-color: var(--bs-body-color);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user