Merge pull request #4260 from tsiegleauq/workflow-creator
Add motion workflow creator
This commit is contained in:
commit
9664e52237
@ -9,6 +9,7 @@ import { Identifiable } from 'app/shared/models/base/identifiable';
|
|||||||
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
|
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
|
||||||
import { WorkflowState } from 'app/shared/models/motions/workflow-state';
|
import { WorkflowState } from 'app/shared/models/motions/workflow-state';
|
||||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||||
|
import { HttpService } from 'app/core/core-services/http.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository Services for Categories
|
* Repository Services for Categories
|
||||||
@ -24,28 +25,54 @@ import { ViewMotion } from 'app/site/motions/models/view-motion';
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Workflow> {
|
export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Workflow> {
|
||||||
|
/**
|
||||||
|
* The url to state on rest
|
||||||
|
*/
|
||||||
|
private restStateUrl = 'rest/motions/state/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a WorkflowRepository
|
* Creates a WorkflowRepository
|
||||||
* Converts existing and incoming workflow to ViewWorkflows
|
* 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(
|
public constructor(
|
||||||
protected DS: DataStoreService,
|
protected DS: DataStoreService,
|
||||||
mapperService: CollectionStringMapperService,
|
mapperService: CollectionStringMapperService,
|
||||||
private dataSend: DataSendService
|
private dataSend: DataSendService,
|
||||||
|
private httpService: HttpService
|
||||||
) {
|
) {
|
||||||
super(DS, mapperService, Workflow);
|
super(DS, mapperService, Workflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a ViewWorkflow from a given Workflow
|
||||||
|
*
|
||||||
|
* @param workflow the Workflow to convert
|
||||||
|
*/
|
||||||
protected createViewModel(workflow: Workflow): ViewWorkflow {
|
protected createViewModel(workflow: Workflow): ViewWorkflow {
|
||||||
return new ViewWorkflow(workflow);
|
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<Identifiable> {
|
public async create(newWorkflow: Workflow): Promise<Identifiable> {
|
||||||
return await this.dataSend.createModel(newWorkflow);
|
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<Workflow>, viewWorkflow: ViewWorkflow): Promise<void> {
|
public async update(workflow: Partial<Workflow>, viewWorkflow: ViewWorkflow): Promise<void> {
|
||||||
let updateWorkflow: Workflow;
|
let updateWorkflow: Workflow;
|
||||||
if (viewWorkflow) {
|
if (viewWorkflow) {
|
||||||
@ -57,25 +84,70 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
|
|||||||
await this.dataSend.updateModel(updateWorkflow);
|
await this.dataSend.updateModel(updateWorkflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given workflow
|
||||||
|
*
|
||||||
|
* @param viewWorkflow the workflow to delete
|
||||||
|
*/
|
||||||
public async delete(viewWorkflow: ViewWorkflow): Promise<void> {
|
public async delete(viewWorkflow: ViewWorkflow): Promise<void> {
|
||||||
const workflow = viewWorkflow.workflow;
|
const workflow = viewWorkflow.workflow;
|
||||||
await this.dataSend.deleteModel(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<void> {
|
||||||
|
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<void> {
|
||||||
|
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<void> {
|
||||||
|
await this.httpService.delete(`${this.restStateUrl}${workflowState.id}/`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collects all existing states from all workflows
|
* Collects all existing states from all workflows
|
||||||
|
*
|
||||||
|
* @returns All currently existing workflow states
|
||||||
*/
|
*/
|
||||||
public getAllWorkflowStates(): WorkflowState[] {
|
public getAllWorkflowStates(): WorkflowState[] {
|
||||||
let states: WorkflowState[] = [];
|
let states: WorkflowState[] = [];
|
||||||
this.getViewModelList().forEach(workflow => {
|
this.getViewModelList().forEach(workflow => {
|
||||||
|
if (workflow) {
|
||||||
states = states.concat(workflow.states);
|
states = states.concat(workflow.states);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return states;
|
return states;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all workflowStates that cover the list of viewMotions given
|
* 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[] {
|
public getWorkflowStatesForMotions(motions: ViewMotion[]): WorkflowState[] {
|
||||||
let states: WorkflowState[] = [];
|
let states: WorkflowState[] = [];
|
||||||
|
@ -21,7 +21,7 @@ export class WorkflowState extends Deserializer {
|
|||||||
public name: string;
|
public name: string;
|
||||||
public recommendation_label: string;
|
public recommendation_label: string;
|
||||||
public css_class: string;
|
public css_class: string;
|
||||||
public required_permission_to_see: string;
|
public access_level: number;
|
||||||
public allow_support: boolean;
|
public allow_support: boolean;
|
||||||
public allow_create_poll: boolean;
|
public allow_create_poll: boolean;
|
||||||
public allow_submitter_edit: boolean;
|
public allow_submitter_edit: boolean;
|
||||||
|
@ -9,7 +9,11 @@ export class Workflow extends BaseModel<Workflow> {
|
|||||||
public id: number;
|
public id: number;
|
||||||
public name: string;
|
public name: string;
|
||||||
public states: WorkflowState[];
|
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) {
|
public constructor(input?: any) {
|
||||||
super('motions/workflow', 'Workflow', input);
|
super('motions/workflow', 'Workflow', input);
|
||||||
@ -35,13 +39,7 @@ export class Workflow extends BaseModel<Workflow> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getStateById(id: number): WorkflowState {
|
public getStateById(id: number): WorkflowState {
|
||||||
let targetState;
|
return this.states.find(state => state.id === id);
|
||||||
this.states.forEach(state => {
|
|
||||||
if (id === state.id) {
|
|
||||||
targetState = state;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return targetState as WorkflowState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public deserialize(input: any): void {
|
public deserialize(input: any): void {
|
||||||
|
@ -172,6 +172,10 @@
|
|||||||
<mat-icon>speaker_notes</mat-icon>
|
<mat-icon>speaker_notes</mat-icon>
|
||||||
<span translate>Comment fields</span>
|
<span translate>Comment fields</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button mat-menu-item routerLink="workflow">
|
||||||
|
<mat-icon>build</mat-icon>
|
||||||
|
<span translate>Workflows</span>
|
||||||
|
</button>
|
||||||
<button mat-menu-item routerLink="/tags" *osPerms="'core.can_manage_tags'">
|
<button mat-menu-item routerLink="/tags" *osPerms="'core.can_manage_tags'">
|
||||||
<mat-icon>local_offer</mat-icon>
|
<mat-icon>local_offer</mat-icon>
|
||||||
<span translate>Tags</span>
|
<span translate>Tags</span>
|
||||||
|
@ -0,0 +1,189 @@
|
|||||||
|
<os-head-bar [nav]="false" [mainButton]="true" (mainEvent)="onNewStateButton()">
|
||||||
|
<!-- Title -->
|
||||||
|
<div class="title-slot">
|
||||||
|
<h2 *ngIf="workflow">
|
||||||
|
{{ workflow }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit button -->
|
||||||
|
<div class="extra-controls-slot on-transition-fade">
|
||||||
|
<button mat-icon-button (click)="onEditWorkflowButton()">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</os-head-bar>
|
||||||
|
|
||||||
|
<!-- Detail -->
|
||||||
|
<div *ngIf="workflow">
|
||||||
|
<div class="title-line">
|
||||||
|
<strong>
|
||||||
|
<span translate>First state</span>:
|
||||||
|
<span>{{ workflow.firstState }}</span>
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scrollable-matrix">
|
||||||
|
<table mat-table class="on-transition-fade" [dataSource]="getTableDataSource()">
|
||||||
|
<ng-container matColumnDef="perm" sticky>
|
||||||
|
<mat-header-cell class="group-head-table-cell" *matHeaderCellDef translate>Permissions</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let perm">
|
||||||
|
<div class="permission-name">
|
||||||
|
{{ perm.name | translate }}
|
||||||
|
</div>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div *ngFor="let state of workflow.states; trackBy: trackElement">
|
||||||
|
<ng-container [matColumnDef]="getColumnDef(state)">
|
||||||
|
<mat-header-cell *matHeaderCellDef (click)="onClickStateName(state)">
|
||||||
|
<div class="clickable-cell">
|
||||||
|
<div class="inner-table">
|
||||||
|
{{ state.name | translate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let perm">
|
||||||
|
<div class="inner-table" *ngIf="perm.type === 'check'">
|
||||||
|
<mat-checkbox
|
||||||
|
[checked]="state[perm.selector]"
|
||||||
|
(change)="onToggleStatePerm(state, perm.selector, $event)"
|
||||||
|
></mat-checkbox>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="clickable-cell"
|
||||||
|
*ngIf="perm.type === 'input'"
|
||||||
|
(click)="onClickInputPerm(perm, state)"
|
||||||
|
>
|
||||||
|
<div class="inner-table">
|
||||||
|
{{ state[perm.selector] || '-' | translate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="inner-table" *ngIf="perm.type === 'color'">
|
||||||
|
<mat-basic-chip
|
||||||
|
[matMenuTriggerFor]="colorMenu"
|
||||||
|
[matMenuTriggerData]="{ state: state }"
|
||||||
|
[disableRipple]="true"
|
||||||
|
[ngClass]="getStateCssColor(state[perm.selector])"
|
||||||
|
>
|
||||||
|
{{ state[perm.selector] }}
|
||||||
|
</mat-basic-chip>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="clickable-cell"
|
||||||
|
*ngIf="perm.type === 'state'"
|
||||||
|
[matMenuTriggerFor]="nextStatesMenu"
|
||||||
|
[matMenuTriggerData]="{ state: state }"
|
||||||
|
>
|
||||||
|
<div class="inner-table">
|
||||||
|
{{ state.next_states_id.length > 0 ? state.getNextStates(workflow.workflow) : '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="clickable-cell"
|
||||||
|
*ngIf="perm.type === 'amendment'"
|
||||||
|
[matMenuTriggerFor]="mergeAmendmentMenu"
|
||||||
|
[matMenuTriggerData]="{ state: state }"
|
||||||
|
>
|
||||||
|
<div class="inner-table">
|
||||||
|
{{ getMergeAmendmentLabel(state.merge_amendment_into_final) | translate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="clickable-cell"
|
||||||
|
*ngIf="perm.type === 'accessLevel'"
|
||||||
|
[matMenuTriggerFor]="accessLevelMenu"
|
||||||
|
[matMenuTriggerData]="{ state: state }"
|
||||||
|
>
|
||||||
|
<div class="inner-table">
|
||||||
|
{{ accessLevels[state.access_level].label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-header-row *matHeaderRowDef="headerRowDef"></mat-header-row>
|
||||||
|
<mat-row *matRowDef="let row; columns: headerRowDef"></mat-row>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- New state dialog -->
|
||||||
|
<ng-template #workflowDialog>
|
||||||
|
<h1 mat-dialog-title>
|
||||||
|
<span translate>{{ dialogData.title }}</span>
|
||||||
|
</h1>
|
||||||
|
<div class="os-form-card-mobile" mat-dialog-content>
|
||||||
|
<p translate>{{ dialogData.description }}</p>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput osAutofocus [(ngModel)]="dialogData.value" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
mat-button
|
||||||
|
color="primary"
|
||||||
|
[mat-dialog-close]="{ action: 'update', value: dialogData.value }"
|
||||||
|
>
|
||||||
|
<span translate>Save</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" mat-button [mat-dialog-close]="null">
|
||||||
|
<span translate>Cancel</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
mat-button
|
||||||
|
color="warn"
|
||||||
|
*ngIf="dialogData.deletable"
|
||||||
|
[mat-dialog-close]="{ action: 'delete' }"
|
||||||
|
>
|
||||||
|
<span translate>Delete</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<!-- select color menu -->
|
||||||
|
<mat-menu matMenuContent #colorMenu="matMenu">
|
||||||
|
<ng-template let-state="state" matMenuContent>
|
||||||
|
<button mat-menu-item *ngFor="let color of labelColors" (click)="onSelectColor(state, color)">
|
||||||
|
<mat-icon *ngIf="color === state.css_class">check</mat-icon>
|
||||||
|
<span>{{ color | translate }}</span>
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<!-- select next states menu -->
|
||||||
|
<mat-menu matMenuContent #nextStatesMenu="matMenu">
|
||||||
|
<ng-template let-state="state" matMenuContent>
|
||||||
|
<div *ngFor="let nextState of workflow.states">
|
||||||
|
<button mat-menu-item *ngIf="nextState.name !== state.name" (click)="onSetNextState(nextState, state)">
|
||||||
|
<mat-icon *ngIf="state.next_states_id.includes(nextState.id)">check</mat-icon>
|
||||||
|
<span>{{ nextState.name | translate }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<!-- Select access level menu -->
|
||||||
|
<mat-menu matMenuContent #accessLevelMenu="matMenu">
|
||||||
|
<ng-template let-state="state" matMenuContent>
|
||||||
|
<button mat-menu-item *ngFor="let level of accessLevels" (click)="onSetAccesLevel(level.level, state)">
|
||||||
|
<mat-icon *ngIf="state.access_level === level.level">check</mat-icon>
|
||||||
|
<span translate> {{ level.label }}</span>
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<!-- Select merge amendment menu -->
|
||||||
|
<mat-menu matMenuContent #mergeAmendmentMenu="matMenu">
|
||||||
|
<ng-template let-state="state" matMenuContent>
|
||||||
|
<div *ngFor="let amendment of amendmentIntoFinal">
|
||||||
|
<button mat-menu-item (click)="setMergeAmendment(amendment.merge, state)">
|
||||||
|
<mat-icon *ngIf="amendment.merge === state.merge_amendment_into_final">check</mat-icon>
|
||||||
|
<span translate> {{ amendment.label }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</mat-menu>
|
@ -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;
|
||||||
|
}
|
@ -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<WorkflowDetailComponent>;
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
@ -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<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<DialogResult> {
|
||||||
|
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<StatePerm> {
|
||||||
|
const dataSource = new MatTableDataSource<StatePerm>();
|
||||||
|
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 '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
<!-- <os-head-bar [mainButton]=true (mainEvent)="onPlusButton()" [multiSelectMode]="isMultiSelect"> -->
|
||||||
|
<os-head-bar [nav]="false" [mainButton]="true" (mainEvent)="onNewButton(newWorkflowDialog)">
|
||||||
|
<!-- Title -->
|
||||||
|
<div class="title-slot"><h2 translate>Workflows</h2></div>
|
||||||
|
</os-head-bar>
|
||||||
|
|
||||||
|
<mat-table class="os-headed-listview-table on-transition-fade" [dataSource]="dataSource">
|
||||||
|
<!-- Name Column -->
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<mat-header-cell *matHeaderCellDef>
|
||||||
|
<span translate>Name</span>
|
||||||
|
</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let workflow" (click)="onClickWorkflow(workflow)">
|
||||||
|
{{ workflow }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Delete Column -->
|
||||||
|
<ng-container matColumnDef="delete">
|
||||||
|
<mat-header-cell *matHeaderCellDef> </mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let workflow">
|
||||||
|
<button type="button" mat-icon-button (click)="onDeleteWorkflow(workflow)">
|
||||||
|
<mat-icon color="warn">delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
|
||||||
|
<mat-row *matRowDef="let row; columns: getColumnDefinition()"></mat-row>
|
||||||
|
</mat-table>
|
||||||
|
|
||||||
|
<!-- New workflow dialog -->
|
||||||
|
<ng-template #newWorkflowDialog>
|
||||||
|
<h1 mat-dialog-title>
|
||||||
|
<span translate>Create new workflow</span>
|
||||||
|
</h1>
|
||||||
|
<div mat-dialog-content>
|
||||||
|
<p translate>Please enter a name for the new workflow:</p>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput osAutofocus [(ngModel)]="newWorkflowTitle" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button type="submit" mat-button color="primary" [mat-dialog-close]="newWorkflowTitle">
|
||||||
|
<span translate>Save</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" mat-button [mat-dialog-close]="null">
|
||||||
|
<span translate>Cancel</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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<WorkflowListComponent>;
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
@ -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<ViewWorkflow> 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<string>): 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<void> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { Workflow } from 'app/shared/models/motions/workflow';
|
import { Workflow } from 'app/shared/models/motions/workflow';
|
||||||
import { WorkflowState } from 'app/shared/models/motions/workflow-state';
|
import { WorkflowState } from 'app/shared/models/motions/workflow-state';
|
||||||
import { BaseViewModel } from '../../base/base-view-model';
|
import { BaseViewModel } from '../../base/base-view-model';
|
||||||
|
import { Deserializable } from 'app/shared/models/base/deserializable';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* class for the ViewWorkflow. Currently only a basic stub
|
* 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;
|
return this.workflow ? this.workflow.states : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get first_state(): number {
|
public get first_state_id(): number {
|
||||||
return this.workflow ? this.workflow.first_state : null;
|
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 {
|
public getTitle(): string {
|
||||||
@ -50,9 +59,12 @@ export class ViewWorkflow extends BaseViewModel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the local objects if required
|
* Updates the local objects if required
|
||||||
|
*
|
||||||
* @param update
|
* @param update
|
||||||
*/
|
*/
|
||||||
public updateValues(update: Workflow): void {
|
public updateValues(update: Deserializable): void {
|
||||||
|
if (update instanceof Workflow) {
|
||||||
this._workflow = update;
|
this._workflow = update;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ import { MotionListComponent } from './components/motion-list/motion-list.compon
|
|||||||
import { SpeakerListComponent } from '../agenda/components/speaker-list/speaker-list.component';
|
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 { 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 { 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 = [
|
const routes: Routes = [
|
||||||
{ path: '', component: MotionListComponent },
|
{ path: '', component: MotionListComponent },
|
||||||
@ -25,6 +27,8 @@ const routes: Routes = [
|
|||||||
{ path: 'blocks/:id', component: MotionBlockDetailComponent },
|
{ path: 'blocks/:id', component: MotionBlockDetailComponent },
|
||||||
{ path: 'new', component: MotionDetailComponent },
|
{ path: 'new', component: MotionDetailComponent },
|
||||||
{ path: 'import', component: MotionImportListComponent },
|
{ path: 'import', component: MotionImportListComponent },
|
||||||
|
{ path: 'workflow', component: WorkflowListComponent },
|
||||||
|
{ path: 'workflow/:id', component: WorkflowDetailComponent },
|
||||||
{ path: ':id', component: MotionDetailComponent },
|
{ path: ':id', component: MotionDetailComponent },
|
||||||
{ path: ':id/speakers', component: SpeakerListComponent },
|
{ path: ':id/speakers', component: SpeakerListComponent },
|
||||||
{ path: ':id/create-amendment', component: AmendmentCreateWizardComponent }
|
{ path: ':id/create-amendment', component: AmendmentCreateWizardComponent }
|
||||||
|
@ -23,6 +23,8 @@ import { MotionPollComponent } from './components/motion-poll/motion-poll.compon
|
|||||||
import { MotionPollDialogComponent } from './components/motion-poll/motion-poll-dialog.component';
|
import { MotionPollDialogComponent } from './components/motion-poll/motion-poll-dialog.component';
|
||||||
import { MotionExportDialogComponent } from './components/motion-export-dialog/motion-export-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 { 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({
|
@NgModule({
|
||||||
imports: [CommonModule, MotionsRoutingModule, SharedModule],
|
imports: [CommonModule, MotionsRoutingModule, SharedModule],
|
||||||
@ -46,7 +48,9 @@ import { StatuteImportListComponent } from './components/statute-paragraph-list/
|
|||||||
MotionPollComponent,
|
MotionPollComponent,
|
||||||
MotionPollDialogComponent,
|
MotionPollDialogComponent,
|
||||||
MotionExportDialogComponent,
|
MotionExportDialogComponent,
|
||||||
StatuteImportListComponent
|
StatuteImportListComponent,
|
||||||
|
WorkflowListComponent,
|
||||||
|
WorkflowDetailComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
MotionChangeRecommendationComponent,
|
MotionChangeRecommendationComponent,
|
||||||
|
Loading…
Reference in New Issue
Block a user