From 7b3d41dae3b4a54722da20db36e81aeac0d091b8 Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Sun, 14 Feb 2021 20:30:37 +0100 Subject: [PATCH 1/4] [REFACTOR] Store part of the BpmnModel into memory for searches This is a first step to avoid depending on the mxGraph model for elements search. It introduces a new dedicated registry to manage the BpmnModel and related objects. They are now used to search element by ids. This first implementation holds a structure to make search faster: this is probably not optimal and will be rework later. The previously called 'DisplayModel' has been extracted out of mxgraph code as it is not tight to it. It has been renamed into 'RenderedModel' for consistency with the wording we use everywhere else. --- src/api/public/api.ts | 3 +- src/component/BpmnVisualization.ts | 8 +- src/component/mxgraph/MxGraphRenderer.ts | 59 +++--------- .../registry/bpmn-elements-registry.ts | 52 ++--------- src/component/registry/bpmn-model-registry.ts | 92 +++++++++++++++++++ src/component/registry/index.ts | 3 +- src/component/registry/types.ts | 36 ++++++++ src/demo/index.ts | 3 +- src/elements-identification.html | 23 ++++- src/model/bpmn/internal/api.ts | 22 +++++ src/static/js/elements-identification.js | 2 + test/integration/dom.interactions.test.ts | 40 ++------ .../registry/bpmn-model-registry.test.ts | 82 +++++++++++++++++ test/unit/helpers/bpmn-semantic-utils.ts | 67 ++++++++++++++ 14 files changed, 359 insertions(+), 133 deletions(-) create mode 100644 src/component/registry/bpmn-model-registry.ts create mode 100644 src/component/registry/types.ts create mode 100644 src/model/bpmn/internal/api.ts create mode 100644 test/unit/component/registry/bpmn-model-registry.test.ts create mode 100644 test/unit/helpers/bpmn-semantic-utils.ts diff --git a/src/api/public/api.ts b/src/api/public/api.ts index 8898c75eca..e1c136e185 100644 --- a/src/api/public/api.ts +++ b/src/api/public/api.ts @@ -20,4 +20,5 @@ export { BpmnVisualization }; export { GlobalOptions, NavigationConfiguration, FitOptions, FitType, LoadOptions, ZoomConfiguration } from '../../component/options'; // Interaction -export { BpmnElementsRegistry, BpmnSemantic, BpmnElement } from '../../component/registry'; +export { BpmnElement, BpmnElementsRegistry, BpmnSemantic } from '../../component/registry'; +export * from '../../model/bpmn/internal/api'; diff --git a/src/component/BpmnVisualization.ts b/src/component/BpmnVisualization.ts index 0ba614cfcc..a3a15c521f 100644 --- a/src/component/BpmnVisualization.ts +++ b/src/component/BpmnVisualization.ts @@ -20,6 +20,7 @@ import { BpmnMxGraph } from './mxgraph/BpmnMxGraph'; import { FitOptions, GlobalOptions, LoadOptions } from './options'; import { BpmnElementsRegistry } from './registry'; import { newBpmnElementsRegistry } from './registry/bpmn-elements-registry'; +import { BpmnModelRegistry } from './registry/bpmn-model-registry'; import { htmlElement } from './helpers/dom-utils'; /** @@ -32,19 +33,22 @@ export default class BpmnVisualization { * @experimental subject to change, feedback welcome */ readonly bpmnElementsRegistry: BpmnElementsRegistry; + private readonly _bpmnModelRegistry: BpmnModelRegistry; constructor(options: GlobalOptions) { // mxgraph configuration const configurator = new MxGraphConfigurator(htmlElement(options?.container)); this.graph = configurator.configure(options); // other configurations - this.bpmnElementsRegistry = newBpmnElementsRegistry(this.graph); + this._bpmnModelRegistry = new BpmnModelRegistry(); + this.bpmnElementsRegistry = newBpmnElementsRegistry(this._bpmnModelRegistry, this.graph); } public load(xml: string, options?: LoadOptions): void { try { const bpmnModel = newBpmnParser().parse(xml); - newMxGraphRenderer(this.graph).render(bpmnModel, options); + const renderedModel = this._bpmnModelRegistry.computeRenderedModel(bpmnModel); + newMxGraphRenderer(this.graph).render(renderedModel, options); } catch (e) { // TODO error handling window.alert(`Cannot load bpmn diagram: ${e.message}`); diff --git a/src/component/mxgraph/MxGraphRenderer.ts b/src/component/mxgraph/MxGraphRenderer.ts index 57293b16db..4da162bec0 100644 --- a/src/component/mxgraph/MxGraphRenderer.ts +++ b/src/component/mxgraph/MxGraphRenderer.ts @@ -15,8 +15,7 @@ */ import Shape from '../../model/bpmn/internal/shape/Shape'; import Edge from '../../model/bpmn/internal/edge/Edge'; -import BpmnModel from '../../model/bpmn/internal/BpmnModel'; -import ShapeBpmnElement, { ShapeBpmnSubProcess } from '../../model/bpmn/internal/shape/ShapeBpmnElement'; +import ShapeBpmnElement from '../../model/bpmn/internal/shape/ShapeBpmnElement'; import Waypoint from '../../model/bpmn/internal/edge/Waypoint'; import Bounds from '../../model/bpmn/internal/Bounds'; import ShapeUtil from '../../model/bpmn/internal/shape/ShapeUtil'; @@ -24,35 +23,33 @@ import CoordinatesTranslator from './renderer/CoordinatesTranslator'; import StyleConfigurator from './config/StyleConfigurator'; import { MessageFlow } from '../../model/bpmn/internal/edge/Flow'; import { MessageVisibleKind } from '../../model/bpmn/internal/edge/MessageVisibleKind'; -import { ShapeBpmnMarkerKind } from '../../model/bpmn/internal/shape'; import { BpmnMxGraph } from './BpmnMxGraph'; import { LoadOptions } from '../options'; +import { RenderedModel } from '../registry/bpmn-model-registry'; // for types import { mxgraph } from './initializer'; import { mxCell } from 'mxgraph'; // for types export default class MxGraphRenderer { constructor(readonly graph: BpmnMxGraph, readonly coordinatesTranslator: CoordinatesTranslator, readonly styleConfigurator: StyleConfigurator) {} - public render(bpmnModel: BpmnModel, loadOptions?: LoadOptions): void { - this.insertShapesAndEdges(bpmnModel); + public render(renderedModel: RenderedModel, loadOptions?: LoadOptions): void { + this.insertShapesAndEdges(renderedModel); this.graph.customFit(loadOptions?.fit); } - private insertShapesAndEdges(bpmnModel: BpmnModel): void { - const displayedModel = toDisplayedModel(bpmnModel); - + private insertShapesAndEdges(renderedModel: RenderedModel): void { const model = this.graph.getModel(); model.clear(); // ensure to remove manual changes or already loaded graphs model.beginUpdate(); try { - this.insertShapes(displayedModel.pools); - this.insertShapes(displayedModel.lanes); - this.insertShapes(displayedModel.subprocesses); - this.insertShapes(displayedModel.otherFlowNodes); + this.insertShapes(renderedModel.pools); + this.insertShapes(renderedModel.lanes); + this.insertShapes(renderedModel.subprocesses); + this.insertShapes(renderedModel.otherFlowNodes); // last shape as the boundary event parent must be in the model (subprocess or activity) - this.insertShapes(displayedModel.boundaryEvents); + this.insertShapes(renderedModel.boundaryEvents); // at last as edge source and target must be present in the model prior insertion, otherwise they are not rendered - this.insertEdges(displayedModel.edges); + this.insertEdges(renderedModel.edges); } finally { model.endUpdate(); } @@ -163,37 +160,3 @@ export default class MxGraphRenderer { export function newMxGraphRenderer(graph: BpmnMxGraph): MxGraphRenderer { return new MxGraphRenderer(graph, new CoordinatesTranslator(graph), new StyleConfigurator(graph)); } - -function toDisplayedModel(bpmnModel: BpmnModel): DisplayedModel { - const collapsedSubProcessIds: string[] = bpmnModel.flowNodes - .filter(shape => { - const bpmnElement = shape.bpmnElement; - return ShapeUtil.isSubProcess(bpmnElement?.kind) && (bpmnElement as ShapeBpmnSubProcess)?.markers.includes(ShapeBpmnMarkerKind.EXPAND); - }) - .map(shape => shape.bpmnElement?.id); - - const subprocesses: Shape[] = []; - const boundaryEvents: Shape[] = []; - const otherFlowNodes: Shape[] = []; - bpmnModel.flowNodes.forEach(shape => { - const kind = shape.bpmnElement?.kind; - if (ShapeUtil.isSubProcess(kind)) { - subprocesses.push(shape); - } else if (ShapeUtil.isBoundaryEvent(kind)) { - boundaryEvents.push(shape); - } else if (!collapsedSubProcessIds.includes(shape.bpmnElement?.parentId)) { - otherFlowNodes.push(shape); - } - }); - - return { boundaryEvents: boundaryEvents, edges: bpmnModel.edges, lanes: bpmnModel.lanes, otherFlowNodes: otherFlowNodes, pools: bpmnModel.pools, subprocesses: subprocesses }; -} - -interface DisplayedModel { - edges: Edge[]; - boundaryEvents: Shape[]; - otherFlowNodes: Shape[]; - lanes: Shape[]; - pools: Shape[]; - subprocesses: Shape[]; -} diff --git a/src/component/registry/bpmn-elements-registry.ts b/src/component/registry/bpmn-elements-registry.ts index 0729ea8244..333684e2c5 100644 --- a/src/component/registry/bpmn-elements-registry.ts +++ b/src/component/registry/bpmn-elements-registry.ts @@ -15,20 +15,16 @@ */ import { ensureIsArray } from '../helpers/array-utils'; import { BpmnMxGraph } from '../mxgraph/BpmnMxGraph'; -import { computeBpmnBaseClassName, extractBpmnKindFromStyle } from '../mxgraph/style-helper'; -import { FlowKind } from '../../model/bpmn/internal/edge/FlowKind'; -import { ShapeBpmnElementKind } from '../../model/bpmn/internal/shape'; +import { computeBpmnBaseClassName } from '../mxgraph/style-helper'; import { CssRegistry } from './css-registry'; import MxGraphCellUpdater from '../mxgraph/MxGraphCellUpdater'; import { BpmnQuerySelectors } from './query-selectors'; +import { BpmnElement } from './types'; +import { BpmnModelRegistry } from './bpmn-model-registry'; +import { BpmnElementKind } from '../../model/bpmn/internal/api'; -export function newBpmnElementsRegistry(graph: BpmnMxGraph): BpmnElementsRegistry { - return new BpmnElementsRegistry( - new BpmnModelRegistry(graph), - new HtmlElementRegistry(new BpmnQuerySelectors(graph.container?.id)), - new CssRegistry(), - new MxGraphCellUpdater(graph), - ); +export function newBpmnElementsRegistry(bpmnModelRegistry: BpmnModelRegistry, graph: BpmnMxGraph): BpmnElementsRegistry { + return new BpmnElementsRegistry(bpmnModelRegistry, new HtmlElementRegistry(new BpmnQuerySelectors(graph.container?.id)), new CssRegistry(), new MxGraphCellUpdater(graph)); } /** @@ -183,42 +179,6 @@ export class BpmnElementsRegistry { } } -export type BpmnElementKind = FlowKind | ShapeBpmnElementKind; - -/** - * @category Interaction - */ -export interface BpmnSemantic { - id: string; - name: string; - /** `true` when relates to a BPMN Shape, `false` when relates to a BPMN Edge. */ - isShape: boolean; - // TODO use a more 'type oriented' BpmnElementKind (as part of #929) - kind: string; -} - -/** - * @category Interaction - */ -export interface BpmnElement { - bpmnSemantic: BpmnSemantic; - htmlElement: HTMLElement; -} - -// for now, we don't store the BpmnModel so we can use it, information are only available in the mxgraph model -class BpmnModelRegistry { - constructor(private graph: BpmnMxGraph) {} - - getBpmnSemantic(bpmnElementId: string): BpmnSemantic | undefined { - const mxCell = this.graph.getModel().getCell(bpmnElementId); - if (mxCell == null) { - return undefined; - } - - return { id: bpmnElementId, name: mxCell.value, isShape: mxCell.isVertex(), kind: extractBpmnKindFromStyle(mxCell) }; - } -} - class HtmlElementRegistry { constructor(private selectors: BpmnQuerySelectors) {} diff --git a/src/component/registry/bpmn-model-registry.ts b/src/component/registry/bpmn-model-registry.ts new file mode 100644 index 0000000000..73831274e1 --- /dev/null +++ b/src/component/registry/bpmn-model-registry.ts @@ -0,0 +1,92 @@ +/** + * Copyright 2021 Bonitasoft S.A. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import BpmnModel from '../../model/bpmn/internal/BpmnModel'; +import Shape from '../../model/bpmn/internal/shape/Shape'; +import Edge from '../../model/bpmn/internal/edge/Edge'; +import { BpmnSemantic } from './types'; +import ShapeUtil from '../../model/bpmn/internal/shape/ShapeUtil'; +import ShapeBpmnElement, { ShapeBpmnSubProcess } from '../../model/bpmn/internal/shape/ShapeBpmnElement'; +import { ShapeBpmnMarkerKind } from '../../model/bpmn/internal/shape'; + +export class BpmnModelRegistry { + private searchableModel: SearchableModel; + + computeRenderedModel(bpmnModel: BpmnModel): RenderedModel { + this.searchableModel = new SearchableModel(bpmnModel); + return toRenderedModel(bpmnModel); + } + + getBpmnSemantic(bpmnElementId: string): BpmnSemantic | undefined { + const element = this.searchableModel.elementById(bpmnElementId); + if (!element) { + return undefined; + } + const bpmnElement = element.bpmnElement; + const isShape = bpmnElement instanceof ShapeBpmnElement; + return { id: bpmnElementId, name: bpmnElement.name, isShape: isShape, kind: bpmnElement.kind }; + } +} + +function toRenderedModel(bpmnModel: BpmnModel): RenderedModel { + const collapsedSubProcessIds: string[] = bpmnModel.flowNodes + .filter(shape => { + const bpmnElement = shape.bpmnElement; + return ShapeUtil.isSubProcess(bpmnElement?.kind) && (bpmnElement as ShapeBpmnSubProcess)?.markers.includes(ShapeBpmnMarkerKind.EXPAND); + }) + .map(shape => shape.bpmnElement?.id); + + const subprocesses: Shape[] = []; + const boundaryEvents: Shape[] = []; + const otherFlowNodes: Shape[] = []; + bpmnModel.flowNodes.forEach(shape => { + const kind = shape.bpmnElement?.kind; + if (ShapeUtil.isSubProcess(kind)) { + subprocesses.push(shape); + } else if (ShapeUtil.isBoundaryEvent(kind)) { + boundaryEvents.push(shape); + } else if (!collapsedSubProcessIds.includes(shape.bpmnElement?.parentId)) { + otherFlowNodes.push(shape); + } + }); + + return { boundaryEvents: boundaryEvents, edges: bpmnModel.edges, lanes: bpmnModel.lanes, otherFlowNodes: otherFlowNodes, pools: bpmnModel.pools, subprocesses: subprocesses }; +} + +export interface RenderedModel { + edges: Edge[]; + boundaryEvents: Shape[]; + otherFlowNodes: Shape[]; + lanes: Shape[]; + pools: Shape[]; + subprocesses: Shape[]; +} + +class SearchableModel { + private elements: Map = new Map(); + + constructor(bpmnModel: BpmnModel) { + ([] as Array) + .concat(bpmnModel.pools, bpmnModel.lanes, bpmnModel.flowNodes, bpmnModel.edges) + // use the bpmn element id and not the bpmn shape id + .forEach(e => { + this.elements.set(e.bpmnElement.id, e); + }); + } + + elementById(id: string): Shape | Edge | undefined { + return this.elements.get(id); + } +} diff --git a/src/component/registry/index.ts b/src/component/registry/index.ts index 123c32a006..04478d4452 100644 --- a/src/component/registry/index.ts +++ b/src/component/registry/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ -export { BpmnElementsRegistry, BpmnElement, BpmnSemantic } from './bpmn-elements-registry'; +export { BpmnElementsRegistry } from './bpmn-elements-registry'; +export * from './types'; diff --git a/src/component/registry/types.ts b/src/component/registry/types.ts new file mode 100644 index 0000000000..085452aa66 --- /dev/null +++ b/src/component/registry/types.ts @@ -0,0 +1,36 @@ +/** + * Copyright 2021 Bonitasoft S.A. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BpmnElementKind } from '../../model/bpmn/internal/api'; + +/** + * @category Interaction + */ +export interface BpmnSemantic { + id: string; + name: string; + /** `true` when relates to a BPMN Shape, `false` when relates to a BPMN Edge. */ + isShape: boolean; + kind: BpmnElementKind; +} + +/** + * @category Interaction + */ +export interface BpmnElement { + bpmnSemantic: BpmnSemantic; + htmlElement: HTMLElement; +} diff --git a/src/demo/index.ts b/src/demo/index.ts index 2e7f94760b..91b4d9b139 100644 --- a/src/demo/index.ts +++ b/src/demo/index.ts @@ -17,7 +17,8 @@ import BpmnVisualization from '../component/BpmnVisualization'; import { GlobalOptions, FitOptions, FitType, LoadOptions } from '../component/options'; import { log, logStartup } from './helper'; import { DropFileUserInterface } from './component/DropFileUserInterface'; -import { BpmnElement, BpmnElementKind } from '../component/registry/bpmn-elements-registry'; +import { BpmnElement } from '../component/registry'; +import { BpmnElementKind } from '../model/bpmn/internal/api'; export * from './helper'; diff --git a/src/elements-identification.html b/src/elements-identification.html index 84d0774938..301a7f2005 100644 --- a/src/elements-identification.html +++ b/src/elements-identification.html @@ -2,7 +2,7 @@ - BPMN Visualization Non Regression + BPMN Visualization - Elements Identification @@ -68,6 +87,8 @@ + + diff --git a/src/model/bpmn/internal/api.ts b/src/model/bpmn/internal/api.ts new file mode 100644 index 0000000000..6ce1a7245a --- /dev/null +++ b/src/model/bpmn/internal/api.ts @@ -0,0 +1,22 @@ +/** + * Copyright 2021 Bonitasoft S.A. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FlowKind } from './edge/FlowKind'; +import { ShapeBpmnElementKind } from './shape'; + +export type BpmnElementKind = FlowKind | ShapeBpmnElementKind; +export { FlowKind } from './edge/FlowKind'; +export { ShapeBpmnElementKind } from './shape'; diff --git a/src/static/js/elements-identification.js b/src/static/js/elements-identification.js index b272000bc2..4799109460 100644 --- a/src/static/js/elements-identification.js +++ b/src/static/js/elements-identification.js @@ -60,6 +60,8 @@ function getCustomCssClassName(bpmnKind) { return 'detection-event'; } else if (bpmnKind.includes('lane')) { return 'detection-lane'; + } else if (bpmnKind.includes('Flow')) { + return 'detection-flow'; } return 'detection'; } diff --git a/test/integration/dom.interactions.test.ts b/test/integration/dom.interactions.test.ts index d392f525b5..7a3dd421f7 100644 --- a/test/integration/dom.interactions.test.ts +++ b/test/integration/dom.interactions.test.ts @@ -17,6 +17,7 @@ import { readFileSync } from '../helpers/file-helper'; import { BpmnElement, BpmnVisualization, ShapeBpmnElementKind } from '../../src/bpmn-visualization'; import { FlowKind } from '../../src/model/bpmn/internal/edge/FlowKind'; import { expectSvgEvent, expectSvgPool, expectSvgSequenceFlow, expectSvgTask, HtmlElementLookup } from './helpers/html-utils'; +import { ExpectedBaseBpmnElement, expectEndEvent, expectPool, expectSequenceFlow, expectServiceTask, expectStartEvent, expectTask } from '../unit/helpers/bpmn-semantic-utils'; const bpmnContainerId = 'bpmn-visualization-container'; const bpmnVisualization = initializeBpmnVisualization(); @@ -41,60 +42,33 @@ describe('DOM only checks', () => { }); }); -interface ExpectedBaseBpmnElement { - id: string; - name?: string; -} - -function expectShapeBpmnElement(bpmnElement: BpmnElement, expected: ExpectedBaseBpmnElement): void { - const bpmnSemantic = bpmnElement.bpmnSemantic; - expect(bpmnSemantic.id).toEqual(expected.id); - expect(bpmnSemantic.name).toEqual(expected.name); - expect(bpmnSemantic.isShape).toBeTruthy(); -} - function expectStartEventBpmnElement(bpmnElement: BpmnElement, expected: ExpectedBaseBpmnElement): void { - expectShapeBpmnElement(bpmnElement, expected); - expect(bpmnElement.bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.EVENT_START); - + expectStartEvent(bpmnElement.bpmnSemantic, expected); expectSvgEvent(bpmnElement.htmlElement); } function expectEndEventBpmnElement(bpmnElement: BpmnElement, expected: ExpectedBaseBpmnElement): void { - expectShapeBpmnElement(bpmnElement, expected); - expect(bpmnElement.bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.EVENT_END); - + expectEndEvent(bpmnElement.bpmnSemantic, expected); expectSvgEvent(bpmnElement.htmlElement); } function expectSequenceFlowBpmnElement(bpmnElement: BpmnElement, expected: ExpectedBaseBpmnElement): void { - const bpmnSemantic = bpmnElement.bpmnSemantic; - expect(bpmnSemantic.id).toEqual(expected.id); - expect(bpmnSemantic.name).toEqual(expected.name); - expect(bpmnSemantic.isShape).toBeFalsy(); - expect(bpmnSemantic.kind).toEqual(FlowKind.SEQUENCE_FLOW); - + expectSequenceFlow(bpmnElement.bpmnSemantic, expected); expectSvgSequenceFlow(bpmnElement.htmlElement); } function expectTaskBpmnElement(bpmnElement: BpmnElement, expected: ExpectedBaseBpmnElement): void { - expectShapeBpmnElement(bpmnElement, expected); - expect(bpmnElement.bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.TASK); - + expectTask(bpmnElement.bpmnSemantic, expected); expectSvgTask(bpmnElement.htmlElement); } function expectServiceTaskBpmnElement(bpmnElement: BpmnElement, expected: ExpectedBaseBpmnElement): void { - expectShapeBpmnElement(bpmnElement, expected); - expect(bpmnElement.bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.TASK_SERVICE); - + expectServiceTask(bpmnElement.bpmnSemantic, expected); expectSvgTask(bpmnElement.htmlElement); } function expectPoolBpmnElement(bpmnElement: BpmnElement, expected: ExpectedBaseBpmnElement): void { - expectShapeBpmnElement(bpmnElement, expected); - expect(bpmnElement.bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.POOL); - + expectPool(bpmnElement.bpmnSemantic, expected); expectSvgPool(bpmnElement.htmlElement); } diff --git a/test/unit/component/registry/bpmn-model-registry.test.ts b/test/unit/component/registry/bpmn-model-registry.test.ts new file mode 100644 index 0000000000..4ec2975bdd --- /dev/null +++ b/test/unit/component/registry/bpmn-model-registry.test.ts @@ -0,0 +1,82 @@ +/** + * Copyright 2021 Bonitasoft S.A. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BpmnModelRegistry } from '../../../../src/component/registry/bpmn-model-registry'; +import BpmnModel from '../../../../src/model/bpmn/internal/BpmnModel'; +import Edge from '../../../../src/model/bpmn/internal/edge/Edge'; +import { SequenceFlow } from '../../../../src/model/bpmn/internal/edge/Flow'; +import Shape from '../../../../src/model/bpmn/internal/shape/Shape'; +import { ShapeBpmnElementKind, ShapeBpmnEventKind } from '../../../../src/model/bpmn/internal/shape'; +import ShapeBpmnElement, { ShapeBpmnStartEvent } from '../../../../src/model/bpmn/internal/shape/ShapeBpmnElement'; +import { expectLane, expectPool, expectSequenceFlow, expectStartEvent } from '../../helpers/bpmn-semantic-utils'; + +const bpmnModelRegistry = new BpmnModelRegistry(); + +function newBpmnModel(): BpmnModel { + return { + edges: [], + flowNodes: [], + lanes: [], + pools: [], + }; +} + +function sequenceFlowInModel(id: string, name: string): BpmnModel { + const bpmnModel = newBpmnModel(); + bpmnModel.edges.push(new Edge(`Edge_${id}`, new SequenceFlow(id, name))); + return bpmnModel; +} + +function startEventInModel(id: string, name: string): BpmnModel { + const bpmnModel = newBpmnModel(); + bpmnModel.flowNodes.push(new Shape(`Shape_${id}`, new ShapeBpmnStartEvent(id, name, ShapeBpmnEventKind.TIMER, 'parentId'))); + return bpmnModel; +} + +function laneInModel(id: string, name: string): BpmnModel { + const bpmnModel = newBpmnModel(); + bpmnModel.lanes.push(new Shape(`Shape_${id}`, new ShapeBpmnElement(id, name, ShapeBpmnElementKind.LANE))); + return bpmnModel; +} + +function poolInModel(id: string, name: string): BpmnModel { + const bpmnModel = newBpmnModel(); + bpmnModel.pools.push(new Shape(`Shape_${id}`, new ShapeBpmnElement(id, name, ShapeBpmnElementKind.POOL))); + return bpmnModel; +} + +describe('Bpmn Model registry', () => { + it('search edge', () => { + bpmnModelRegistry.computeRenderedModel(sequenceFlowInModel('seq flow id', 'seq flow name')); + const bpmnSemantic = bpmnModelRegistry.getBpmnSemantic('seq flow id'); + expectSequenceFlow(bpmnSemantic, { id: 'seq flow id', name: 'seq flow name' }); + }); + it('search flownode', () => { + bpmnModelRegistry.computeRenderedModel(startEventInModel('start event id', 'start event name')); + const bpmnSemantic = bpmnModelRegistry.getBpmnSemantic('start event id'); + expectStartEvent(bpmnSemantic, { id: 'start event id', name: 'start event name' }); + }); + it('search lane', () => { + bpmnModelRegistry.computeRenderedModel(laneInModel('lane id', 'lane name')); + const bpmnSemantic = bpmnModelRegistry.getBpmnSemantic('lane id'); + expectLane(bpmnSemantic, { id: 'lane id', name: 'lane name' }); + }); + it('search pool', () => { + bpmnModelRegistry.computeRenderedModel(poolInModel('pool id', 'pool name')); + const bpmnSemantic = bpmnModelRegistry.getBpmnSemantic('pool id'); + expectPool(bpmnSemantic, { id: 'pool id', name: 'pool name' }); + }); +}); diff --git a/test/unit/helpers/bpmn-semantic-utils.ts b/test/unit/helpers/bpmn-semantic-utils.ts new file mode 100644 index 0000000000..3212e5142e --- /dev/null +++ b/test/unit/helpers/bpmn-semantic-utils.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2021 Bonitasoft S.A. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BpmnSemantic } from '../../../src/component/registry'; +import { FlowKind } from '../../../src/model/bpmn/internal/edge/FlowKind'; +import { ShapeBpmnElementKind } from '../../../src/model/bpmn/internal/shape'; + +export interface ExpectedBaseBpmnElement { + id: string; + name?: string; +} + +export function expectSequenceFlow(bpmnSemantic: BpmnSemantic, expected: ExpectedBaseBpmnElement): void { + expect(bpmnSemantic.id).toEqual(expected.id); + expect(bpmnSemantic.name).toEqual(expected.name); + expect(bpmnSemantic.isShape).toBeFalsy(); + expect(bpmnSemantic.kind).toEqual(FlowKind.SEQUENCE_FLOW); +} + +function expectShape(bpmnSemantic: BpmnSemantic, expected: ExpectedBaseBpmnElement): void { + expect(bpmnSemantic.id).toEqual(expected.id); + expect(bpmnSemantic.name).toEqual(expected.name); + expect(bpmnSemantic.isShape).toBeTruthy(); +} + +export function expectStartEvent(bpmnSemantic: BpmnSemantic, expected: ExpectedBaseBpmnElement): void { + expectShape(bpmnSemantic, expected); + expect(bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.EVENT_START); +} + +export function expectEndEvent(bpmnSemantic: BpmnSemantic, expected: ExpectedBaseBpmnElement): void { + expectShape(bpmnSemantic, expected); + expect(bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.EVENT_END); +} + +export function expectLane(bpmnSemantic: BpmnSemantic, expected: ExpectedBaseBpmnElement): void { + expectShape(bpmnSemantic, expected); + expect(bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.LANE); +} + +export function expectPool(bpmnSemantic: BpmnSemantic, expected: ExpectedBaseBpmnElement): void { + expectShape(bpmnSemantic, expected); + expect(bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.POOL); +} + +export function expectTask(bpmnSemantic: BpmnSemantic, expected: ExpectedBaseBpmnElement): void { + expectShape(bpmnSemantic, expected); + expect(bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.TASK); +} + +export function expectServiceTask(bpmnSemantic: BpmnSemantic, expected: ExpectedBaseBpmnElement): void { + expectShape(bpmnSemantic, expected); + expect(bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.TASK_SERVICE); +} From 7dd96bf3ba73398175afd08bf9d51733d5b123d0 Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Tue, 2 Mar 2021 12:11:21 +0100 Subject: [PATCH 2/4] Remove 'safe navigation operator' usage on bpmnElement It must never be null or undefined (if so this is a bug elsewhere), so drop it for simplicity. --- src/component/registry/bpmn-model-registry.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/component/registry/bpmn-model-registry.ts b/src/component/registry/bpmn-model-registry.ts index 73831274e1..1090e9d0cd 100644 --- a/src/component/registry/bpmn-model-registry.ts +++ b/src/component/registry/bpmn-model-registry.ts @@ -44,20 +44,20 @@ function toRenderedModel(bpmnModel: BpmnModel): RenderedModel { const collapsedSubProcessIds: string[] = bpmnModel.flowNodes .filter(shape => { const bpmnElement = shape.bpmnElement; - return ShapeUtil.isSubProcess(bpmnElement?.kind) && (bpmnElement as ShapeBpmnSubProcess)?.markers.includes(ShapeBpmnMarkerKind.EXPAND); + return ShapeUtil.isSubProcess(bpmnElement.kind) && (bpmnElement as ShapeBpmnSubProcess).markers.includes(ShapeBpmnMarkerKind.EXPAND); }) - .map(shape => shape.bpmnElement?.id); + .map(shape => shape.bpmnElement.id); const subprocesses: Shape[] = []; const boundaryEvents: Shape[] = []; const otherFlowNodes: Shape[] = []; bpmnModel.flowNodes.forEach(shape => { - const kind = shape.bpmnElement?.kind; + const kind = shape.bpmnElement.kind; if (ShapeUtil.isSubProcess(kind)) { subprocesses.push(shape); } else if (ShapeUtil.isBoundaryEvent(kind)) { boundaryEvents.push(shape); - } else if (!collapsedSubProcessIds.includes(shape.bpmnElement?.parentId)) { + } else if (!collapsedSubProcessIds.includes(shape.bpmnElement.parentId)) { otherFlowNodes.push(shape); } }); From 086dd945559b70d0f3b31fc3e8944e4cc5b228db Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Tue, 2 Mar 2021 12:31:19 +0100 Subject: [PATCH 3/4] MxGraphRenderer: simplify syntax --- src/component/mxgraph/MxGraphRenderer.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/component/mxgraph/MxGraphRenderer.ts b/src/component/mxgraph/MxGraphRenderer.ts index 4da162bec0..f6a53d7d43 100644 --- a/src/component/mxgraph/MxGraphRenderer.ts +++ b/src/component/mxgraph/MxGraphRenderer.ts @@ -25,7 +25,7 @@ import { MessageFlow } from '../../model/bpmn/internal/edge/Flow'; import { MessageVisibleKind } from '../../model/bpmn/internal/edge/MessageVisibleKind'; import { BpmnMxGraph } from './BpmnMxGraph'; import { LoadOptions } from '../options'; -import { RenderedModel } from '../registry/bpmn-model-registry'; // for types +import { RenderedModel } from '../registry/bpmn-model-registry'; import { mxgraph } from './initializer'; import { mxCell } from 'mxgraph'; // for types @@ -37,19 +37,19 @@ export default class MxGraphRenderer { this.graph.customFit(loadOptions?.fit); } - private insertShapesAndEdges(renderedModel: RenderedModel): void { + private insertShapesAndEdges({ pools, lanes, subprocesses, otherFlowNodes, boundaryEvents, edges }: RenderedModel): void { const model = this.graph.getModel(); model.clear(); // ensure to remove manual changes or already loaded graphs model.beginUpdate(); try { - this.insertShapes(renderedModel.pools); - this.insertShapes(renderedModel.lanes); - this.insertShapes(renderedModel.subprocesses); - this.insertShapes(renderedModel.otherFlowNodes); + this.insertShapes(pools); + this.insertShapes(lanes); + this.insertShapes(subprocesses); + this.insertShapes(otherFlowNodes); // last shape as the boundary event parent must be in the model (subprocess or activity) - this.insertShapes(renderedModel.boundaryEvents); + this.insertShapes(boundaryEvents); // at last as edge source and target must be present in the model prior insertion, otherwise they are not rendered - this.insertEdges(renderedModel.edges); + this.insertEdges(edges); } finally { model.endUpdate(); } From ce8735cc39a93056ab4b4aad3f43b18cc5164ccb Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Tue, 2 Mar 2021 12:38:30 +0100 Subject: [PATCH 4/4] Remove 'safe navigation operator' usage on bpmnElement It must never be null or undefined (if so this is a bug elsewhere), so drop it for simplicity. --- src/component/mxgraph/config/StyleConfigurator.ts | 2 +- src/model/bpmn/internal/edge/Edge.ts | 4 ++-- src/model/bpmn/internal/shape/Shape.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/component/mxgraph/config/StyleConfigurator.ts b/src/component/mxgraph/config/StyleConfigurator.ts index ddb379c79f..edfd20aa49 100644 --- a/src/component/mxgraph/config/StyleConfigurator.ts +++ b/src/component/mxgraph/config/StyleConfigurator.ts @@ -286,7 +286,7 @@ export default class StyleConfigurator { } computeStyle(bpmnCell: Shape | Edge, labelBounds: Bounds): string { - const styles: string[] = [bpmnCell.bpmnElement?.kind as string]; + const styles: string[] = [bpmnCell.bpmnElement.kind as string]; let shapeStyleValues; if (bpmnCell instanceof Shape) { diff --git a/src/model/bpmn/internal/edge/Edge.ts b/src/model/bpmn/internal/edge/Edge.ts index f3100ee351..d31726bd8c 100644 --- a/src/model/bpmn/internal/edge/Edge.ts +++ b/src/model/bpmn/internal/edge/Edge.ts @@ -20,8 +20,8 @@ import { MessageVisibleKind } from './MessageVisibleKind'; export default class Edge { constructor( - readonly id?: string, - readonly bpmnElement?: Flow, + readonly id: string, + readonly bpmnElement: Flow, readonly waypoints?: Waypoint[], readonly label?: Label, readonly messageVisibleKind: MessageVisibleKind = MessageVisibleKind.NONE, diff --git a/src/model/bpmn/internal/shape/Shape.ts b/src/model/bpmn/internal/shape/Shape.ts index c41f593a8d..f9715e25a8 100644 --- a/src/model/bpmn/internal/shape/Shape.ts +++ b/src/model/bpmn/internal/shape/Shape.ts @@ -18,5 +18,5 @@ import Bounds from '../Bounds'; import Label from '../Label'; export default class Shape { - constructor(readonly id?: string, readonly bpmnElement?: ShapeBpmnElement, readonly bounds?: Bounds, readonly label?: Label, readonly isHorizontal?: boolean) {} + constructor(readonly id: string, readonly bpmnElement: ShapeBpmnElement, readonly bounds?: Bounds, readonly label?: Label, readonly isHorizontal?: boolean) {} }