Skip to content

Commit

Permalink
feat: add a method to only get BPMN semantic of elements by kind (#2841)
Browse files Browse the repository at this point in the history
Complement the existing method that also retrieves DOM information. This
simplifies the API usage and reduce the amount of retrieved data when
the caller only needs to get the information from the model.

In addition:
- Simplify the existing `getElementsByKinds` implementation: use a flat
array instead of an array of array. This is then also consistent with
the implementation of the new method.
  - Use the new API in demo to simplify the code.
  - Rework the existing JSDoc for consistency
  • Loading branch information
tbouffard authored Aug 31, 2023
1 parent 9c1852d commit 5190882
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 31 deletions.
14 changes: 7 additions & 7 deletions dev/ts/pages/elements-identification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import type { BpmnElement, BpmnElementKind, Overlay, ShapeStyleUpdate, StyleUpdate } from '../dev-bundle-index';
import type { BpmnElementKind, BpmnSemantic, Overlay, ShapeStyleUpdate, StyleUpdate } from '../dev-bundle-index';
import {
addCssClasses,
addOverlays,
documentReady,
downloadPng,
downloadSvg,
FitType,
getElementsByKinds,
getModelElementsByKinds,
log,
removeAllOverlays,
removeCssClasses,
Expand Down Expand Up @@ -125,7 +125,7 @@ function updateStyleByAPI(bpmnIds: string[], bpmnKind: ShapeBpmnElementKind): vo
const otherIds = bpmnIds.filter(bpmnId => !subProcessChildrenIds.includes(bpmnId));

if (subProcessChildrenIds.length > 0) {
styledPoolAndLaneIds = getElementsByKinds([ShapeBpmnElementKind.POOL, ShapeBpmnElementKind.LANE]).map(element => element.bpmnSemantic.id);
styledPoolAndLaneIds = getModelElementsByKinds([ShapeBpmnElementKind.POOL, ShapeBpmnElementKind.LANE]).map(element => element.id);
updateStyle(styledPoolAndLaneIds, { opacity: 5, font: { color: 'blue', opacity: 5 }, fill: { color: 'pink' }, stroke: { color: 'green' } });
}
updateStyle(subProcessChildrenIds, { fill: { color: 'swimlane' }, stroke: { color: 'swimlane' }, font: { color: 'swimlane' } });
Expand Down Expand Up @@ -168,25 +168,25 @@ function manageOverlays(idsToAddOverlay: string[], bpmnKind: ShapeBpmnElementKin

function updateSelectedBPMNElements(bpmnKind: ShapeBpmnElementKind): void {
log(`Searching for Bpmn elements of '${bpmnKind}' kind`);
const elementsByKinds = getElementsByKinds(bpmnKind);
const elementsByKinds = getModelElementsByKinds(bpmnKind);

updateTextArea(elementsByKinds, bpmnKind);

// newly identified elements and values
const newlyIdentifiedBpmnIds = elementsByKinds.map(elt => elt.bpmnSemantic.id);
const newlyIdentifiedBpmnIds = elementsByKinds.map(elt => elt.id);
useCSS ? styleByCSS(newlyIdentifiedBpmnIds) : styleByAPI(newlyIdentifiedBpmnIds, bpmnKind);
manageOverlays(newlyIdentifiedBpmnIds, bpmnKind);

// keep track of newly identified elements and values
lastIdentifiedBpmnIds = newlyIdentifiedBpmnIds;
}

function updateTextArea(elementsByKinds: BpmnElement[], bpmnKind: string): void {
function updateTextArea(elementsByKinds: BpmnSemantic[], bpmnKind: string): void {
const textArea = document.getElementById('elements-result') as HTMLTextAreaElement;

const textHeader = `Found ${elementsByKinds.length} ${bpmnKind}(s)`;
log(textHeader);
const lines = elementsByKinds.map(elt => ` - ${elt.bpmnSemantic.id}: '${elt.bpmnSemantic.name}'`).join('\n');
const lines = elementsByKinds.map(elt => ` - ${elt.id}: '${elt.name}'`).join('\n');

textArea.value += [textHeader, lines].join('\n') + '\n';
textArea.scrollTop = textArea.scrollHeight;
Expand Down
12 changes: 5 additions & 7 deletions dev/ts/shared/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,20 @@ limitations under the License.

import type { mxCell } from 'mxgraph';
import type {
BpmnElement,
BpmnElementKind,
BpmnSemantic,
FillColorGradient,
FitOptions,
FitType,
GlobalOptions,
GradientDirection,
LoadOptions,
ModelFilter,
Overlay,
PoolFilter,
StyleUpdate,
Version,
ZoomType,
FillColorGradient,
GradientDirection,
} from '../../../src/bpmn-visualization';
import { FlowKind, ShapeBpmnElementKind } from '../../../src/bpmn-visualization';
import { fetchBpmnContent, logDownload, logError, logErrorAndOpenAlert, logStartup } from './internal-helpers';
Expand Down Expand Up @@ -101,8 +100,8 @@ export function zoom(zoomType: ZoomType): void {
log('Zoom done');
}

export function getElementsByKinds(bpmnKinds: BpmnElementKind | BpmnElementKind[]): BpmnElement[] {
return bpmnVisualization.bpmnElementsRegistry.getElementsByKinds(bpmnKinds);
export function getModelElementsByKinds(bpmnKinds: BpmnElementKind | BpmnElementKind[]): BpmnSemantic[] {
return bpmnVisualization.bpmnElementsRegistry.getModelElementsByKinds(bpmnKinds);
}

export function getModelElementsByIds(bpmnIds: string | string[]): BpmnSemantic[] {
Expand Down Expand Up @@ -379,8 +378,7 @@ function updateStyleOfElementsIfRequested(): void {
function retrieveAllBpmnElementIds(): string[] {
log('Retrieving ids of all BPMN elements');
const allKinds = [...Object.values(ShapeBpmnElementKind), ...Object.values(FlowKind)];
const elements = bpmnVisualization.bpmnElementsRegistry.getElementsByKinds(allKinds);
const bpmnElementsIds = elements.map(elt => elt.bpmnSemantic.id);
const bpmnElementsIds = bpmnVisualization.bpmnElementsRegistry.getModelElementsByKinds(allKinds).map(elt => elt.id);
log('All BPMN elements ids retrieved:', bpmnElementsIds.length);
return bpmnElementsIds;
}
24 changes: 14 additions & 10 deletions src/component/registry/bpmn-elements-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,22 @@ export class BpmnElementsRegistry implements CssClassesRegistry, ElementsRegistr
}));
}

getModelElementsByKinds(bpmnKinds: BpmnElementKind | BpmnElementKind[]): BpmnSemantic[] {
return ensureIsArray<BpmnElementKind>(bpmnKinds)
.map(kind => this.htmlElementRegistry.getBpmnHtmlElements(kind))
.flat()
.map(htmlElement => this.getRelatedBpmnSemantic(htmlElement));
}

getElementsByKinds(bpmnKinds: BpmnElementKind | BpmnElementKind[]): BpmnElement[] {
return ensureIsArray<BpmnElementKind>(bpmnKinds)
.map(kind =>
this.htmlElementRegistry.getBpmnHtmlElements(kind).map(htmlElement => ({
htmlElement: htmlElement,
bpmnSemantic: this.bpmnModelRegistry.getBpmnSemantic(htmlElement.getAttribute('data-bpmn-id')),
})),
)
.reduce((accumulator, bpmnElements) => {
accumulator.push(...bpmnElements);
return accumulator;
}, []);
.map(kind => this.htmlElementRegistry.getBpmnHtmlElements(kind))
.flat()
.map(htmlElement => ({ htmlElement, bpmnSemantic: this.getRelatedBpmnSemantic(htmlElement) }));
}

private getRelatedBpmnSemantic(htmlElement: HTMLElement): BpmnSemantic {
return this.bpmnModelRegistry.getBpmnSemantic(htmlElement.getAttribute('data-bpmn-id'));
}

addCssClasses(bpmnElementIds: string | string[], classNames: string | string[]): void {
Expand Down
28 changes: 26 additions & 2 deletions src/component/registry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export interface CssClassesRegistry {
*/
export interface ElementsRegistry {
/**
* Get all model elements by ids. The returned array contains elements in the order of the `bpmnElementIds` parameter.
* Get all model elements in the form of {@link BpmnSemantic} objects corresponding to the identifiers supplied. The returned array contains elements in the order of the `bpmnElementIds` parameter.
*
* Not found elements are not returned as undefined in the array, so the returned array contains at most as many elements as the `bpmnElementIds` parameter.
*
Expand All @@ -144,6 +144,7 @@ export interface ElementsRegistry {
* If you also need to retrieve the related DOM elements and more information, use {@link getElementsByIds} instead.
*
* @param bpmnElementIds The BPMN ID of the element(s) to retrieve.
* @since 0.39.0
*/
getModelElementsByIds(bpmnElementIds: string | string[]): BpmnSemantic[];

Expand All @@ -164,12 +165,31 @@ export interface ElementsRegistry {
* **WARNING**: this method is not designed to accept a large amount of ids. It does DOM lookup to retrieve the HTML elements relative to the BPMN elements.
* Attempts to retrieve too many elements, especially on large BPMN diagram, may lead to performance issues.
*
* @see {@link getModelElementsByIds}
* If you only need to retrieve the BPMN model data, use {@link getModelElementsByIds} instead.
*
* @param bpmnElementIds The BPMN ID of the element(s) to retrieve.
*/
getElementsByIds(bpmnElementIds: string | string[]): BpmnElement[];

/**
* Get all model elements in the form of {@link BpmnSemantic} objects corresponding to the BPMN kinds supplied
*
* ```javascript
* ...
* // Find all elements by specified id or ids
* const bpmnElements1 = bpmnVisualization.bpmnElementsRegistry.getModelElementsByIds('userTask_1');
* const bpmnElements2 = bpmnVisualization.bpmnElementsRegistry.getModelElementsByIds(['startEvent_3', 'userTask_2']);
* // now you can do whatever you want with the elements
* ...
* ```
*
* If you also need to retrieve the related DOM elements and more information, use {@link getElementsByKinds} instead.
*
* @param bpmnKinds The BPMN kind of the element(s) to retrieve.
* @since 0.39.0
*/
getModelElementsByKinds(bpmnKinds: BpmnElementKind | BpmnElementKind[]): BpmnSemantic[];

/**
* Get all elements by kinds.
*
Expand All @@ -182,8 +202,12 @@ export interface ElementsRegistry {
* ...
* ```
*
* If you only need to retrieve the BPMN model data, use {@link getModelElementsByKinds} instead.
*
* **WARNING**: this method is not designed to accept a large amount of types. It does DOM lookup to retrieve the HTML elements relative to the BPMN elements.
* Attempts to retrieve too many elements, especially on large BPMN diagrams, may lead to performance issues.
*
* @param bpmnKinds The BPMN kind of the element(s) to retrieve.
*/
getElementsByKinds(bpmnKinds: BpmnElementKind | BpmnElementKind[]): BpmnElement[];
}
Expand Down
80 changes: 75 additions & 5 deletions test/integration/model.elements.api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,29 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import type { EdgeBpmnSemantic, ShapeBpmnSemantic } from '@lib/component/registry';
import { type BpmnElementKind, FlowKind, ShapeBpmnElementKind } from '@lib/model/bpmn/internal';
import type { BpmnSemantic, EdgeBpmnSemantic, ShapeBpmnSemantic } from '@lib/component/registry';
import { readFileSync } from '@test/shared/file-helper';
import { expectBoundaryEvent, expectSequenceFlow, expectStartEvent, expectSubprocess } from '@test/shared/model/bpmn-semantic-utils';
import {
expectBoundaryEvent,
expectEndEvent,
expectParallelGateway,
expectSequenceFlow,
expectStartEvent,
expectSubprocess,
expectUserTask,
} from '@test/shared/model/bpmn-semantic-utils';
import { initializeBpmnVisualization } from './helpers/bpmn-visualization-initialization';
import { bpmnVisualization } from './helpers/model-expect';

describe('Registry API - retrieve Model Bpmn elements', () => {
bpmnVisualization.load(readFileSync('../fixtures/bpmn/model-complete-semantic.bpmn'));
const bpmnElementsRegistry = bpmnVisualization.bpmnElementsRegistry;

describe('Get by ids', () => {
const bpmnElementsRegistry = bpmnVisualization.bpmnElementsRegistry;

beforeEach(() => {
bpmnVisualization.load(readFileSync('../fixtures/bpmn/model-complete-semantic.bpmn'));
});

test('Pass a single existing id', () => {
const modelElements = bpmnElementsRegistry.getModelElementsByIds('start_event_message_id');

Expand Down Expand Up @@ -71,4 +84,61 @@ describe('Registry API - retrieve Model Bpmn elements', () => {
});
});
});

describe('Get by kinds', () => {
const bv = initializeBpmnVisualization(null);
const bpmnElementsRegistry = bv.bpmnElementsRegistry;

beforeEach(() => {
bv.load(readFileSync('../fixtures/bpmn/registry/1-pool-3-lanes-message-start-end-intermediate-events.bpmn'));
});

test('Pass a single kind with matching elements', () => {
const modelElements = bpmnElementsRegistry.getModelElementsByKinds(ShapeBpmnElementKind.TASK_USER);

expect(modelElements).toHaveLength(4);

expectUserTask(modelElements[0] as ShapeBpmnSemantic, {
id: 'userTask_0',
name: 'User Task 0',
incoming: ['sequenceFlow_lane_1_elt_1'],
outgoing: ['sequenceFlow_lane_1_elt_2'],
});
expectAllElementsWithKind(modelElements, ShapeBpmnElementKind.TASK_USER);
});

test('Pass a single kind without matching elements', () => {
expect(bpmnElementsRegistry.getModelElementsByKinds(ShapeBpmnElementKind.TEXT_ANNOTATION)).toHaveLength(0);
});

test('Pass several kinds, with and without matching elements', () => {
const modelElements = bpmnElementsRegistry.getModelElementsByKinds([
ShapeBpmnElementKind.EVENT_END,
ShapeBpmnElementKind.TEXT_ANNOTATION,
ShapeBpmnElementKind.GATEWAY_PARALLEL,
ShapeBpmnElementKind.GROUP,
FlowKind.SEQUENCE_FLOW,
]);

expect(modelElements).toHaveLength(17);

expectEndEvent(modelElements[0] as ShapeBpmnSemantic, { id: 'endEvent_terminate_1', name: 'terminate end 1' });
expectEndEvent(modelElements[1] as ShapeBpmnSemantic, { id: 'endEvent_message_1', name: 'message end 2' });
expectParallelGateway(modelElements[2] as ShapeBpmnSemantic, { id: 'Gateway_1hq21li', name: 'gateway 2' });
expectSequenceFlow(modelElements[3] as EdgeBpmnSemantic, { id: 'Flow_1noi0ay', source: 'task_1', target: 'gateway_01' });
// all remaining are sequence flows
expectAllElementsWithKind(modelElements.slice(3), FlowKind.SEQUENCE_FLOW);
});

test.each([null, undefined])('Pass nullish parameter: %s', (nullishResetParameter: BpmnElementKind) => {
expect(bpmnElementsRegistry.getModelElementsByKinds(nullishResetParameter)).toHaveLength(0);
});
});
});

function expectAllElementsWithKind(elements: BpmnSemantic[], kind: BpmnElementKind): void {
const array: BpmnElementKind[] = [];
array.length = elements.length;
array.fill(kind);
expect(elements.map(bpmnSemantic => bpmnSemantic.kind)).toIncludeSameMembers(array);
}
10 changes: 10 additions & 0 deletions test/shared/model/bpmn-semantic-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ export function expectBoundaryEvent(bpmnSemantic: ShapeBpmnSemantic, expected: E
expectedFlowNode(bpmnSemantic, expected);
}

export function expectParallelGateway(bpmnSemantic: BpmnSemantic, expected: ExpectedBaseBpmnElement): void {
expectShape(bpmnSemantic, expected);
expect(bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.GATEWAY_PARALLEL);
}

export function expectLane(bpmnSemantic: BpmnSemantic, expected: ExpectedBaseBpmnElement): void {
expectShape(bpmnSemantic, expected);
expect(bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.LANE);
Expand All @@ -102,6 +107,11 @@ export function expectServiceTask(bpmnSemantic: BpmnSemantic, expected: Expected
expect(bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.TASK_SERVICE);
}

export function expectUserTask(bpmnSemantic: ShapeBpmnSemantic, expected: ExpectedFlowNodeElement): void {
expect(bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.TASK_USER);
expectedFlowNode(bpmnSemantic, expected);
}

export function expectSubprocess(bpmnSemantic: ShapeBpmnSemantic, expected: ExpectedFlowNodeElement): void {
expectedFlowNode(bpmnSemantic, expected);
expect(bpmnSemantic.kind).toEqual(ShapeBpmnElementKind.SUB_PROCESS);
Expand Down

0 comments on commit 5190882

Please sign in to comment.