Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] Detect the Sequential Multi-instance Marker of an Activity #468

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/bpmn-support.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ The default rendering uses `white` as fill color and `black` as stroke color.
The size of the icon will be changed later to be consistent between shapes. +
The icon is derived from https://github.com/jgraph/drawio/blob/9394fb0f1430d2c869865827b2bbef5639f63478/src/main/webapp/stencils/bpmn.xml#L543[loop] of draw.io

|Sequential Multi-Instance Marker
|
|The icon position is currently not centered but absolute, but this will be managed later.
|===


Expand Down
13 changes: 8 additions & 5 deletions src/component/mxgraph/shape/activity-shapes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,28 @@ export abstract class BaseActivityShape extends mxRectangleShape {

public paintForeground(c: mxAbstractCanvas2D, x: number, y: number, w: number, h: number): void {
super.paintForeground(c, x, y, w, h);
this.paintMarkerIcons(c, x, y, w, h);
this.paintMarkerIcons(buildPaintParameter(c, x, y, w, h, this, 0.17, false, 1.5));
}

protected paintMarkerIcons(c: mxAbstractCanvas2D, x: number, y: number, w: number, h: number): void {
protected paintMarkerIcons(paintParameter: PaintParameter): void {
const markers = StyleUtils.getBpmnMarkers(this.style);
if (markers) {
markers.split(',').forEach(marker => {
switch (marker) {
case ShapeBpmnMarkerKind.LOOP:
this.iconPainter.paintLoopIcon(buildPaintParameter(c, x, y, w, h, this, 0.17, false, 1.5));
this.iconPainter.paintLoopIcon(paintParameter);
break;
case ShapeBpmnMarkerKind.MULTI_INSTANCE_SEQUENTIAL:
this.iconPainter.paintSequentialMultiInstanceIcon(paintParameter);
break;
case 'expand':
this.iconPainter.paintExpandIcon(buildPaintParameter(c, x, y, w, h, this, 0.17, false, 1.5));
this.iconPainter.paintExpandIcon(paintParameter);
break;
}
// Restore original configuration to avoid side effects if the iconPainter changed the canvas configuration (colors, ....)
// TODO missing mxShape.configureCanvas in mxgraph-type-definitions (this will replace explicit function calls)
// this.configureCanvas(c, x, y, w, h);
c.setStrokeColor(StyleUtils.getStrokeColor(this.style));
paintParameter.c.setStrokeColor(StyleUtils.getStrokeColor(this.style));
});
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/component/mxgraph/shape/render/IconPainter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,28 @@ export default class IconPainter {
canvas.close();
canvas.fillAndStroke();
}

paintSequentialMultiInstanceIcon({ c, ratioFromParent, shape, icon }: PaintParameter): void {
const originalIconSize = { width: 16, height: 16 };
const canvas = new BpmnCanvas({
mxCanvas: c,
shapeConfiguration: shape,
iconConfiguration: {
originalSize: originalIconSize,
style: icon,
ratioFromShape: ratioFromParent,
},
});
canvas.setIconOriginToShapeBottomLeft();

const w = originalIconSize.width;
const h = originalIconSize.height;

// Temporary render
c.setStrokeColor('Chartreuse');
canvas.roundrect(0, 0, w, h, 2, 2);
canvas.stroke();
}
}

export class IconPainterProvider {
Expand Down
14 changes: 10 additions & 4 deletions src/component/parser/json/converter/ProcessConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,16 @@ export default class ProcessConverter {
shapeBpmnElement = new ShapeBpmnElement(bpmnElement.id, name, kind, processId, bpmnElement.instantiate);
}

// @ts-ignore We know that the standardLoopCharacteristics field is not on all types, but it's already tested
const standardLoopCharacteristics = bpmnElement.standardLoopCharacteristics;
if (ShapeUtil.isActivity(kind) && (standardLoopCharacteristics || standardLoopCharacteristics === '')) {
shapeBpmnElement.marker = ShapeBpmnMarkerKind.LOOP;
if (ShapeUtil.isActivity(kind)) {
// @ts-ignore We know that the standardLoopCharacteristics field is not on all types, but it's already tested
const standardLoopCharacteristics = bpmnElement.standardLoopCharacteristics;
// @ts-ignore We know that the multiInstanceLoopCharacteristics field is not on all types, but it's already tested
const multiInstanceLoopCharacteristics = ensureIsArray(bpmnElement.multiInstanceLoopCharacteristics, true)[0];
if (standardLoopCharacteristics || standardLoopCharacteristics === '') {
shapeBpmnElement.marker = ShapeBpmnMarkerKind.LOOP;
} else if (multiInstanceLoopCharacteristics && multiInstanceLoopCharacteristics.isSequential) {
shapeBpmnElement.marker = ShapeBpmnMarkerKind.MULTI_INSTANCE_SEQUENTIAL;
}
}

// @ts-ignore We know that the default field is not on all types, but it's already tested
Expand Down
1 change: 1 addition & 0 deletions src/model/bpmn/shape/ShapeBpmnMarkerKind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
*/
export enum ShapeBpmnMarkerKind {
LOOP = 'loop',
MULTI_INSTANCE_SEQUENTIAL = 'sequential multi instance',
}
68 changes: 68 additions & 0 deletions test/e2e/mxGraph.model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,13 @@ describe('mxGraph model', () => {
isExpanded: true,
marker: ShapeBpmnMarkerKind.LOOP,
});
expectModelContainsSubProcess('expanded_embedded_sub_process_with_sequential_multi_instance_id', {
kind: null,
subProcessKind: ShapeBpmnSubProcessKind.EMBEDDED,
label: 'Expanded Embedded Sub-Process With Sequential Multi-instance',
isExpanded: true,
marker: ShapeBpmnMarkerKind.MULTI_INSTANCE_SEQUENTIAL,
});

expectModelContainsSubProcess('collapsed_embedded_sub_process_id', {
kind: null,
Expand All @@ -428,6 +435,13 @@ describe('mxGraph model', () => {
isExpanded: false,
marker: ShapeBpmnMarkerKind.LOOP,
});
expectModelContainsSubProcess('collapsed_embedded_sub_process_with_sequential_multi_instance_id', {
kind: null,
subProcessKind: ShapeBpmnSubProcessKind.EMBEDDED,
label: 'Collapsed Embedded Sub-Process With Sequential Multi-instance',
isExpanded: false,
marker: ShapeBpmnMarkerKind.MULTI_INSTANCE_SEQUENTIAL,
});

expectModelContainsSubProcess('expanded_event_sub_process_id', {
kind: null,
Expand All @@ -442,6 +456,13 @@ describe('mxGraph model', () => {
isExpanded: true,
marker: ShapeBpmnMarkerKind.LOOP,
});
expectModelContainsSubProcess('expanded_event_sub_process_with_sequential_multi_instance_id', {
kind: null,
subProcessKind: ShapeBpmnSubProcessKind.EVENT,
label: 'Expanded Event Sub-Process With Sequential Multi-instance',
isExpanded: true,
marker: ShapeBpmnMarkerKind.MULTI_INSTANCE_SEQUENTIAL,
});

expectModelContainsSubProcess('collapsed_event_sub_process_id', {
kind: null,
Expand All @@ -456,6 +477,13 @@ describe('mxGraph model', () => {
isExpanded: false,
marker: ShapeBpmnMarkerKind.LOOP,
});
expectModelContainsSubProcess('collapsed_event_sub_process_with_sequential_multi_instance_id', {
kind: null,
subProcessKind: ShapeBpmnSubProcessKind.EVENT,
label: 'Collapsed Event Sub-Process With Sequential Multi-instance',
isExpanded: false,
marker: ShapeBpmnMarkerKind.MULTI_INSTANCE_SEQUENTIAL,
});

// activity
expectModelContainsShape('task_1', {
Expand Down Expand Up @@ -483,6 +511,19 @@ describe('mxGraph model', () => {
label: 'Task With Loop',
marker: ShapeBpmnMarkerKind.LOOP,
});
expectModelContainsShape('task_with_sequential_multi_instance', {
kind: ShapeBpmnElementKind.TASK,
font: {
isBold: false,
isItalic: false,
isStrikeThrough: false,
isUnderline: true,
name: 'Arial',
size: 11.0,
},
label: 'Task With Sequential Multi-instance',
marker: ShapeBpmnMarkerKind.MULTI_INSTANCE_SEQUENTIAL,
});

expectModelContainsShape('serviceTask_2', { kind: ShapeBpmnElementKind.TASK_SERVICE, font: expectedBoldFont, label: 'Service Task 2' });
expectModelContainsShape('serviceTask_with_loop', {
Expand All @@ -491,6 +532,12 @@ describe('mxGraph model', () => {
label: 'Service Task With Loop',
marker: ShapeBpmnMarkerKind.LOOP,
});
expectModelContainsShape('serviceTask_with_sequential_multi_instance', {
kind: ShapeBpmnElementKind.TASK_SERVICE,
font: expectedBoldFont,
label: 'Service Task With Sequential Multi-instance',
marker: ShapeBpmnMarkerKind.MULTI_INSTANCE_SEQUENTIAL,
});

expectModelContainsShape('userTask_3', { kind: ShapeBpmnElementKind.TASK_USER, font: expectedBoldFont, label: 'User Task 3' });
expectModelContainsShape('userTask_with_loop', {
Expand All @@ -499,23 +546,44 @@ describe('mxGraph model', () => {
label: 'User Task With Loop',
marker: ShapeBpmnMarkerKind.LOOP,
});
expectModelContainsShape('userTask_with_sequential_multi_instance', {
kind: ShapeBpmnElementKind.TASK_USER,
font: expectedBoldFont,
label: 'User Task With Sequential Multi-instance',
marker: ShapeBpmnMarkerKind.MULTI_INSTANCE_SEQUENTIAL,
});

expectModelContainsShape('callActivity_1', { kind: ShapeBpmnElementKind.CALL_ACTIVITY, label: 'Call Activity Collapsed' });
expectModelContainsShape('callActivity_with_loop', { kind: ShapeBpmnElementKind.CALL_ACTIVITY, label: 'Call Activity Collapsed With Loop', marker: ShapeBpmnMarkerKind.LOOP });
expectModelContainsShape('callActivity_with_sequential_multi_instance', {
kind: ShapeBpmnElementKind.CALL_ACTIVITY,
label: 'Call Activity Collapsed With Sequential Multi-instance',
marker: ShapeBpmnMarkerKind.MULTI_INSTANCE_SEQUENTIAL,
});

expectModelContainsShape('receiveTask_not_instantiated', { kind: ShapeBpmnElementKind.TASK_RECEIVE, label: 'Not instantiated Receive Task' });
expectModelContainsShape('receiveTask_not_instantiated_with_loop', {
kind: ShapeBpmnElementKind.TASK_RECEIVE,
label: 'Not instantiated Receive Task With Loop',
marker: ShapeBpmnMarkerKind.LOOP,
});
expectModelContainsShape('receiveTask_not_instantiated_with_sequential_multi_instance', {
kind: ShapeBpmnElementKind.TASK_RECEIVE,
label: 'Not instantiated Receive Task With Sequential Multi-instance',
marker: ShapeBpmnMarkerKind.MULTI_INSTANCE_SEQUENTIAL,
});

expectModelContainsShape('receiveTask_instantiated', { kind: ShapeBpmnElementKind.TASK_RECEIVE, label: 'Instantiated Receive Task' });
expectModelContainsShape('receiveTask_instantiated_with_loop', {
kind: ShapeBpmnElementKind.TASK_RECEIVE,
label: 'Instantiated Receive Task With Loop',
marker: ShapeBpmnMarkerKind.LOOP,
});
expectModelContainsShape('receiveTask_instantiated_with_sequential_multi_instance', {
kind: ShapeBpmnElementKind.TASK_RECEIVE,
label: 'Instantiated Receive Task With Sequential Multi-instance',
marker: ShapeBpmnMarkerKind.MULTI_INSTANCE_SEQUENTIAL,
});

// text annotation
expectModelContainsShape('text_annotation_id_1', { kind: ShapeBpmnElementKind.TEXT_ANNOTATION, label: 'Annotation' });
Expand Down
Loading