Fixes all the modeler stuff that broke with the latest version changes.

This commit is contained in:
Aaron Louie 2021-08-13 14:58:13 -04:00
parent 1b447a9d88
commit 53badb187a
4 changed files with 142 additions and 56 deletions

View File

@ -1,7 +1,12 @@
export interface BpmnError {
warnings: BpmnWarning[];
}
export interface BpmnWarning {
message?: string;
element?: any;
property?: string;
value?: string;
context?: any;
error?: Error;
}

View File

@ -13,6 +13,6 @@ export const bpmnModelerConfig: ModelerConfig = {
codeModule,
],
moddleExtensions: {
camunda: bpmnModdleDescriptor.default
camunda: bpmnModdleDescriptor
}
};

View File

@ -1,12 +1,18 @@
import { APP_BASE_HREF } from '@angular/common';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { DebugNode } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { MatIconModule } from '@angular/material/icon';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import * as FileSaver from 'file-saver';
import { ApiService, BPMN_DIAGRAM_DEFAULT, FileType, MockEnvironment } from 'sartography-workflow-lib';
import {
ApiService,
BPMN_DIAGRAM_DEFAULT,
DMN_DIAGRAM_DEFAULT,
FileType,
MockEnvironment,
} from 'sartography-workflow-lib';
import {
BPMN_DIAGRAM,
BPMN_DIAGRAM_WITH_WARNINGS,
@ -20,6 +26,16 @@ describe('DiagramComponent', () => {
let fixture: ComponentFixture<DiagramComponent>;
let component: DebugNode['componentInstance'];
const mockRouter = {navigate: jasmine.createSpy('navigate')};
const testSaveAs = async (fileType: FileType, xml: string, callMethod: string, filename: string) => {
const saveDiagramSpy = spyOn(component, 'saveDiagram').and.callThrough();
const _fileSaveAsSpy = spyOn((component as any), '_fileSaveAs').and.stub();
component.fileName = filename + '.' + fileType;
component.diagramType = fileType;
component.xml = xml;
await component[callMethod]();
await expect(saveDiagramSpy).toHaveBeenCalled();
await expect(_fileSaveAsSpy).toHaveBeenCalledWith(jasmine.any(Blob), jasmine.stringMatching(filename));
};
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
@ -87,13 +103,14 @@ describe('DiagramComponent', () => {
const diagramURL = 'some-url';
component.importDone.subscribe(
result => {
expect(result.type).toEqual('success');
expect(result.warnings.length).toEqual(1);
expect(result.warnings[0].message).toContain('unparsable content <process> detected');
expect(result.type).toEqual('error');
expect(result.error).toBeDefined();
expect(result.error.warnings).toBeDefined();
expect(result.error.warnings.length).toEqual(1);
expect(result.error.warnings[0].message).toContain('unparsable content <process> detected');
done();
},
error => {
console.log('importDone > subscribe > error', error);
});
component.loadUrl(diagramURL);
@ -131,16 +148,20 @@ describe('DiagramComponent', () => {
});
});
it('should save diagram as SVG', () => {
const fileSaverSpy = spyOn(FileSaver, 'saveAs').and.stub();
component.saveSVG();
expect(fileSaverSpy).toHaveBeenCalled();
it('should save BPMN diagram as SVG', async () => {
await testSaveAs(FileType.BPMN, BPMN_DIAGRAM, 'saveSVG', 'some_bpmn_name');
});
it('should save diagram as XML', () => {
const fileSaverSpy = spyOn(FileSaver, 'saveAs').and.stub();
component.saveXML();
expect(fileSaverSpy).toHaveBeenCalled();
it('should save BPMN diagram as XML', async () => {
await testSaveAs(FileType.BPMN, BPMN_DIAGRAM, 'saveXML', 'some_bpmn_name');
});
it('should save DMN diagram as SVG', async () => {
await testSaveAs(FileType.DMN, DMN_DIAGRAM, 'saveSVG', 'some_dmn_name');
});
it('should save DMN diagram as XML', async () => {
await testSaveAs(FileType.DMN, DMN_DIAGRAM, 'saveXML', 'some_dmn_name');
});
it('should insert date into filename', () => {
@ -188,7 +209,6 @@ describe('DiagramComponent', () => {
done();
},
error => {
console.log('importDone > subscribe > error', error);
});
});
@ -196,16 +216,16 @@ describe('DiagramComponent', () => {
component.openDiagram('INVALID DMN XML', FileType.DMN);
component.importDone.subscribe(
result => {
expect(result.type).toEqual('success');
expect(result.type).toEqual('error');
expect(result.error.message).toContain('unparsable content INVALID DMN XML detected');
done();
},
error => {
console.log('importDone > subscribe > error', error);
console.error('importDone > subscribe > error', error);
});
});
it('should edit diagram', () => {
it('should edit BPMN diagram', () => {
const initializeModelerSpy = spyOn(component, 'initializeModeler').and.stub();
const onChangeSpy = spyOn(component, 'onChange').and.stub();
const importXMLSpy = spyOn(component.modeler, 'importXML').and.callThrough();
@ -219,6 +239,23 @@ describe('DiagramComponent', () => {
expect(onChangeSpy).toHaveBeenCalled();
});
it('should edit DMN diagram', () => {
const initializeModelerSpy = spyOn(component, 'initializeModeler').and.stub();
const onChangeSpy = spyOn(component, 'onChange').and.stub();
const importXMLSpy = spyOn(component.modeler, 'importXML').and.callThrough();
spyOn(component, 'getRandomString').and.returnValue('REPLACE_ME');
component.diagramType = FileType.DMN;
component.openDiagram(DMN_DIAGRAM_DEFAULT, FileType.DMN);
expect(initializeModelerSpy).toHaveBeenCalledWith(FileType.DMN);
expect(importXMLSpy).toHaveBeenCalledWith(DMN_DIAGRAM_DEFAULT, jasmine.any(Function));
initializeModelerSpy.calls.reset();
component.writeValue(DMN_DIAGRAM);
expect(initializeModelerSpy).toHaveBeenCalledWith(undefined);
expect(onChangeSpy).toHaveBeenCalled();
});
it('should register onChange function', () => {
const fn = (s: string) => s.toLowerCase().trim();
const input = ' TRIMMED AND LOWERCASED ';

View File

@ -24,11 +24,12 @@ import {
FileType,
} from 'sartography-workflow-lib';
import { v4 as uuidv4 } from 'uuid';
import { BpmnWarning } from '../_interfaces/bpmn-warning';
import { BpmnError, BpmnWarning } from '../_interfaces/bpmn-warning';
import { ImportEvent } from '../_interfaces/import-event';
import { bpmnModelerConfig } from './bpmn-modeler-config';
import { dmnModelerConfig } from './dmn-modeler-config';
import { getDiagramTypeFromXml } from '../_util/diagram-type';
import isEqual from 'lodash.isequal';
@Component({
selector: 'app-diagram',
@ -144,61 +145,89 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit, On
return this.zone.run(() => {
const isDMN = diagramType === FileType.DMN;
if (!xml) {
const defaultXml = isDMN ? DMN_DIAGRAM_DEFAULT : BPMN_DIAGRAM_DEFAULT;
xml = defaultXml.replace(/REPLACE_ME/gi, () => this.getRandomString(7));
const randomString = this.getRandomString(7);
xml = defaultXml.replace(/REPLACE_ME/gi, () => randomString);
}
// Add an arbitrary string to get the save button to enable
if (isDMN) {
// DMN Modeler takes a callback
this.modeler.importXML(xml, (e, w) => this.onImport(e, w));
this.modeler.importXML(xml, (e, w) => this.onImport(e, w || e && e.warnings));
} else {
// BPMN Modeler returns a Promise
this.modeler.importXML(xml).then(
(e, w) => this.onImport(e, w),
error => this.onImport(error, [error.warnings]),
(e, w) => this.onImport(e, w || e && e.warnings),
e => this.onImport(e, e && e.warnings),
);
}
});
}
saveSVG() {
this.saveDiagram();
this.modeler.saveSVG((err, svg) => {
const blob = new Blob([svg], {type: 'image/svg+xml'});
fileSaver.saveAs(blob, `${this.diagramType.toString().toUpperCase()} Diagram - ${new Date().toISOString()}.svg`);
});
async saveSVG() {
await this.saveDiagram();
const {svg} = await this.modeler.saveSVG();
const blob = new Blob([svg], {type: 'image/svg+xml'});
this._fileSaveAs(blob, this.insertDateIntoFileName());
}
saveDiagram() {
async saveDiagram() {
if (this.modeler && this.modeler.saveSVG) {
this.modeler.saveSVG((svgErr, svg) => {
this.svg = svg;
this.modeler.saveXML({format: true}, (xmlErr, xml) => {
this.xml = xml;
this.writeValue(xml);
});
});
} else {
this.modeler.saveXML({format: true}, (xmlErr, xml) => {
this.xml = xml;
this.writeValue(xml);
});
const {svg} = await this.modeler.saveSVG();
this.svg = svg;
}
const {xml} = await this.modeler.saveXML({format: true});
this.xml = xml;
this.writeValue(xml);
}
saveXML() {
this.saveDiagram();
this.modeler.saveXML({format: true}, (err, xml) => {
const blob = new Blob([xml], {type: 'text/xml'});
fileSaver.saveAs(blob, this.insertDateIntoFileName());
});
async saveXML() {
await this.saveDiagram();
const {xml} = await this.modeler.saveXML({format: true})
const blob = new Blob([xml], {type: 'text/xml'});
this._fileSaveAs(blob, this.insertDateIntoFileName());
}
onImport(err?: HttpErrorResponse, warnings?: BpmnWarning[]) {
if (err && warnings && warnings.length > 0) {
this._handleErrors(err);
onImport(err?: Error | BpmnError, warnings?: BpmnWarning[]) {
warnings = warnings || [];
// SUCCESS (no errors or warnings)
// -----------------------------------------
// err = {warnings: []}
// warnings = []
// -----------------------------------------
// err = undefined
// warnings = []
// -----------------------------------------
// ERROR
// -----------------------------------------
// err = {warnings: [{message: '...', error: {stack '...', message: '...'}}]}
// warnings = [{message: '...', error: {stack '...', message: '...'}}]
// -----------------------------------------
// SUCCESS WITH WARNINGS
// -----------------------------------------
// err = {warnings: [{message: '...', error: {}}]
// warnings = [{message: '...', error: {}}]
// -----------------------------------------
// err = undefined
// warnings = [{message: '...', error: {}}]
// -----------------------------------------
const isSuccess = (!err || isEqual(err, {warnings: []})) && isEqual(warnings, []);
const isError = !isSuccess && err && (
err instanceof Error ||
err.warnings && err.warnings.some(w => w.error && !isEqual(w.error, {}))
);
if (isSuccess && !isError) {
this._handleSuccess();
} else if (isError) {
const errors = err || warnings.filter(w => !!w.error);
this._handleErrors(errors);
} else {
this._handleWarnings(warnings);
}
@ -208,10 +237,20 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit, On
* Load diagram from URL and emit completion event
*/
loadUrl(url: string) {
this.api.getStringFromUrl(url).subscribe(xml => {
const diagramType = getDiagramTypeFromXml(xml);
this.openDiagram(xml, diagramType);
}, error => this._handleErrors(error));
this.api.getStringFromUrl(url).subscribe(
xml => {
const diagramType = getDiagramTypeFromXml(xml);
this.openDiagram(xml, diagramType);
},
error => this._handleErrors(error)
);
}
private _handleSuccess() {
this.importDone.emit({
type: 'success',
warnings: [],
});
}
private _handleWarnings(warnings: BpmnWarning[]) {
@ -338,4 +377,9 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit, On
return `${this.fileName}_${dateString}.${this.diagramType}`;
}
}
private _fileSaveAs(blob: Blob, filename: string) {
fileSaver.saveAs(blob, filename);
};
}