diff --git a/client/src/app/core/repositories/motions/workflow-repository.service.ts b/client/src/app/core/repositories/motions/workflow-repository.service.ts index a3926287a..f7253863e 100644 --- a/client/src/app/core/repositories/motions/workflow-repository.service.ts +++ b/client/src/app/core/repositories/motions/workflow-repository.service.ts @@ -9,6 +9,7 @@ import { Identifiable } from 'app/shared/models/base/identifiable'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { WorkflowState } from 'app/shared/models/motions/workflow-state'; import { ViewMotion } from 'app/site/motions/models/view-motion'; +import { HttpService } from 'app/core/core-services/http.service'; /** * Repository Services for Categories @@ -24,28 +25,54 @@ import { ViewMotion } from 'app/site/motions/models/view-motion'; providedIn: 'root' }) export class WorkflowRepositoryService extends BaseRepository { + /** + * The url to state on rest + */ + private restStateUrl = 'rest/motions/state/'; + /** * Creates a WorkflowRepository * Converts existing and incoming workflow to ViewWorkflows - * @param DS - * @param dataSend + * + * @param DS Accessing the data store + * @param mapperService mapping models + * @param dataSend sending data to the server + * @param httpService HttpService */ public constructor( protected DS: DataStoreService, mapperService: CollectionStringMapperService, - private dataSend: DataSendService + private dataSend: DataSendService, + private httpService: HttpService ) { super(DS, mapperService, Workflow); } + /** + * Creates a ViewWorkflow from a given Workflow + * + * @param workflow the Workflow to convert + */ protected createViewModel(workflow: Workflow): ViewWorkflow { return new ViewWorkflow(workflow); } + /** + * Creates a new workflow + * + * @param newWorkflow the workflow to create + * @returns the ID of a new workflow as promise + */ public async create(newWorkflow: Workflow): Promise { return await this.dataSend.createModel(newWorkflow); } + /** + * Updates the workflow by the given changes + * + * @param workflow Contains the update + * @param viewWorkflow the target workflow + */ public async update(workflow: Partial, viewWorkflow: ViewWorkflow): Promise { let updateWorkflow: Workflow; if (viewWorkflow) { @@ -57,25 +84,70 @@ export class WorkflowRepositoryService extends BaseRepository { const workflow = viewWorkflow.workflow; await this.dataSend.deleteModel(workflow); } + /** + * Adds a new state to the given workflow + * + * @param stateName The name of the new Workflow + * @param viewWorkflow The workflow + */ + public async addState(stateName: string, viewWorkflow: ViewWorkflow): Promise { + const newStatePayload = { + name: stateName, + workflow_id: viewWorkflow.id + }; + await this.httpService.post(this.restStateUrl, newStatePayload); + } + + /** + * Updates workflow state with a new value-object and sends it to the server + * + * @param newValue a key-value pair with the new state value + * @param workflowState the workflow state to update + */ + public async updateState(newValue: object, workflowState: WorkflowState): Promise { + const stateUpdate = Object.assign(workflowState, newValue); + await this.httpService.put(`${this.restStateUrl}${workflowState.id}/`, stateUpdate); + } + + /** + * Deletes the selected work + * + * @param workflowState the workflow state to delete + */ + public async deleteState(workflowState: WorkflowState): Promise { + await this.httpService.delete(`${this.restStateUrl}${workflowState.id}/`); + } + /** * Collects all existing states from all workflows + * + * @returns All currently existing workflow states */ public getAllWorkflowStates(): WorkflowState[] { let states: WorkflowState[] = []; this.getViewModelList().forEach(workflow => { - states = states.concat(workflow.states); + if (workflow) { + states = states.concat(workflow.states); + } }); return states; } /** * Returns all workflowStates that cover the list of viewMotions given - * @param motions + * + * @param motions The motions to get the workflows from + * @returns The workflow states to the given motion */ public getWorkflowStatesForMotions(motions: ViewMotion[]): WorkflowState[] { let states: WorkflowState[] = []; diff --git a/client/src/app/shared/models/motions/workflow-state.ts b/client/src/app/shared/models/motions/workflow-state.ts index fdf334298..6e39361b8 100644 --- a/client/src/app/shared/models/motions/workflow-state.ts +++ b/client/src/app/shared/models/motions/workflow-state.ts @@ -21,7 +21,7 @@ export class WorkflowState extends Deserializer { public name: string; public recommendation_label: string; public css_class: string; - public required_permission_to_see: string; + public access_level: number; public allow_support: boolean; public allow_create_poll: boolean; public allow_submitter_edit: boolean; diff --git a/client/src/app/shared/models/motions/workflow.ts b/client/src/app/shared/models/motions/workflow.ts index babbd73ec..75bf9a278 100644 --- a/client/src/app/shared/models/motions/workflow.ts +++ b/client/src/app/shared/models/motions/workflow.ts @@ -9,7 +9,11 @@ export class Workflow extends BaseModel { public id: number; public name: string; public states: WorkflowState[]; - public first_state: number; + public first_state_id: number; + + public get firstState(): WorkflowState { + return this.getStateById(this.first_state_id); + } public constructor(input?: any) { super('motions/workflow', 'Workflow', input); @@ -35,13 +39,7 @@ export class Workflow extends BaseModel { } public getStateById(id: number): WorkflowState { - let targetState; - this.states.forEach(state => { - if (id === state.id) { - targetState = state; - } - }); - return targetState as WorkflowState; + return this.states.find(state => state.id === id); } public deserialize(input: any): void { diff --git a/client/src/app/site/motions/components/motion-list/motion-list.component.html b/client/src/app/site/motions/components/motion-list/motion-list.component.html index ec38d5856..df5e030af 100644 --- a/client/src/app/site/motions/components/motion-list/motion-list.component.html +++ b/client/src/app/site/motions/components/motion-list/motion-list.component.html @@ -172,6 +172,10 @@ speaker_notes Comment fields + + + + + +
+
+ + First state: + {{ workflow.firstState }} + +
+ +
+ + + Permissions + +
+ {{ perm.name | translate }} +
+
+
+ +
+ + +
+
+ {{ state.name | translate }} +
+
+
+ +
+ +
+
+
+ {{ state[perm.selector] || '-' | translate }} +
+
+
+ + {{ state[perm.selector] }} + +
+
+
+ {{ state.next_states_id.length > 0 ? state.getNextStates(workflow.workflow) : '-' }} +
+
+
+
+ {{ getMergeAmendmentLabel(state.merge_amendment_into_final) | translate }} +
+
+
+
+ {{ accessLevels[state.access_level].label }} +
+
+
+
+
+ + + +
+
+
+ + + +

+ {{ dialogData.title }} +

+
+

{{ dialogData.description }}

+ + + +
+
+ + + +
+
+ + + + + + + + + + + +
+ +
+
+
+ + + + + + + + + + + +
+ +
+
+
diff --git a/client/src/app/site/motions/components/workflow-detail/workflow-detail.component.scss b/client/src/app/site/motions/components/workflow-detail/workflow-detail.component.scss new file mode 100644 index 000000000..623b819b9 --- /dev/null +++ b/client/src/app/site/motions/components/workflow-detail/workflow-detail.component.scss @@ -0,0 +1,54 @@ +.title-line { + margin: 20px 25px; +} + +table { + width: 100%; + + .mat-header-cell { + min-width: 150px; + position: relative; + } + + .mat-cell { + min-width: 150px; + position: relative; + } + + // scaled up version of an inner table + .clickable-cell { + position: absolute; + display: flex; + width: 100%; + height: 100%; + } + + .clickable-cell:hover { + cursor: pointer; + background-color: rgba(0, 0, 0, 0.025); + } + + .inner-table { + align-items: center; + margin: auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 0 10px; + } + + .mat-column-perm { + min-width: 150px; + .permission-name { + margin-right: 10px; + } + } +} + +.mat-chip:hover { + cursor: pointer; +} + +.scrollable-matrix { + overflow: auto; +} diff --git a/client/src/app/site/motions/components/workflow-detail/workflow-detail.component.spec.ts b/client/src/app/site/motions/components/workflow-detail/workflow-detail.component.spec.ts new file mode 100644 index 000000000..40a857a3c --- /dev/null +++ b/client/src/app/site/motions/components/workflow-detail/workflow-detail.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkflowDetailComponent } from './workflow-detail.component'; +import { E2EImportsModule } from 'e2e-imports.module'; + +describe('WorkflowDetailComponent', () => { + let component: WorkflowDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [E2EImportsModule], + declarations: [WorkflowDetailComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkflowDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/site/motions/components/workflow-detail/workflow-detail.component.ts b/client/src/app/site/motions/components/workflow-detail/workflow-detail.component.ts new file mode 100644 index 000000000..68b0ddde7 --- /dev/null +++ b/client/src/app/site/motions/components/workflow-detail/workflow-detail.component.ts @@ -0,0 +1,390 @@ +import { ActivatedRoute } from '@angular/router'; +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { MatDialog, MatSnackBar, MatTableDataSource, MatCheckboxChange } from '@angular/material'; +import { Observable } from 'rxjs'; +import { Title } from '@angular/platform-browser'; + +import { TranslateService } from '@ngx-translate/core'; + +import { BaseViewComponent } from 'app/site/base/base-view'; +import { ViewWorkflow } from '../../models/view-workflow'; +import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service'; +import { WorkflowState, MergeAmendment } from 'app/shared/models/motions/workflow-state'; + +/** + * Declares data for the workflow dialog + */ +interface DialogData { + title: string; + description: string; + value: string; + deletable?: boolean; +} + +/** + * Determine answers from the dialog + */ +interface DialogResult { + action: 'update' | 'delete'; + value?: string; +} + +/** + * Defines state permissions + */ +interface StatePerm { + name: string; + selector: string; + type: string; + reference?: string; +} + +/** + * Defines the structure of access levels + */ +interface AccessLevel { + level: number; + label: string; +} + +/** + * Defines the structure of access levels + */ +interface AmendmentIntoFinal { + merge: MergeAmendment; + label: string; +} + +/** + * Motion workflow management detail view + */ +@Component({ + selector: 'os-workflow-detail', + templateUrl: './workflow-detail.component.html', + styleUrls: ['./workflow-detail.component.scss'] +}) +export class WorkflowDetailComponent extends BaseViewComponent implements OnInit { + /** + * Reference to the workflow dialog + */ + @ViewChild('workflowDialog') + private workflowDialog: TemplateRef; + + /** + * Holds the dialog data + */ + public dialogData: DialogData; + + /** + * Holds the current workflow + */ + public workflow: ViewWorkflow; + + /** + * The header rows that the table should show + * Updated through subscription + */ + public headerRowDef: string[] = []; + + /** + * Determine label colors. Where they should come from is currently now know + */ + public labelColors = ['default', 'primary', 'success', 'danger']; + + /** + * Holds state permissions + */ + private statePermissionsList = [ + { name: 'Recommendation label', selector: 'recommendation_label', type: 'input' }, + { name: 'Allow support', selector: 'allow_support', type: 'check' }, + { name: 'Allow create poll', selector: 'allow_create_poll', type: 'check' }, + { name: 'Allow submitter edit', selector: 'allow_submitter_edit', type: 'check' }, + { name: 'Set identifier', selector: 'dont_set_identifier', type: 'check' }, + { name: 'Show state extension field', selector: 'show_state_extension_field', type: 'check' }, + { + name: 'Show recommendation extension field', + selector: 'show_recommendation_extension_field', + type: 'check' + }, + { name: 'Show amendment in parent motoin', selector: 'merge_amendment_into_final', type: 'amendment' }, + { name: 'Access level', selector: 'access_level', type: 'accessLevel' }, + { name: 'Label color', selector: 'css_class', type: 'color' }, + { name: 'Next states', selector: 'next_states_id', type: 'state' } + ] as StatePerm[]; + + /** + * Determines possible access levels + */ + public accessLevels = [ + { level: 0, label: 'All users' }, + { level: 1, label: 'Submitters, managers and users with permission to manage metadata' }, + { level: 2, label: 'Only managers and users with permission to manage metadata' }, + { level: 3, label: 'Managers only' } + ] as AccessLevel[]; + + /** + * Determines possible "Merge amendments into final" + */ + public amendmentIntoFinal = [ + { merge: MergeAmendment.NO, label: 'No' }, + { merge: MergeAmendment.UNDEFINED, label: '-' }, + { merge: MergeAmendment.YES, label: 'Yes' } + ] as AmendmentIntoFinal[]; + + /** + * Constructor + * + * @param title Set the page title + * @param translate Handle translations + * @param matSnackBar Showing error + * @param dialog Opening dialogs + * @param workflowRepo The repository for workflows + * @param route Read out URL paramters + */ + public constructor( + title: Title, + translate: TranslateService, + matSnackBar: MatSnackBar, + private dialog: MatDialog, + private workflowRepo: WorkflowRepositoryService, + private route: ActivatedRoute + ) { + super(title, translate, matSnackBar); + } + + /** + * Init. + * + * Observe the parameters of the URL and loads the specified workflow + */ + public ngOnInit(): void { + this.route.params.subscribe(params => { + if (params) { + this.workflowRepo.getViewModelObservable(params.id).subscribe(newWorkflow => { + this.workflow = newWorkflow; + this.updateRowDef(); + }); + } + }); + } + + /** + * Click handler for the state names. + * Opens a dialog to rename a state. + * + * @param state the selected workflow state + */ + public onClickStateName(state: WorkflowState): void { + this.openEditDialog(state.name, 'Rename state', '', true).subscribe(result => { + if (result) { + if (result.action === 'update') { + this.workflowRepo.updateState({ name: result.value }, state).then(() => {}, this.raiseError); + } else if (result.action === 'delete') { + this.workflowRepo.deleteState(state).then(() => {}, this.raiseError); + } + } + }); + } + + /** + * Click handler for the button to add new states the the current workflow + * Opens a dialog to enter the workflow name + */ + public onNewStateButton(): void { + this.openEditDialog('', 'Create new state', 'Name').subscribe(result => { + if (result && result.action === 'update') { + this.workflowRepo.addState(result.value, this.workflow).then(() => {}, this.raiseError); + } + }); + } + + /** + * Click handler for the edit button. + * Opens a dialog to rename the workflow + */ + public onEditWorkflowButton(): void { + this.openEditDialog(this.workflow.name, 'Edit name', 'Please enter a new workflow name:').subscribe(result => { + if (result && result.action === 'update') { + this.workflowRepo.update({ name: result.value }, this.workflow).then(() => {}, this.raiseError); + } + }); + } + + /** + * Handler for the workflow state "input" fields. + * Opens a dialog to edit a label. + * + * @param perm The permission + * @param state The selected workflow state + */ + public onClickInputPerm(perm: StatePerm, state: WorkflowState): void { + this.openEditDialog(state[perm.selector], 'Edit', perm.name).subscribe(result => { + if (result && result.action === 'update') { + this.workflowRepo.updateState({ [perm.selector]: result.value }, state).then(() => {}, this.raiseError); + } + }); + } + + /** + * Handler for toggling workflow states + * + * @param state The workflow state to edit + * @param perm The states permission that was changed + * @param event The change event. + */ + public onToggleStatePerm(state: WorkflowState, perm: string, event: MatCheckboxChange): void { + this.workflowRepo.updateState({ [perm]: event.checked }, state).then(() => {}, this.raiseError); + } + + /** + * Handler for selecting colors / css classes for workflow states. + * Sets the css class for the specific workflow + * + * @param state The selected workflow state + * @param color The selected color + */ + public onSelectColor(state: WorkflowState, color: string): void { + this.workflowRepo.updateState({ css_class: color }, state).then(() => {}, this.raiseError); + } + + /** + * Handler to add or remove next states to a workflow state + * + * @param nextState the potential next workflow state + * @param state the state to add or remove another state to + */ + public onSetNextState(nextState: WorkflowState, state: WorkflowState): void { + const ids = state.next_states_id.map(id => id); + const stateIdIndex = ids.findIndex(id => id === nextState.id); + + if (stateIdIndex < 0) { + ids.push(nextState.id); + } else { + ids.splice(stateIdIndex, 1); + } + this.workflowRepo.updateState({ next_states_id: ids }, state).then(() => {}, this.raiseError); + } + + /** + * Sets an access level to the given workflow state + * + * @param level The new access level + * @param state the state to change + */ + public onSetAccesLevel(level: number, state: WorkflowState): void { + this.workflowRepo.updateState({ access_level: level }, state).then(() => {}, this.raiseError); + } + + /** + * Sets the 'merge_amendment_into_final' value + * + * @param amendment determines the amendment + * @param state the state to change + */ + public setMergeAmendment(amendment: number, state: WorkflowState): void { + this.workflowRepo.updateState({ merge_amendment_into_final: amendment }, state).then(() => {}, this.raiseError); + } + + /** + * Function to open the edit dialog. Returns the observable to the result after the dialog + * was closed + * + * @param value holds the valie + * @param title The title of the dialog + * @param description The description of the dialog + * @param deletable determine if a delete button should be offered + */ + private openEditDialog( + value: string, + title?: string, + description?: string, + deletable?: boolean + ): Observable { + this.dialogData = { + title: title || '', + description: description || '', + value: value, + deletable: deletable + }; + + const dialogRef = this.dialog.open(this.workflowDialog, { + maxHeight: '90vh', + width: '400px', + maxWidth: '90vw' + }); + + return dialogRef.afterClosed(); + } + + /** + * Returns a merge amendment label from state + */ + public getMergeAmendmentLabel(mergeAmendment: MergeAmendment): string { + return this.amendmentIntoFinal.find(amendment => amendment.merge === mergeAmendment).label; + } + + /** + * Defines the data source for the workflow state table + * + * @returns the MatTableDateSource to iterate over a workflow states contents + */ + public getTableDataSource(): MatTableDataSource { + const dataSource = new MatTableDataSource(); + dataSource.data = this.statePermissionsList; + return dataSource; + } + + /** + * Update the rowDefinition after Reloading or changes + */ + public updateRowDef(): void { + // reset the rowDef list first + this.headerRowDef = ['perm']; + if (this.workflow) { + this.workflow.states.forEach(state => { + this.headerRowDef.push(this.getColumnDef(state)); + }); + } + } + + /** + * Creates a unique column-def from the name and the id of a state + * + * @param state the workflow state + * @returns a unique definition + */ + public getColumnDef(state: WorkflowState): string { + return `${state.name}${state.id}`; + } + + /** + * Required to detect changes in *ngFor loops + * + * @param index Corresponding group that was changed + * @returns the tracked workflows id + */ + public trackElement(index: number): number { + return index; + } + + /** + * Translate the state's css class into a color + * + * @param colorLabel the default color label of a selected workflow + * @returns a string representing a color + */ + public getStateCssColor(colorLabel: string): string { + switch (colorLabel) { + case 'success': + return 'green'; + case 'danger': + return 'red'; + case 'default': + return 'grey'; + case 'primary': + return 'lightblue'; + default: + return ''; + } + } +} diff --git a/client/src/app/site/motions/components/workflow-list/workflow-list.component.html b/client/src/app/site/motions/components/workflow-list/workflow-list.component.html new file mode 100644 index 000000000..bcfd377b3 --- /dev/null +++ b/client/src/app/site/motions/components/workflow-list/workflow-list.component.html @@ -0,0 +1,51 @@ + + + +

Workflows

+
+ + + + + + Name + + + {{ workflow }} + + + + + + + + + + + + + + + + + +

+ Create new workflow +

+
+

Please enter a name for the new workflow:

+ + + +
+
+ + +
+
diff --git a/client/src/app/site/motions/components/workflow-list/workflow-list.component.scss b/client/src/app/site/motions/components/workflow-list/workflow-list.component.scss new file mode 100644 index 000000000..8893fc535 --- /dev/null +++ b/client/src/app/site/motions/components/workflow-list/workflow-list.component.scss @@ -0,0 +1,14 @@ +.os-headed-listview-table { + /** name */ + .mat-column-name { + width: 100%; + flex: 1 0 200px; + padding-left: 10px; + } + + /** delete */ + .mat-column-delete { + flex: 0 0 190px; + justify-content: flex-end !important; + } +} diff --git a/client/src/app/site/motions/components/workflow-list/workflow-list.component.spec.ts b/client/src/app/site/motions/components/workflow-list/workflow-list.component.spec.ts new file mode 100644 index 000000000..269e4401d --- /dev/null +++ b/client/src/app/site/motions/components/workflow-list/workflow-list.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkflowListComponent } from './workflow-list.component'; +import { E2EImportsModule } from 'e2e-imports.module'; + +describe('WorkflowListComponent', () => { + let component: WorkflowListComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [E2EImportsModule], + declarations: [WorkflowListComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkflowListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/site/motions/components/workflow-list/workflow-list.component.ts b/client/src/app/site/motions/components/workflow-list/workflow-list.component.ts new file mode 100644 index 000000000..47ea1bc0f --- /dev/null +++ b/client/src/app/site/motions/components/workflow-list/workflow-list.component.ts @@ -0,0 +1,114 @@ +import { Component, OnInit, TemplateRef } from '@angular/core'; +import { Title } from '@angular/platform-browser'; + +import { TranslateService } from '@ngx-translate/core'; + +import { ListViewBaseComponent } from 'app/site/base/list-view-base'; +import { MatSnackBar, MatDialog } from '@angular/material'; +import { PromptService } from 'app/core/ui-services/prompt.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ViewWorkflow } from '../../models/view-workflow'; +import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service'; +import { Workflow } from 'app/shared/models/motions/workflow'; + +/** + * List view for workflows + */ +@Component({ + selector: 'os-workflow-list', + templateUrl: './workflow-list.component.html', + styleUrls: ['./workflow-list.component.scss'] +}) +export class WorkflowListComponent extends ListViewBaseComponent implements OnInit { + /** + * Holds the new workflow title + */ + public newWorkflowTitle: string; + + /** + * Determine the coloms in the table + */ + private columns: string[] = ['name', 'delete']; + + /** + * Constructor + * + * @param titleService Sets the title + * @param matSnackBar Showing errors + * @param translate handle trandlations + * @param dialog Dialog options + * @param router navigating back and forth + * @param route Information about the current router + * @param workflowRepo Repository for Workflows + * @param promptService Before delete, ask + */ + public constructor( + titleService: Title, + matSnackBar: MatSnackBar, + protected translate: TranslateService, + private dialog: MatDialog, + private router: Router, + private route: ActivatedRoute, + private workflowRepo: WorkflowRepositoryService, + private promptService: PromptService + ) { + super(titleService, translate, matSnackBar); + } + + /** + * Init. Observe the repository + */ + public ngOnInit(): void { + super.setTitle('Workflows'); + this.initTable(); + this.workflowRepo.getViewModelListObservable().subscribe(newWorkflows => (this.dataSource.data = newWorkflows)); + } + + /** + * Click a workflow in the table + * + * @param selected the selected workflow + */ + public onClickWorkflow(selected: ViewWorkflow): void { + this.router.navigate([`${selected.id}`], { relativeTo: this.route }); + } + + /** + * Main Event handler. Create new Workflow + * + * @param templateRef The reference to the dialog + */ + public onNewButton(templateRef: TemplateRef): void { + this.newWorkflowTitle = ''; + const dialogRef = this.dialog.open(templateRef, { + width: '400px' + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.workflowRepo.create(new Workflow({ name: result })).then(() => {}, this.raiseError); + } + }); + } + + /** + * Click delete button for workflow + * + * @param selected the selected workflow + */ + public async onDeleteWorkflow(selected: ViewWorkflow): Promise { + const content = this.translate.instant('Delete') + ` ${selected}?`; + if (await this.promptService.open('Are you sure?', content)) { + this.workflowRepo.delete(selected).then(() => {}, this.raiseError); + } + } + + /** + * Get the column definition for the current workflow table + * + * @returns The column definition for the table + */ + public getColumnDefinition(): string[] { + return this.columns; + } +} diff --git a/client/src/app/site/motions/models/view-workflow.ts b/client/src/app/site/motions/models/view-workflow.ts index d2ca191ec..d284c7bff 100644 --- a/client/src/app/site/motions/models/view-workflow.ts +++ b/client/src/app/site/motions/models/view-workflow.ts @@ -1,6 +1,7 @@ import { Workflow } from 'app/shared/models/motions/workflow'; import { WorkflowState } from 'app/shared/models/motions/workflow-state'; import { BaseViewModel } from '../../base/base-view-model'; +import { Deserializable } from 'app/shared/models/base/deserializable'; /** * class for the ViewWorkflow. Currently only a basic stub @@ -33,8 +34,16 @@ export class ViewWorkflow extends BaseViewModel { return this.workflow ? this.workflow.states : null; } - public get first_state(): number { - return this.workflow ? this.workflow.first_state : null; + public get first_state_id(): number { + return this.workflow ? this.workflow.first_state_id : null; + } + + public get firstState(): WorkflowState { + return this.workflow ? this.workflow.firstState : null; + } + + public getStateById(id: number): WorkflowState { + return this.workflow ? this.workflow.getStateById(id) : null; } public getTitle(): string { @@ -50,9 +59,12 @@ export class ViewWorkflow extends BaseViewModel { /** * Updates the local objects if required + * * @param update */ - public updateValues(update: Workflow): void { - this._workflow = update; + public updateValues(update: Deserializable): void { + if (update instanceof Workflow) { + this._workflow = update; + } } } diff --git a/client/src/app/site/motions/motions-routing.module.ts b/client/src/app/site/motions/motions-routing.module.ts index f53f731d3..549309f15 100644 --- a/client/src/app/site/motions/motions-routing.module.ts +++ b/client/src/app/site/motions/motions-routing.module.ts @@ -13,6 +13,8 @@ import { MotionListComponent } from './components/motion-list/motion-list.compon import { SpeakerListComponent } from '../agenda/components/speaker-list/speaker-list.component'; import { StatuteImportListComponent } from './components/statute-paragraph-list/statute-import-list/statute-import-list.component'; import { StatuteParagraphListComponent } from './components/statute-paragraph-list/statute-paragraph-list.component'; +import { WorkflowListComponent } from './components/workflow-list/workflow-list.component'; +import { WorkflowDetailComponent } from './components/workflow-detail/workflow-detail.component'; const routes: Routes = [ { path: '', component: MotionListComponent }, @@ -25,6 +27,8 @@ const routes: Routes = [ { path: 'blocks/:id', component: MotionBlockDetailComponent }, { path: 'new', component: MotionDetailComponent }, { path: 'import', component: MotionImportListComponent }, + { path: 'workflow', component: WorkflowListComponent }, + { path: 'workflow/:id', component: WorkflowDetailComponent }, { path: ':id', component: MotionDetailComponent }, { path: ':id/speakers', component: SpeakerListComponent }, { path: ':id/create-amendment', component: AmendmentCreateWizardComponent } diff --git a/client/src/app/site/motions/motions.module.ts b/client/src/app/site/motions/motions.module.ts index 96ff274ed..366d43863 100644 --- a/client/src/app/site/motions/motions.module.ts +++ b/client/src/app/site/motions/motions.module.ts @@ -23,6 +23,8 @@ import { MotionPollComponent } from './components/motion-poll/motion-poll.compon import { MotionPollDialogComponent } from './components/motion-poll/motion-poll-dialog.component'; import { MotionExportDialogComponent } from './components/motion-export-dialog/motion-export-dialog.component'; import { StatuteImportListComponent } from './components/statute-paragraph-list/statute-import-list/statute-import-list.component'; +import { WorkflowListComponent } from './components/workflow-list/workflow-list.component'; +import { WorkflowDetailComponent } from './components/workflow-detail/workflow-detail.component'; @NgModule({ imports: [CommonModule, MotionsRoutingModule, SharedModule], @@ -46,7 +48,9 @@ import { StatuteImportListComponent } from './components/statute-paragraph-list/ MotionPollComponent, MotionPollDialogComponent, MotionExportDialogComponent, - StatuteImportListComponent + StatuteImportListComponent, + WorkflowListComponent, + WorkflowDetailComponent ], entryComponents: [ MotionChangeRecommendationComponent,