Merge pull request #9 from sartography/feature/activate-workflow-specs

Feature/activate workflow specs
This commit is contained in:
Aaron Louie 2020-03-25 09:25:59 -04:00 committed by GitHub
commit c0c7b48c57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 146 additions and 33 deletions

6
package-lock.json generated
View File

@ -12171,9 +12171,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sartography-workflow-lib": {
"version": "0.0.72",
"resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.72.tgz",
"integrity": "sha512-l6153jMOkKPgi2dOODOdTSHdPyyZpYjuylkPt38ct4XYaaF+kiT3GadhZli3sxg8KRkYfiaJ5krZRgl3adNu7A=="
"version": "0.0.74",
"resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.74.tgz",
"integrity": "sha512-v8Kz89ta85O1nO7ICtJnok0LbxwEofwcqG2eivmo78xR4qYGisUl68GUTYOHdctAQwNdNbyUW7DxkqpkaPb8ew=="
},
"sass": {
"version": "1.23.3",

View File

@ -49,7 +49,7 @@
"ngx-file-drop": "^8.0.8",
"ngx-markdown": "^9.0.0",
"rxjs": "~6.5.4",
"sartography-workflow-lib": "^0.0.72",
"sartography-workflow-lib": "^0.0.74",
"tslib": "^1.11.1",
"uuid": "^7.0.2",
"zone.js": "^0.10.3"

View File

@ -1,6 +1,12 @@
<div mat-dialog-content>
<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>
</div>

View File

@ -1,5 +1,5 @@
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 {FormlyFieldConfig, FormlyFormOptions} from '@ngx-formly/core';
import {cleanUpFilename, FileType} from 'sartography-workflow-lib';
@ -51,15 +51,18 @@ export class FileMetaDialogComponent {
options: fileTypeOptions,
},
},
// {
// key: 'file',
// type: 'file',
// defaultValue: this.data.file,
// templateOptions: {
// label: 'File',
// required: true,
// },
// }
{
key: 'file',
type: 'file',
defaultValue: this.data.file,
templateOptions: {
label: 'File',
required: true,
},
modelOptions: {
updateOn: 'change'
},
}
];
}
@ -72,4 +75,16 @@ export class FileMetaDialogComponent {
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);
}
}
}

View File

@ -202,11 +202,15 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
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}) => {
if (!error) {
const activeView = this.modeler.getActiveView();
if (activeView.type === 'drd') {
this.modeler.getActiveViewer().get('canvas').zoom('fit-viewport');
}

View File

@ -11,6 +11,7 @@ import {of} from 'rxjs';
import {
ApiService,
FileMeta,
FileType,
MockEnvironment,
mockFileMeta0,
mockFileMetas,
@ -20,6 +21,8 @@ import {DeleteFileDialogComponent} from '../_dialogs/delete-file-dialog/delete-f
import {DeleteFileDialogData} from '../_interfaces/dialog-data';
import {GetIconCodePipe} from '../_pipes/get-icon-code.pipe';
import {FileListComponent} from './file-list.component';
import createClone from 'rfdc';
describe('FileListComponent', () => {
let httpMock: HttpTestingController;
@ -146,11 +149,58 @@ describe('FileListComponent', () => {
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');
component.workflowSpec = mockWorkflowSpec0;
component.editFile(mockFileMeta0);
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', () => {

View File

@ -283,7 +283,6 @@ describe('ModelerComponent', () => {
});
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 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.saveFileChanges();
expect(updateFileMetaSpy).toHaveBeenCalledWith(mockFileMeta0);
expect(updateFileDataSpy).toHaveBeenCalledWith(mockFileMeta0);
expect(snackBarOpenSpy).toHaveBeenCalled();
});

View File

@ -295,10 +295,9 @@ export class ModelerComponent implements AfterViewInit {
this.xml = this.draftXml;
this.diagramFileMeta.file = new File([this.xml], this.diagramFileMeta.name, {type: 'text/xml'});
this.api.updateFileData(this.diagramFileMeta).subscribe(() => {
this.api.updateFileMeta(this.diagramFileMeta).subscribe(() => {
this.snackBar.open(`Saved changes to file metadata ${this.diagramFileMeta.name}.`, 'Ok', {duration: 5000});
});
this.api.updateFileData(this.diagramFileMeta).subscribe(newFileMeta => {
this.diagramFileMeta = newFileMeta;
this.snackBar.open(`Saved changes to file metadata ${this.diagramFileMeta.name}.`, 'Ok', {duration: 5000});
});
}

View File

@ -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-title fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="10px">
<h3>{{workflowSpec.display_name}}</h3>
<div class="action-buttons">
<ng-container *ngTemplateOutlet="actionButtons"></ng-container>
</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-header>
<mat-card-content>

View File

@ -1,6 +1,12 @@
@import "../../config";
mat-card {
margin-bottom: 1em;
border: 1px solid #CCCCCC;
border: 1px solid $brand-gray;
&.master-status {
border: 2px dashed $brand-accent;
}
mat-card-title {
h3 {

View File

@ -1,6 +1,4 @@
import {Component, Input, OnInit, TemplateRef} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {MatSnackBar} from '@angular/material/snack-bar';
import {Component, EventEmitter, Input, OnInit, Output, TemplateRef} from '@angular/core';
import {ApiService, WorkflowSpec} from 'sartography-workflow-lib';
@Component({
@ -11,15 +9,20 @@ import {ApiService, WorkflowSpec} from 'sartography-workflow-lib';
export class WorkflowSpecCardComponent implements OnInit {
@Input() workflowSpec: WorkflowSpec;
@Input() actionButtons: TemplateRef<any>;
@Output() workflowUpdated: EventEmitter<WorkflowSpec> = new EventEmitter();
constructor() {
constructor(
private api: ApiService
) {
}
ngOnInit(): void {
}
openFileDialog() {
makeMasterStatus() {
this.workflowSpec.is_status = true;
this.api.updateWorkflowSpecification(this.workflowSpec.id, this.workflowSpec).subscribe(spec => {
this.workflowUpdated.emit(spec);
});
}
}

View File

@ -25,7 +25,10 @@
</div>
</div>
<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>
<ng-template #actionButtons>
<div class="workflow-spec-actions">

View File

@ -233,5 +233,23 @@ export class WorkflowSpecListComponent implements OnInit {
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();
}
}