Fixes various bugs with opening and saving files. Saves files to the selected workflow spec. Displays selected workflow spec in toolbar.

This commit is contained in:
Aaron Louie 2020-01-28 11:00:25 -05:00
parent 7142069b0f
commit a01a6594a2
12 changed files with 144 additions and 160 deletions

View File

@ -1,7 +1,6 @@
import {FileType} from 'sartography-workflow-lib';
export interface FileMetaDialogData {
fileName: string;
workflowSpecId: string;
name: string;
displayName: string;
description: string;
fileType: FileType;
}

View File

@ -1,3 +1,5 @@
import {FileType} from 'sartography-workflow-lib';
export const trimString = (str: string): string => {
return !str ? '' : String(str).replace(/^\W+|\W+$/gi, '');
};
@ -9,12 +11,12 @@ export const toSnakeCase = (str: string): string => {
.toLowerCase();
};
export const cleanUpFilename = (str: string, extension: string): string => {
export const cleanUpFilename = (str: string, extension: FileType|string): string => {
const arr = trimString(str).split('.');
// Add file extension, if necessary
if (arr.length < 2) {
arr.push('bpmn');
arr.push(extension);
} else {
(arr[arr.length - 1]) = extension;
}

View File

@ -3,7 +3,7 @@ import {DebugNode} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MatIconModule} from '@angular/material/icon';
import * as FileSaver from 'file-saver';
import {ApiService, FileType, MockEnvironment} from 'sartography-workflow-lib';
import {ApiService, BPMN_DIAGRAM_DEFAULT, FileType, MockEnvironment} from 'sartography-workflow-lib';
import {
BPMN_DIAGRAM,
BPMN_DIAGRAM_WITH_WARNINGS,
@ -134,10 +134,11 @@ describe('DiagramComponent', () => {
it('should create a new diagram', () => {
const initializeModelerSpy = spyOn(component, 'initializeModeler').and.stub();
const createDiagramSpy = spyOn(component.modeler, 'createDiagram').and.stub();
const importXMLSpy = spyOn(component.modeler, 'importXML').and.stub();
spyOn(component, 'getRandomString').and.returnValue('REPLACE_ME');
component.openDiagram();
expect(initializeModelerSpy).toHaveBeenCalledWith(undefined);
expect(createDiagramSpy).toHaveBeenCalled();
expect(importXMLSpy).toHaveBeenCalledWith(BPMN_DIAGRAM_DEFAULT, jasmine.any(Function));
});
it('should open an existing BPMN diagram from XML', () => {
@ -177,10 +178,11 @@ describe('DiagramComponent', () => {
it('should edit diagram', () => {
const initializeModelerSpy = spyOn(component, 'initializeModeler').and.stub();
const onChangeSpy = spyOn(component, 'onChange').and.stub();
const createDiagramSpy = spyOn(component.modeler, 'createDiagram').and.stub();
const importXMLSpy = spyOn(component.modeler, 'importXML').and.stub();
spyOn(component, 'getRandomString').and.returnValue('REPLACE_ME');
component.openDiagram();
expect(initializeModelerSpy).toHaveBeenCalledWith(undefined);
expect(createDiagramSpy).toHaveBeenCalled();
expect(importXMLSpy).toHaveBeenCalledWith(BPMN_DIAGRAM_DEFAULT, jasmine.any(Function));
component.writeValue(BPMN_DIAGRAM);
expect(initializeModelerSpy).toHaveBeenCalledWith(undefined);

View File

@ -4,14 +4,13 @@ import {ControlValueAccessor} from '@angular/forms';
import BpmnModeler from 'bpmn-js/lib/Modeler';
import DmnModeler from 'dmn-js/lib/Modeler';
import * as fileSaver from 'file-saver';
import {ApiService, FileType} from 'sartography-workflow-lib';
import {DMN_DIAGRAM_EMPTY} from '../../testing/mocks/diagram.mocks';
import {ApiService, BPMN_DIAGRAM_DEFAULT, DMN_DIAGRAM_DEFAULT, FileType} from 'sartography-workflow-lib';
import {v4 as uuidv4} from 'uuid';
import {BpmnWarning} from '../_interfaces/bpmn-warning';
import {ImportEvent} from '../_interfaces/import-event';
import {getDiagramTypeFromXml} from '../_util/diagram-type';
import {bpmnModelerConfig} from './bpmn-modeler-config';
import {dmnModelerConfig} from './dmn-modeler-config';
import {v4 as uuidv4} from 'uuid';
@Component({
selector: 'app-diagram',
@ -89,18 +88,14 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
this.diagramType = diagramType || getDiagramTypeFromXml(xml);
this.xml = xml;
this.initializeModeler(diagramType);
return this.zone.run(() => {
if (xml) {
this.modeler.importXML(xml, (e, w) => this.onImport(e, w));
} else {
if (this.modeler.createDiagram) {
this.modeler.createDiagram((e, w) => this.onImport(e, w));
} else {
const r = 'REPLACE_ME';
const newXml = DMN_DIAGRAM_EMPTY.replace(/REPLACE_ME/gi, () => uuidv4().slice(0, 7));
this.modeler.importXML(newXml, (e, w) => this.onImport(e, w));
}
if (!xml) {
const defaultXml = diagramType === FileType.DMN ? DMN_DIAGRAM_DEFAULT : BPMN_DIAGRAM_DEFAULT;
xml = defaultXml.replace(/REPLACE_ME/gi, () => this.getRandomString(7));
}
this.modeler.importXML(xml, (e, w) => this.onImport(e, w));
});
}
@ -174,6 +169,11 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
this.modeler.on('import.done', ({error}) => {
if (!error) {
this.modeler.get('canvas').zoom('fit-viewport');
window.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
}
});
}
@ -197,6 +197,11 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
this.modeler.on('import.done', ({error}) => {
if (!error) {
this.modeler.getActiveViewer().get('canvas').zoom('fit-viewport');
window.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
}
});
}
@ -207,4 +212,8 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
e.innerHTML = '';
});
}
private getRandomString(len: number): string {
return uuidv4().slice(0, len);
}
}

View File

@ -2,6 +2,7 @@ import {HttpClientTestingModule, HttpTestingController} from '@angular/common/ht
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MatIconModule} from '@angular/material/icon';
import {MatListModule} from '@angular/material/list';
import {RouterTestingModule} from '@angular/router/testing';
import {
ApiService,
FileType,
@ -10,6 +11,7 @@ import {
mockFileMetas,
mockWorkflowSpec0
} from 'sartography-workflow-lib';
import {GetIconCodePipe} from '../_pipes/get-icon-code.pipe';
import {FileListComponent} from './file-list.component';
describe('FileListComponent', () => {
@ -23,8 +25,10 @@ describe('FileListComponent', () => {
HttpClientTestingModule,
MatIconModule,
MatListModule,
RouterTestingModule,
],
declarations: [
GetIconCodePipe,
FileListComponent
],
providers: [

View File

@ -1,22 +1,13 @@
<div mat-dialog-content>
<mat-form-field>
<input disabled required [(ngModel)]="data.workflowSpecId" matInput placeholder="Workflow specification id">
</mat-form-field>
<mat-form-field>
<input required [(ngModel)]="data.name" matInput placeholder="Workflow specification name">
</mat-form-field>
<mat-form-field>
<input required [(ngModel)]="data.fileName" matInput placeholder="File name">
</mat-form-field>
<mat-form-field>
<input required [(ngModel)]="data.displayName" matInput placeholder="Display name">
</mat-form-field>
<mat-form-field>
<textarea required [(ngModel)]="data.description" matInput matTextareaAutosize placeholder="Description"></textarea>
<input disabled required [(ngModel)]="data.fileType" matInput placeholder="File type">
</mat-form-field>
</div>
<div mat-dialog-actions>
<button (click)="onSubmit()" color="primary" mat-flat-button>Save</button>
<button [disabled]="!data.fileName" (click)="onSubmit()" color="primary" mat-flat-button>Save</button>
<button (click)="onNoClick()" mat-flat-button>Cancel</button>
</div>

View File

@ -4,6 +4,7 @@ import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from '@angular/material/
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input';
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
import {FileType} from 'sartography-workflow-lib';
import {FileMetaDialogData} from '../_interfaces/file-meta-dialog-data';
import {FileMetaDialogComponent} from './file-meta-dialog.component';
@ -52,10 +53,7 @@ describe('EditFileMetaDialogComponent', () => {
const closeSpy = spyOn(component.dialogRef, 'close').and.stub();
const dataBefore: FileMetaDialogData = {
fileName: 'green_eggs.bpmn',
workflowSpecId: 'green_eggs',
name: 'green_eggs',
displayName: 'Green Eggs',
description: 'Eat them! Eat them! Here they are.',
fileType: FileType.BPMN,
};
component.data = dataBefore;
@ -67,10 +65,7 @@ describe('EditFileMetaDialogComponent', () => {
const closeSpy = spyOn(component.dialogRef, 'close').and.stub();
const dataBefore: FileMetaDialogData = {
fileName: 'and_ham.bpmn',
workflowSpecId: 'and_ham',
name: 'and_hame',
displayName: 'And Ham',
description: 'Would you, could you, in a box?',
fileType: FileType.BPMN,
};
component.data = dataBefore;
@ -82,10 +77,7 @@ describe('EditFileMetaDialogComponent', () => {
const closeSpy = spyOn(component.dialogRef, 'close').and.stub();
const dataBefore: FileMetaDialogData = {
fileName: ' 🍳 green_eggs.v1-2020-01-01.XML.bmnp 🍖 ',
workflowSpecId: 'green_eggs',
name: 'green_eggs',
displayName: 'Green Eggs',
description: 'Eat them! Eat them! Here they are.',
fileType: FileType.BPMN,
};
component.data = dataBefore;
@ -94,21 +86,4 @@ describe('EditFileMetaDialogComponent', () => {
expectedData.fileName = 'green_eggs.v1-2020-01-01.XML.bpmn';
expect(closeSpy).toHaveBeenCalledWith(expectedData);
});
it('should clean up workflow spec id', () => {
const closeSpy = spyOn(component.dialogRef, 'close').and.stub();
const dataBefore: FileMetaDialogData = {
fileName: 'green_eggs.bpmn',
workflowSpecId: ' 🍳 Green Eggs & Ham: A Dish Best Served Cold? 🍖 ',
name: 'green_eggs',
displayName: 'Green Eggs',
description: 'I would not, could not, with a fox!',
};
component.data = dataBefore;
component.onSubmit();
const expectedData: FileMetaDialogData = JSON.parse(JSON.stringify(dataBefore));
expectedData.workflowSpecId = 'green_eggs_ham_a_dish_best_served_cold';
expect(closeSpy).toHaveBeenCalledWith(dataBefore);
});
});

View File

@ -21,8 +21,7 @@ export class FileMetaDialogComponent {
}
onSubmit() {
this.data.workflowSpecId = toSnakeCase(this.data.workflowSpecId);
this.data.fileName = cleanUpFilename(this.data.fileName, 'bpmn');
this.data.fileName = cleanUpFilename(this.data.fileName, this.data.fileType);
this.dialogRef.close(this.data);
}

View File

@ -1,4 +1,12 @@
<mat-toolbar [ngClass]="{'expanded': expandToolbar}">
<mat-toolbar-row *ngIf="workflowSpec">
<button mat-button [routerLink]="['/']">
<mat-icon>arrow_back</mat-icon>
Back
</button>
{{workflowSpec.display_name}}
({{workflowSpec.name}})
</mat-toolbar-row>
<mat-toolbar-row>
<button mat-button [matMenuTriggerFor]="newMenu" title="Open diagram">
<mat-icon>insert_drive_file</mat-icon>
@ -58,9 +66,9 @@
<button mat-menu-item (click)="diagram.saveXML()"><mat-icon>code</mat-icon> Download XML File</button>
</mat-menu>
<button mat-button *ngIf="diagramFileMeta && diagramFileMeta.workflow_spec_id" (click)="editFileMeta()">
<button mat-button *ngIf="diagramFile" (click)="editFileMeta()">
<mat-icon>edit</mat-icon>
{{getFileMetaDisplayString(diagramFileMeta)}}
{{getFileName()}}
</button>
</mat-toolbar-row>
<mat-toolbar-row *ngIf="expandToolbar">
@ -82,7 +90,13 @@
<input name="diagramUrl" [(ngModel)]="diagramUrl" matInput placeholder="Open diagram from URL" type="text">
</mat-form-field>
</ng-container>
<button mat-flat-button (click)="onSubmitFileToOpen()" color="primary" id="open_file_button">Open file <mat-icon>arrow_forward</mat-icon></button>
<button
mat-flat-button
(click)="onSubmitFileToOpen()"
[disabled]="!diagramFile"
color="primary"
id="open_file_button"
>Open file <mat-icon>arrow_forward</mat-icon></button>
<span fxFlex></span>
<button mat-icon-button (click)="expandToolbar = false"><mat-icon>close</mat-icon></button>
</mat-toolbar-row>

View File

@ -22,11 +22,11 @@ mat-form-field {
mat-toolbar {
transition: all 200ms ease-in-out;
height: 64px;
height: 128px;
}
mat-toolbar.expanded {
height: 128px;
height: 192px;
}
::ng-deep .tooltip-text {

View File

@ -13,7 +13,8 @@ import {MatToolbarModule} from '@angular/material/toolbar';
import {MatTooltipModule} from '@angular/material/tooltip';
import {BrowserDynamicTestingModule} from '@angular/platform-browser-dynamic/testing';
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
import {of} from 'rxjs';
import {ActivatedRoute, convertToParamMap} from '@angular/router';
import {Observable, of} from 'rxjs';
import {
ApiService,
FileMeta,
@ -27,6 +28,7 @@ import {
import {BPMN_DIAGRAM, BPMN_DIAGRAM_WITH_WARNINGS} from '../../testing/mocks/diagram.mocks';
import {BpmnWarning} from '../_interfaces/bpmn-warning';
import {FileMetaDialogData} from '../_interfaces/file-meta-dialog-data';
import {GetIconCodePipe} from '../_pipes/get-icon-code.pipe';
import {DiagramComponent} from '../diagram/diagram.component';
import {FileMetaDialogComponent} from '../file-meta-dialog/file-meta-dialog.component';
import {ModelerComponent} from './modeler.component';
@ -40,6 +42,7 @@ describe('ModelerComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
GetIconCodePipe,
ModelerComponent,
DiagramComponent,
FileMetaDialogComponent,
@ -70,6 +73,12 @@ describe('ModelerComponent', () => {
}
},
{provide: MAT_DIALOG_DATA, useValue: []},
{provide: ActivatedRoute, useValue: {
paramMap: of(convertToParamMap({
workflowSpecId: mockWorkflowSpec0.id,
fileMetaId: `${mockFileMeta0.id}`
}))
}}
]
}).overrideModule(BrowserDynamicTestingModule, {set: {entryComponents: [FileMetaDialogComponent]}})
.compileComponents();
@ -198,7 +207,7 @@ describe('ModelerComponent', () => {
});
it('should get the diagram file name', () => {
expect(component.getFileName()).toEqual('No file selected');
expect(component.getFileName()).toEqual(mockFileMeta0.name);
const filename = 'expected_file_name.jpg';
component.diagramFile = new File([], filename, {type: 'image/jpeg'});
@ -261,10 +270,7 @@ describe('ModelerComponent', () => {
it('should open file metadata dialog', () => {
const data: FileMetaDialogData = {
fileName: 'after',
workflowSpecId: 'after',
name: 'after',
description: 'after',
displayName: 'after',
fileType: FileType.BPMN,
};
const upsertSpy = spyOn(component, '_upsertSpecAndFileMeta').and.stub();
@ -275,14 +281,11 @@ describe('ModelerComponent', () => {
expect(upsertSpy).toHaveBeenCalledWith(data);
});
it('should update spec and file metadata for existing file', () => {
it('should update file metadata for existing file', () => {
const newXml = '<xml>New Value</xml>';
const data: FileMetaDialogData = {
fileName: mockFileMeta0.name,
workflowSpecId: mockWorkflowSpec0.id,
name: mockWorkflowSpec0.name,
description: mockWorkflowSpec0.description,
displayName: mockWorkflowSpec0.display_name,
fileType: FileType.BPMN,
};
const updateWorkflowSpecificationSpy = spyOn(component.api, 'updateWorkflowSpecification')
.and.returnValue(of(mockWorkflowSpec0));
@ -310,14 +313,11 @@ describe('ModelerComponent', () => {
expect(snackBarSpy).toHaveBeenCalled();
});
it('should create new spec and file metadata for new file', () => {
it('should create new file metadata for new file', () => {
const newXml = '<xml>New Value</xml>';
const data: FileMetaDialogData = {
fileName: mockFileMeta0.name,
workflowSpecId: mockWorkflowSpec0.id,
name: mockWorkflowSpec0.id,
description: mockWorkflowSpec0.description,
displayName: mockWorkflowSpec0.display_name,
fileType: FileType.BPMN,
};
const noDateOrVersion: FileMeta = {

View File

@ -33,6 +33,9 @@ export class ModelerComponent implements AfterViewInit {
private xml = '';
private draftXml = '';
@ViewChild(DiagramComponent, {static: false}) private diagramComponent: DiagramComponent;
private diagramType: FileType;
private workflowSpecId: string;
private fileMetaId: number;
constructor(
private api: ApiService,
@ -40,7 +43,24 @@ export class ModelerComponent implements AfterViewInit {
public dialog: MatDialog,
private route: ActivatedRoute,
) {
this.loadFilesFromDb();
this.route.queryParams.subscribe(q => {
if (q && q.action) {
if (q.action === 'openFile') {
this.expandToolbar = true;
this.openMethod = 'file';
} else if (q.action === 'newFile') {
}
}
})
this.route.paramMap.subscribe(paramMap => {
console.log('paramMap', paramMap);
this.workflowSpecId = paramMap.get('workflowSpecId');
this.fileMetaId = parseInt(paramMap.get('fileMetaId'), 10);
this.loadFilesFromDb();
});
}
ngAfterViewInit(): void {
@ -89,7 +109,7 @@ export class ModelerComponent implements AfterViewInit {
}
getFileName() {
return this.diagramFile ? this.diagramFile.name : 'No file selected';
return this.diagramFile ? this.diagramFile.name : this.fileName || 'No file selected';
}
onFileSelected($event: Event) {
@ -99,9 +119,9 @@ export class ModelerComponent implements AfterViewInit {
// Arrow function here preserves this context
onLoad = (event: ProgressEvent) => {
this.xml = (event.target as FileReader).result.toString();
const diagramType = this.diagramFileMeta ? this.diagramFileMeta.type : getDiagramTypeFromXml(this.xml);
const diagramType = getDiagramTypeFromXml(this.xml);
this.diagramComponent.openDiagram(this.xml, diagramType);
};
}
readFile(file: File) {
// FileReader must be instantiated this way so unit test can spy on it.
@ -139,6 +159,7 @@ export class ModelerComponent implements AfterViewInit {
this.fileName = '';
this.diagramFileMeta = undefined;
this.diagramFile = undefined;
this.diagramType = diagramType;
this.diagramComponent.openDiagram(undefined, diagramType);
}
@ -148,16 +169,14 @@ export class ModelerComponent implements AfterViewInit {
height: '400px',
width: '400px',
data: {
fileName: this.diagramFileMeta ? this.diagramFileMeta.name : '',
workflowSpecId: this.diagramFileMeta ? this.diagramFileMeta.workflow_spec_id : '',
description: this.workflowSpec ? this.workflowSpec.description : '',
displayName: this.workflowSpec ? this.workflowSpec.display_name : '',
fileName: this.diagramFile ? this.diagramFile.name : this.fileName || '',
fileType: getDiagramTypeFromXml(this.xml),
},
});
dialogRef.afterClosed().subscribe((data: FileMetaDialogData) => {
if (data && data.fileName && data.workflowSpecId) {
this._upsertSpecAndFileMeta(data);
if (data && data.fileName) {
this._upsertFileMeta(data);
}
});
}
@ -167,12 +186,9 @@ export class ModelerComponent implements AfterViewInit {
}
getFileMetaDisplayString(fileMeta: FileMeta) {
const spec = this.getWorkflowSpec(fileMeta.workflow_spec_id);
if (spec) {
const specName = spec.id + ' - ' + spec.name + ' - ' + spec.display_name;
if (fileMeta) {
const lastUpdated = new DatePipe('en-us').transform(fileMeta.last_updated);
return `${specName} (${fileMeta.name}) - v${fileMeta.version} (${lastUpdated})`;
return `${fileMeta.name} - v${fileMeta.version} (${lastUpdated})`;
} else {
return 'Loading...';
}
@ -198,89 +214,62 @@ export class ModelerComponent implements AfterViewInit {
}
private loadFilesFromDb() {
this.route.paramMap.subscribe(paramMap => {
const workflowSpecId = paramMap.get('workflowSpecId');
const fileMetaId = parseInt(paramMap.get('fileMetaId'), 10);
console.log('workflowSpecId', workflowSpecId);
console.log('fileMetaId', fileMetaId);
this.api.getWorkflowSpecList().subscribe(wfs => {
this.api.getWorkflowSpecList().subscribe(wfs => {
this.workflowSpecs = wfs;
this.workflowSpecs.forEach(w => {
if (w.id === workflowSpecId) {
if (w.id === this.workflowSpecId) {
this.workflowSpec = w;
}
this.api.listBpmnFiles(w.id).subscribe(files => {
this.bpmnFiles = [];
files.forEach(f => {
this.api.getFileData(f.id).subscribe(d => {
if ((f.type === FileType.BPMN) || (f.type === FileType.DMN)) {
f.content_type = 'text/xml';
f.file = new File([d], f.name, {type: f.content_type});
this.bpmnFiles.push(f);
this.api.listBpmnFiles(w.id).subscribe(files => {
this.bpmnFiles = [];
files.forEach(f => {
this.api.getFileData(f.id).subscribe(d => {
if ((f.type === FileType.BPMN) || (f.type === FileType.DMN)) {
f.content_type = 'text/xml';
f.file = new File([d], f.name, {type: f.content_type});
this.bpmnFiles.push(f);
if (f.id === fileMetaId) {
this.diagramFileMeta = f;
this.diagramFile = f.file;
this.onSubmitFileToOpen();
if (f.id === this.fileMetaId) {
this.diagramFileMeta = f;
this.diagramFile = f.file;
this.onSubmitFileToOpen();
}
}
}
});
});
});
});
}
});
});
});
}
private _upsertSpecAndFileMeta(data: FileMetaDialogData) {
if (data.fileName && data.workflowSpecId) {
private _upsertFileMeta(data: FileMetaDialogData) {
if (data.fileName) {
this.xml = this.draftXml;
// Save old workflow spec id, if user wants to change it
const specId = this.workflowSpec ? this.workflowSpec.id : undefined;
this.workflowSpec = {
id: data.workflowSpecId,
name: data.name,
display_name: data.displayName,
description: data.description,
};
const fileMetaId = this.diagramFileMeta ? this.diagramFileMeta.id : undefined;
const fileType = this.diagramFileMeta ? this.diagramFileMeta.type : FileType.BPMN;
this.diagramFileMeta = {
id: fileMetaId,
content_type: 'text/xml',
name: data.fileName,
type: FileType.BPMN,
type: fileType,
file: new File([this.xml], data.fileName, {type: 'text/xml'}),
workflow_spec_id: data.workflowSpecId,
workflow_spec_id: this.workflowSpec.id,
};
const newSpec: WorkflowSpec = {
id: data.workflowSpecId,
name: data.name,
display_name: data.displayName,
description: data.description,
};
if (specId) {
// Update existing workflow spec and file
this.api.updateWorkflowSpecification(specId, newSpec).subscribe(spec => {
this.api.updateFileMeta(specId, this.diagramFileMeta).subscribe(fileMeta => {
this.loadFilesFromDb();
this.snackBar.open('Saved changes to workflow spec and file.', 'Ok', {duration: 5000});
});
if (specId && fileMetaId) {
// Update existing file meta
this.api.updateFileMeta(specId, this.diagramFileMeta).subscribe(fileMeta => {
this.loadFilesFromDb();
this.snackBar.open('Saved changes to workflow spec and file.', 'Ok', {duration: 5000});
});
} else {
// Add new workflow spec and file
this.api.addWorkflowSpecification(newSpec).subscribe(spec => {
this.api.addFileMeta(newSpec.id, this.diagramFileMeta).subscribe(fileMeta => {
this.loadFilesFromDb();
this.snackBar.open('Saved new workflow spec and file.', 'Ok', {duration: 5000});
});
// Add new file meta
this.api.addFileMeta(specId, this.diagramFileMeta).subscribe(fileMeta => {
this.loadFilesFromDb();
this.snackBar.open('Saved new workflow spec and file.', 'Ok', {duration: 5000});
});
}
}