mirror of
https://github.com/sartography/cr-connect-bpmn.git
synced 2025-01-12 01:54:51 +00:00
Merge pull request #9 from sartography/feature/activate-workflow-specs
Feature/activate workflow specs
This commit is contained in:
commit
c0c7b48c57
6
package-lock.json
generated
6
package-lock.json
generated
@ -12171,9 +12171,9 @@
|
|||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
},
|
},
|
||||||
"sartography-workflow-lib": {
|
"sartography-workflow-lib": {
|
||||||
"version": "0.0.72",
|
"version": "0.0.74",
|
||||||
"resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.72.tgz",
|
"resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.74.tgz",
|
||||||
"integrity": "sha512-l6153jMOkKPgi2dOODOdTSHdPyyZpYjuylkPt38ct4XYaaF+kiT3GadhZli3sxg8KRkYfiaJ5krZRgl3adNu7A=="
|
"integrity": "sha512-v8Kz89ta85O1nO7ICtJnok0LbxwEofwcqG2eivmo78xR4qYGisUl68GUTYOHdctAQwNdNbyUW7DxkqpkaPb8ew=="
|
||||||
},
|
},
|
||||||
"sass": {
|
"sass": {
|
||||||
"version": "1.23.3",
|
"version": "1.23.3",
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
"ngx-file-drop": "^8.0.8",
|
"ngx-file-drop": "^8.0.8",
|
||||||
"ngx-markdown": "^9.0.0",
|
"ngx-markdown": "^9.0.0",
|
||||||
"rxjs": "~6.5.4",
|
"rxjs": "~6.5.4",
|
||||||
"sartography-workflow-lib": "^0.0.72",
|
"sartography-workflow-lib": "^0.0.74",
|
||||||
"tslib": "^1.11.1",
|
"tslib": "^1.11.1",
|
||||||
"uuid": "^7.0.2",
|
"uuid": "^7.0.2",
|
||||||
"zone.js": "^0.10.3"
|
"zone.js": "^0.10.3"
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
<div mat-dialog-content>
|
<div mat-dialog-content>
|
||||||
<form [formGroup]="form">
|
<form [formGroup]="form">
|
||||||
<formly-form [model]="model" [fields]="fields" [options]="options" [form]="form"></formly-form>
|
<formly-form
|
||||||
|
[model]="model"
|
||||||
|
[fields]="fields"
|
||||||
|
[options]="options"
|
||||||
|
[form]="form"
|
||||||
|
(modelChange)="onModelChange($event)"
|
||||||
|
></formly-form>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {Component, Inject} from '@angular/core';
|
import {Component, Inject} from '@angular/core';
|
||||||
import {FormGroup} from '@angular/forms';
|
import {FormControl, FormGroup} from '@angular/forms';
|
||||||
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
|
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
|
||||||
import {FormlyFieldConfig, FormlyFormOptions} from '@ngx-formly/core';
|
import {FormlyFieldConfig, FormlyFormOptions} from '@ngx-formly/core';
|
||||||
import {cleanUpFilename, FileType} from 'sartography-workflow-lib';
|
import {cleanUpFilename, FileType} from 'sartography-workflow-lib';
|
||||||
@ -51,15 +51,18 @@ export class FileMetaDialogComponent {
|
|||||||
options: fileTypeOptions,
|
options: fileTypeOptions,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// key: 'file',
|
key: 'file',
|
||||||
// type: 'file',
|
type: 'file',
|
||||||
// defaultValue: this.data.file,
|
defaultValue: this.data.file,
|
||||||
// templateOptions: {
|
templateOptions: {
|
||||||
// label: 'File',
|
label: 'File',
|
||||||
// required: true,
|
required: true,
|
||||||
// },
|
},
|
||||||
// }
|
modelOptions: {
|
||||||
|
updateOn: 'change'
|
||||||
|
},
|
||||||
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,4 +75,16 @@ export class FileMetaDialogComponent {
|
|||||||
this.dialogRef.close(this.model);
|
this.dialogRef.close(this.model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onModelChange(model: any) {
|
||||||
|
console.log('model', model);
|
||||||
|
if (model.file && typeof model.file === 'object' && model.file instanceof Blob) {
|
||||||
|
// Upload file
|
||||||
|
const fileReader = new (window as any).FileReader();
|
||||||
|
fileReader.onload = (event: ProgressEvent) => {
|
||||||
|
const stringContent = (event.target as FileReader).result.toString();
|
||||||
|
console.log(stringContent);
|
||||||
|
};
|
||||||
|
fileReader.readAsText(model.file);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,11 +202,15 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
|
|||||||
|
|
||||||
this.modeler.on('commandStack.changed', () => this.saveDiagram());
|
this.modeler.on('commandStack.changed', () => this.saveDiagram());
|
||||||
|
|
||||||
this.modeler.on('views.changed', event => this.saveDiagram());
|
this.modeler.on('views.changed', () => {
|
||||||
|
this.modeler.getActiveViewer().get('eventBus').on('commandStack.changed', () => this.saveDiagram());
|
||||||
|
this.saveDiagram();
|
||||||
|
});
|
||||||
|
|
||||||
this.modeler.on('import.done', ({error}) => {
|
this.modeler.on('import.done', ({error}) => {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
const activeView = this.modeler.getActiveView();
|
const activeView = this.modeler.getActiveView();
|
||||||
|
|
||||||
if (activeView.type === 'drd') {
|
if (activeView.type === 'drd') {
|
||||||
this.modeler.getActiveViewer().get('canvas').zoom('fit-viewport');
|
this.modeler.getActiveViewer().get('canvas').zoom('fit-viewport');
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import {of} from 'rxjs';
|
|||||||
import {
|
import {
|
||||||
ApiService,
|
ApiService,
|
||||||
FileMeta,
|
FileMeta,
|
||||||
|
FileType,
|
||||||
MockEnvironment,
|
MockEnvironment,
|
||||||
mockFileMeta0,
|
mockFileMeta0,
|
||||||
mockFileMetas,
|
mockFileMetas,
|
||||||
@ -20,6 +21,8 @@ import {DeleteFileDialogComponent} from '../_dialogs/delete-file-dialog/delete-f
|
|||||||
import {DeleteFileDialogData} from '../_interfaces/dialog-data';
|
import {DeleteFileDialogData} from '../_interfaces/dialog-data';
|
||||||
import {GetIconCodePipe} from '../_pipes/get-icon-code.pipe';
|
import {GetIconCodePipe} from '../_pipes/get-icon-code.pipe';
|
||||||
import {FileListComponent} from './file-list.component';
|
import {FileListComponent} from './file-list.component';
|
||||||
|
import createClone from 'rfdc';
|
||||||
|
|
||||||
|
|
||||||
describe('FileListComponent', () => {
|
describe('FileListComponent', () => {
|
||||||
let httpMock: HttpTestingController;
|
let httpMock: HttpTestingController;
|
||||||
@ -146,11 +149,58 @@ describe('FileListComponent', () => {
|
|||||||
expect(loadFileMetasSpy).toHaveBeenCalled();
|
expect(loadFileMetasSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should navigate to modeler to edit a file', () => {
|
it('should navigate to modeler to edit a BPMN or DMN file', () => {
|
||||||
const routerNavigateSpy = spyOn((component as any).router, 'navigate');
|
const routerNavigateSpy = spyOn((component as any).router, 'navigate');
|
||||||
component.workflowSpec = mockWorkflowSpec0;
|
component.workflowSpec = mockWorkflowSpec0;
|
||||||
component.editFile(mockFileMeta0);
|
component.editFile(mockFileMeta0);
|
||||||
expect(routerNavigateSpy).toHaveBeenCalledWith([`/modeler/${mockWorkflowSpec0.id}/${mockFileMeta0.id}`]);
|
expect(routerNavigateSpy).toHaveBeenCalledWith([`/modeler/${mockWorkflowSpec0.id}/${mockFileMeta0.id}`]);
|
||||||
|
|
||||||
|
routerNavigateSpy.calls.reset();
|
||||||
|
const mockDmnMeta = createClone()(mockFileMeta0);
|
||||||
|
mockDmnMeta.type = FileType.DMN;
|
||||||
|
component.editFile(mockDmnMeta);
|
||||||
|
expect(routerNavigateSpy).toHaveBeenCalledWith([`/modeler/${mockWorkflowSpec0.id}/${mockDmnMeta.id}`]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open file metadata dialog for non-BPMN files', () => {
|
||||||
|
const routerNavigateSpy = spyOn((component as any).router, 'navigate');
|
||||||
|
const editFileMetaSpy = spyOn(component, 'editFileMeta');
|
||||||
|
component.workflowSpec = mockWorkflowSpec0;
|
||||||
|
const mockDocMeta = createClone()(mockFileMeta0);
|
||||||
|
mockDocMeta.type = FileType.DOCX;
|
||||||
|
component.editFile(mockDocMeta);
|
||||||
|
expect(routerNavigateSpy).not.toHaveBeenCalled();
|
||||||
|
expect(editFileMetaSpy).toHaveBeenCalledWith(mockDocMeta);
|
||||||
|
|
||||||
|
routerNavigateSpy.calls.reset();
|
||||||
|
editFileMetaSpy.calls.reset();
|
||||||
|
component.editFile(null);
|
||||||
|
expect(routerNavigateSpy).not.toHaveBeenCalled();
|
||||||
|
expect(editFileMetaSpy).toHaveBeenCalledWith(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change route and then open file metadata dialog', () => {
|
||||||
|
const routerNavigateSpy = spyOn((component as any).router, 'navigate')
|
||||||
|
.and.returnValue({finally: finallyCallback => finallyCallback()});
|
||||||
|
const _openFileDialogSpy = spyOn((component as any), '_openFileDialog').and.stub();
|
||||||
|
component.workflowSpec = mockWorkflowSpec0;
|
||||||
|
const mockDocMeta = createClone()(mockFileMeta0);
|
||||||
|
mockDocMeta.type = FileType.DOCX;
|
||||||
|
component.editFileMeta(mockDocMeta);
|
||||||
|
expect(routerNavigateSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
const fakeBlob = new Blob(['I am a fake blob. A real blob says "blorp blorp blorp."']);
|
||||||
|
const fReq = httpMock.expectOne(`apiRoot/file/${mockDocMeta.id}/data`);
|
||||||
|
expect(fReq.request.method).toEqual('GET');
|
||||||
|
fReq.flush(fakeBlob);
|
||||||
|
expect(_openFileDialogSpy).toHaveBeenCalledWith(mockDocMeta, fakeBlob);
|
||||||
|
|
||||||
|
routerNavigateSpy.calls.reset();
|
||||||
|
_openFileDialogSpy.calls.reset();
|
||||||
|
|
||||||
|
component.editFileMeta(null);
|
||||||
|
expect(routerNavigateSpy).toHaveBeenCalled();
|
||||||
|
expect(_openFileDialogSpy).toHaveBeenCalledWith();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should flag a file as primary', () => {
|
it('should flag a file as primary', () => {
|
||||||
|
@ -283,7 +283,6 @@ describe('ModelerComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should save file changes', () => {
|
it('should save file changes', () => {
|
||||||
const updateFileMetaSpy = spyOn(component.api, 'updateFileMeta').and.returnValue(of(mockFileMeta0));
|
|
||||||
const updateFileDataSpy = spyOn(component.api, 'updateFileData').and.returnValue(of(mockFileMeta0));
|
const updateFileDataSpy = spyOn(component.api, 'updateFileData').and.returnValue(of(mockFileMeta0));
|
||||||
const snackBarOpenSpy = spyOn(component.snackBar, 'open').and.stub();
|
const snackBarOpenSpy = spyOn(component.snackBar, 'open').and.stub();
|
||||||
|
|
||||||
@ -292,7 +291,6 @@ describe('ModelerComponent', () => {
|
|||||||
component.diagramComponent.writeValue(BPMN_DIAGRAM_EMPTY.replace(/REPLACE_ME/g, 'cream_colored_ponies'));
|
component.diagramComponent.writeValue(BPMN_DIAGRAM_EMPTY.replace(/REPLACE_ME/g, 'cream_colored_ponies'));
|
||||||
component.saveFileChanges();
|
component.saveFileChanges();
|
||||||
|
|
||||||
expect(updateFileMetaSpy).toHaveBeenCalledWith(mockFileMeta0);
|
|
||||||
expect(updateFileDataSpy).toHaveBeenCalledWith(mockFileMeta0);
|
expect(updateFileDataSpy).toHaveBeenCalledWith(mockFileMeta0);
|
||||||
expect(snackBarOpenSpy).toHaveBeenCalled();
|
expect(snackBarOpenSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
@ -295,11 +295,10 @@ export class ModelerComponent implements AfterViewInit {
|
|||||||
this.xml = this.draftXml;
|
this.xml = this.draftXml;
|
||||||
this.diagramFileMeta.file = new File([this.xml], this.diagramFileMeta.name, {type: 'text/xml'});
|
this.diagramFileMeta.file = new File([this.xml], this.diagramFileMeta.name, {type: 'text/xml'});
|
||||||
|
|
||||||
this.api.updateFileData(this.diagramFileMeta).subscribe(() => {
|
this.api.updateFileData(this.diagramFileMeta).subscribe(newFileMeta => {
|
||||||
this.api.updateFileMeta(this.diagramFileMeta).subscribe(() => {
|
this.diagramFileMeta = newFileMeta;
|
||||||
this.snackBar.open(`Saved changes to file metadata ${this.diagramFileMeta.name}.`, 'Ok', {duration: 5000});
|
this.snackBar.open(`Saved changes to file metadata ${this.diagramFileMeta.name}.`, 'Ok', {duration: 5000});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleAction(q: Params) {
|
private _handleAction(q: Params) {
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
<mat-card class="mat-elevation-z0" [id]="workflowSpec.id">
|
<mat-card class="mat-elevation-z0 {{workflowSpec.is_status ? 'master-status' : ''}}" [id]="workflowSpec.id">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="10px">
|
<mat-card-title fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="10px">
|
||||||
<h3>{{workflowSpec.display_name}}</h3>
|
<h3>{{workflowSpec.display_name}}</h3>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<ng-container *ngTemplateOutlet="actionButtons"></ng-container>
|
<ng-container *ngTemplateOutlet="actionButtons"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
<span fxFlex></span>
|
||||||
|
<ng-container>
|
||||||
|
<button (click)="makeMasterStatus()" *ngIf="!workflowSpec.is_status" mat-button color="accent">
|
||||||
|
<mat-icon>radio_button_unchecked</mat-icon>
|
||||||
|
Master status spec
|
||||||
|
</button>
|
||||||
|
<button *ngIf="workflowSpec.is_status" (click)="makeMasterStatus()" mat-button color="accent">
|
||||||
|
<mat-icon>radio_button_checked</mat-icon>
|
||||||
|
Master status spec
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
</mat-card-title>
|
</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
|
@import "../../config";
|
||||||
|
|
||||||
mat-card {
|
mat-card {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
border: 1px solid #CCCCCC;
|
border: 1px solid $brand-gray;
|
||||||
|
|
||||||
|
&.master-status {
|
||||||
|
border: 2px dashed $brand-accent;
|
||||||
|
}
|
||||||
|
|
||||||
mat-card-title {
|
mat-card-title {
|
||||||
h3 {
|
h3 {
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import {Component, Input, OnInit, TemplateRef} from '@angular/core';
|
import {Component, EventEmitter, Input, OnInit, Output, TemplateRef} from '@angular/core';
|
||||||
import {MatDialog} from '@angular/material/dialog';
|
|
||||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
|
||||||
import {ApiService, WorkflowSpec} from 'sartography-workflow-lib';
|
import {ApiService, WorkflowSpec} from 'sartography-workflow-lib';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -11,15 +9,20 @@ import {ApiService, WorkflowSpec} from 'sartography-workflow-lib';
|
|||||||
export class WorkflowSpecCardComponent implements OnInit {
|
export class WorkflowSpecCardComponent implements OnInit {
|
||||||
@Input() workflowSpec: WorkflowSpec;
|
@Input() workflowSpec: WorkflowSpec;
|
||||||
@Input() actionButtons: TemplateRef<any>;
|
@Input() actionButtons: TemplateRef<any>;
|
||||||
|
@Output() workflowUpdated: EventEmitter<WorkflowSpec> = new EventEmitter();
|
||||||
|
|
||||||
constructor() {
|
constructor(
|
||||||
|
private api: ApiService
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
openFileDialog() {
|
makeMasterStatus() {
|
||||||
|
this.workflowSpec.is_status = true;
|
||||||
|
this.api.updateWorkflowSpecification(this.workflowSpec.id, this.workflowSpec).subscribe(spec => {
|
||||||
|
this.workflowUpdated.emit(spec);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngFor="let wfs of cat.workflow_specs" class="workflow-spec">
|
<div *ngFor="let wfs of cat.workflow_specs" class="workflow-spec">
|
||||||
<app-workflow-spec-card [workflowSpec]="wfs" [actionButtons]="actionButtons">
|
<app-workflow-spec-card
|
||||||
|
[workflowSpec]="wfs"
|
||||||
|
[actionButtons]="actionButtons"
|
||||||
|
(workflowUpdated)="onWorkflowUpdated($event)">
|
||||||
</app-workflow-spec-card>
|
</app-workflow-spec-card>
|
||||||
<ng-template #actionButtons>
|
<ng-template #actionButtons>
|
||||||
<div class="workflow-spec-actions">
|
<div class="workflow-spec-actions">
|
||||||
|
@ -233,5 +233,23 @@ export class WorkflowSpecListComponent implements OnInit {
|
|||||||
this.snackBar.open(message, 'Ok', {duration: 3000});
|
this.snackBar.open(message, 'Ok', {duration: 3000});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onWorkflowUpdated(spec: WorkflowSpec) {
|
||||||
|
if (spec.is_status) {
|
||||||
|
// Mark all other specs as not is_status
|
||||||
|
let numUpdated = this.workflowSpecs.length - 1;
|
||||||
|
this.workflowSpecs.forEach(wfs => {
|
||||||
|
if (wfs.id !== spec.id) {
|
||||||
|
wfs.is_status = false;
|
||||||
|
this.api.updateWorkflowSpecification(wfs.id, wfs).subscribe(() => {
|
||||||
|
numUpdated--;
|
||||||
|
if (numUpdated === 0) {
|
||||||
|
this._loadWorkflowSpecCategories();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._loadWorkflowSpecCategories();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user