Skip to content

Commit

Permalink
feat: allow conditional flow only if source is (in/ex)clusive gateway
Browse files Browse the repository at this point in the history
Closes #82
  • Loading branch information
philippfromme authored and merge-me[bot] committed May 17, 2023
1 parent adf4185 commit 02fcc42
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 18 deletions.
56 changes: 38 additions & 18 deletions rules/sequence-flow-condition.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,53 @@
const { isAny } = require('bpmnlint-utils');
const {
is,
isAny
} = require('bpmnlint-utils');

const { hasProperties } = require('./utils/element');
const {
ERROR_TYPES,
hasProperties
} = require('./utils/element');

const { reportErrors } = require('./utils/reporter');

const { skipInNonExecutableProcess } = require('./utils/rule');

module.exports = skipInNonExecutableProcess(function() {
function check(node, reporter) {
if (!isAny(node, [ 'bpmn:ExclusiveGateway', 'bpmn:InclusiveGateway' ])) {
return;
}

const outgoing = node.get('outgoing');

if (outgoing && outgoing.length > 1) {
for (let sequenceFlow of outgoing) {
if (node.get('default') !== sequenceFlow) {
const errors = hasProperties(sequenceFlow, {
conditionExpression: {
required: true
if (isAny(node, [ 'bpmn:ExclusiveGateway', 'bpmn:InclusiveGateway' ])) {
const outgoing = node.get('outgoing');

if (outgoing && outgoing.length > 1) {
for (let sequenceFlow of outgoing) {
if (node.get('default') !== sequenceFlow) {
const errors = hasProperties(sequenceFlow, {
conditionExpression: {
required: true
}
}, sequenceFlow);

if (errors.length) {
reportErrors(sequenceFlow, reporter, errors);
}
}, sequenceFlow);

if (errors.length) {
reportErrors(sequenceFlow, reporter, errors);
}
}
}
} else if (is(node, 'bpmn:SequenceFlow')) {
const source = node.get('sourceRef'),
conditionExpression = node.get('conditionExpression');

if (!isAny(source, [ 'bpmn:ExclusiveGateway', 'bpmn:InclusiveGateway' ]) && conditionExpression) {
reportErrors(node, reporter, {
message: 'Property <conditionExpression> only allowed if source is of type <bpmn:ExclusiveGateway> or <bpmn:InclusiveGateway>',
path: [ 'conditionExpression' ],
data: {
type: ERROR_TYPES.PROPERTY_NOT_ALLOWED,
node: node,
parentNode: null,
property: 'conditionExpression'
}
});
}
}
}

Expand Down
56 changes: 56 additions & 0 deletions test/camunda-cloud/integration/sequence-flow-condition-errors.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0pt29rm" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.11.0-dev" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.2.0">
<bpmn:process id="Process_1" isExecutable="true">
<bpmn:exclusiveGateway id="ExclusiveGateway_1">
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
<bpmn:outgoing>SequenceFlow_2</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:endEvent id="EndEvent_1">
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_1" sourceRef="ExclusiveGateway_1" targetRef="EndEvent_1" />
<bpmn:endEvent id="Event_07d4ll9EndEvent_1">
<bpmn:incoming>SequenceFlow_2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_2" sourceRef="ExclusiveGateway_1" targetRef="Event_07d4ll9EndEvent_1" />
<bpmn:eventBasedGateway id="EventBasedGateway_1">
<bpmn:outgoing>SequenceFlow_3</bpmn:outgoing>
</bpmn:eventBasedGateway>
<bpmn:receiveTask id="ReceiveTask_1">
<bpmn:incoming>SequenceFlow_3</bpmn:incoming>
</bpmn:receiveTask>
<bpmn:sequenceFlow id="SequenceFlow_3" sourceRef="EventBasedGateway_1" targetRef="ReceiveTask_1" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="Gateway_00nyrno_di" bpmnElement="ExclusiveGateway_1" isMarkerVisible="true">
<dc:Bounds x="155" y="85" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1i2qlqn_di" bpmnElement="EndEvent_1">
<dc:Bounds x="262" y="92" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_07d4ll9_di" bpmnElement="Event_07d4ll9EndEvent_1">
<dc:Bounds x="262" y="202" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_0348o9e_di" bpmnElement="EventBasedGateway_1">
<dc:Bounds x="155" y="355" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1vk0ftd_di" bpmnElement="ReceiveTask_1">
<dc:Bounds x="260" y="340" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0neb115_di" bpmnElement="SequenceFlow_1">
<di:waypoint x="205" y="110" />
<di:waypoint x="262" y="110" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0jp993u_di" bpmnElement="SequenceFlow_2">
<di:waypoint x="180" y="135" />
<di:waypoint x="180" y="220" />
<di:waypoint x="262" y="220" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1uii19h_di" bpmnElement="SequenceFlow_3">
<di:waypoint x="205" y="380" />
<di:waypoint x="260" y="380" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
58 changes: 58 additions & 0 deletions test/camunda-cloud/integration/sequence-flow-condition.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0pt29rm" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.11.0-dev" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.2.0">
<bpmn:process id="Process_1" isExecutable="true">
<bpmn:exclusiveGateway id="ExclusiveGateway_1" default="SequenceFlow_1">
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
<bpmn:outgoing>SequenceFlow_2</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:endEvent id="EndEvent_1">
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_1" sourceRef="ExclusiveGateway_1" targetRef="EndEvent_1" />
<bpmn:endEvent id="Event_07d4ll9EndEvent_1">
<bpmn:incoming>SequenceFlow_2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_2" sourceRef="ExclusiveGateway_1" targetRef="Event_07d4ll9EndEvent_1">
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=foo</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:receiveTask id="ReceiveTask_1">
<bpmn:incoming>SequenceFlow_3</bpmn:incoming>
</bpmn:receiveTask>
<bpmn:sequenceFlow id="SequenceFlow_3" sourceRef="ExclusiveGateway_2" targetRef="ReceiveTask_1" />
<bpmn:exclusiveGateway id="ExclusiveGateway_2">
<bpmn:outgoing>SequenceFlow_3</bpmn:outgoing>
</bpmn:exclusiveGateway>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="Gateway_00nyrno_di" bpmnElement="ExclusiveGateway_1" isMarkerVisible="true">
<dc:Bounds x="155" y="85" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1i2qlqn_di" bpmnElement="EndEvent_1">
<dc:Bounds x="262" y="92" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_07d4ll9_di" bpmnElement="Event_07d4ll9EndEvent_1">
<dc:Bounds x="262" y="202" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1vk0ftd_di" bpmnElement="ReceiveTask_1">
<dc:Bounds x="260" y="340" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_0k8fwif_di" bpmnElement="ExclusiveGateway_2" isMarkerVisible="true">
<dc:Bounds x="155" y="355" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0neb115_di" bpmnElement="SequenceFlow_1">
<di:waypoint x="205" y="110" />
<di:waypoint x="262" y="110" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0jp993u_di" bpmnElement="SequenceFlow_2">
<di:waypoint x="180" y="135" />
<di:waypoint x="180" y="220" />
<di:waypoint x="262" y="220" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1uii19h_di" bpmnElement="SequenceFlow_3">
<di:waypoint x="205" y="380" />
<di:waypoint x="260" y="380" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
75 changes: 75 additions & 0 deletions test/camunda-cloud/integration/sequence-flow-condition.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const { expect } = require('chai');

const Linter = require('bpmnlint/lib/linter');

const NodeResolver = require('bpmnlint/lib/resolver/node-resolver');

const { readModdle } = require('../../helper');

const versions = [
'1.0',
'1.1',
'1.2',
'1.3',
'8.0',
'8.1',
'8.2',
'8.3'
];

describe('integration - sequence-flow-condition', function() {

versions.forEach(function(version) {

let linter;

beforeEach(function() {
linter = new Linter({
config: {
extends: `plugin:camunda-compat/camunda-cloud-${ version.replace('.', '-') }`
},
resolver: new NodeResolver()
});
});


describe(`Camunda Cloud ${ version }`, function() {

describe('no errors', function() {

it('should not have errors', async function() {

// given
const { root } = await readModdle('test/camunda-cloud/integration/sequence-flow-condition.bpmn');

// when
const reports = await linter.lint(root);

// then
expect(reports[ 'camunda-compat/sequence-flow-condition' ]).not.to.exist;
});

});


describe('errors', function() {

it('should have errors', async function() {

// given
const { root } = await readModdle('test/camunda-cloud/integration/sequence-flow-condition-errors.bpmn');

// when
const reports = await linter.lint(root);

// then
expect(reports[ 'camunda-compat/sequence-flow-condition' ]).to.exist;
});

});

});

});

});
27 changes: 27 additions & 0 deletions test/camunda-cloud/sequence-flow-condition.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,33 @@ const invalid = [
}
}
]
},
{
name: 'task (1 outgoing sequence flow with condition)',
moddleElement: createModdle(createProcess(`
<bpmn:task id="Task_1">
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
</bpmn:task>
<bpmn:endEvent id="EndEvent_1">
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_1" sourceRef="Task_1" targetRef="EndEvent_1">
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=foo</bpmn:conditionExpression>
</bpmn:sequenceFlow>
`)),
report: {
id: 'SequenceFlow_1',
message: 'Property <conditionExpression> only allowed if source is of type <bpmn:ExclusiveGateway> or <bpmn:InclusiveGateway>',
path: [
'conditionExpression'
],
data: {
type: ERROR_TYPES.PROPERTY_NOT_ALLOWED,
node: 'SequenceFlow_1',
parentNode: null,
property: 'conditionExpression'
}
}
}
];

Expand Down

0 comments on commit 02fcc42

Please sign in to comment.