Skip to content

Commit

Permalink
test with bpmn-elements@14 and remove expireAt formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
paed01 committed May 1, 2024
1 parent 7c7f83c commit e10132c
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 55 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

# unreleased

- update all dev dependencies and run tests
# 8.0.0

Stop execution if an invalid time duration, cycle, or date is encountered.

## Breaking

- invalid TimerEventDefinition timer type value stops execution if using [`bpmn-elements@14`](https://github.com/paed01/bpmn-elements/blob/master/CHANGELOG.md)
- remove expireAt formatting from events with TimerEventDefinion(s)

# 7.1.0

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onify/flow-extensions",
"version": "7.1.0",
"version": "8.0.0",
"description": "Onify Flow extensions",
"type": "module",
"module": "src/index.js",
Expand Down Expand Up @@ -58,9 +58,10 @@
"prettier": "^3.2.4"
},
"peerDependencies": {
"bpmn-elements": ">=8"
"bpmn-elements": "^14.0.0"
},
"dependencies": {
"@0dep/piso": "^0.1.3",
"cron-parser": "^4.9.0"
},
"files": [
Expand Down
15 changes: 13 additions & 2 deletions src/OnifySequenceFlow.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,26 @@ export class OnifySequenceFlow extends SequenceFlow {
const properties = this.extensions.properties;
if (!properties) return super.evaluate(fromMessage, callback);

super.evaluate(fromMessage, (err, result) => {
try {
const preProperties = properties.resolve(this.getApi(fromMessage));
var evaluateMessage = fromMessage;
evaluateMessage.content.properties = {
...fromMessage.content.properties,
...preProperties,
};
} catch (err) {
return callback(err);
}

super.evaluate(evaluateMessage, (err, result) => {
if (err) return callback(err);

try {
let overriddenResult = result ? {} : false;
if (result) {
overriddenResult = {
...(typeof result === 'object' && result),
properties: properties.resolve(this.getApi(fromMessage)),
properties: properties.resolve(this.getApi(evaluateMessage)),
};
}
return callback(err, overriddenResult);
Expand Down
4 changes: 3 additions & 1 deletion src/OnifyTimerEventDefinition.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TimerEventDefinition } from 'bpmn-elements';
import cronParser from 'cron-parser';
import { TimerEventDefinition } from 'bpmn-elements';

export class OnifyTimerEventDefinition extends TimerEventDefinition {
constructor(activity, def) {
Expand All @@ -14,12 +14,14 @@ export class OnifyTimerEventDefinition extends TimerEventDefinition {
if (cron.expressions?.length) {
// cronParser.parseString expressions disregards seconds, so we have to parse again
const expireAt = cronParser.parseExpression(value).next().toDate();

return {
expireAt,
delay: expireAt - Date.now(),
};
}
}

return super.parse(timerType, value);
}
}
19 changes: 1 addition & 18 deletions src/formatters.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import cronParser from 'cron-parser';

const iso8601cycle = /^\s*(R\d+\/)?P\w+/i;

export class FormatActivity {
constructor(activity) {
this.activity = activity;
Expand All @@ -10,7 +6,7 @@ export class FormatActivity {
let timeCycles;
if (activity.eventDefinitions) {
for (const ed of activity.eventDefinitions.filter((e) => e.type === 'bpmn:TimerEventDefinition')) {
if (ed.supports?.includes('cron')) continue;
if (!ed.supports?.includes('cron')) continue;
if (!('timeCycle' in ed)) continue;
timeCycles = timeCycles || [];
timeCycles.push(ed.timeCycle);
Expand All @@ -28,25 +24,12 @@ export class FormatActivity {
if (assignee) assigneeValue = elementApi.resolveExpression(assignee);
if (documentation) description = documentation[0]?.text;

let expireAt;
const timeCycles = this.timeCycles;
if (timeCycles) {
for (const cycle of timeCycles) {
const cron = elementApi.resolveExpression(cycle);
if (!cron || iso8601cycle.test(cron)) continue;

const expireAtDt = cronParser.parseExpression(cron).next().toDate();
if (!expireAt || expireAtDt < expireAt) expireAt = expireAtDt;
}
}

return {
...(this.resultVariable && { resultVariable: this.resultVariable }),
...(scheduledStart && activity.parent.type === 'bpmn:Process' && { scheduledStart }),
...(user?.length && { candidateUsers: user }),
...(groups?.length && { candidateGroups: groups }),
...(!elementApi.content.description && description && { description: elementApi.resolveExpression(description) }),
...(expireAt && { expireAt }),
...(assigneeValue && { assignee: assigneeValue }),
};
}
Expand Down
4 changes: 2 additions & 2 deletions test/features/process-history-ttl-feaure.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { parseInterval } from '@0dep/piso';
import testHelpers from '../helpers/testHelpers.js';
import { ISODuration } from 'bpmn-elements';

Feature('Process history ttl', () => {
Scenario('Flow with process history time to live with number of days', () => {
Expand Down Expand Up @@ -32,7 +32,7 @@ Feature('Process history ttl', () => {
});

And('it is parsable as ISO8601 duration', () => {
expect(ISODuration.parse(historyTtl).days).to.equal(180);
expect(parseInterval(historyTtl).duration.result.D).to.equal(180);
});

let processEnd, state;
Expand Down
152 changes: 152 additions & 0 deletions test/features/sequence-flow-feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ Feature('Sequence flow', () => {
});

Scenario('Sequence flow with properties', () => {
/** @type {import('bpmn-elements').Definition} */
let flow;
const messages = [];
Given('a flow with one conditional sequence flows with properties', async () => {
Expand Down Expand Up @@ -220,6 +221,78 @@ Feature('Sequence flow', () => {
expect(message.content.inbound[0].properties).to.not.be.ok;
});

Given('flow with condition that address properties', async () => {
const source = `<?xml version="1.0" encoding="UTF-8"?>
<definitions id="def_0" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" targetNamespace="http://bpmn.io/schema/bpmn">
<process id="my-process" isExecutable="true">
<startEvent id="start" default="to-join2">
<extensionElements>
<camunda:properties>
<camunda:property name="prop1" value="\${false}" />
</camunda:properties>
</extensionElements>
</startEvent>
<sequenceFlow id="to-join1" sourceRef="start" targetRef="join">
<conditionExpression xsi:type="tFormalExpression" language="js">
next(null, content.properties.prop1);
</conditionExpression>
<extensionElements>
<camunda:properties>
<camunda:property name="prop1" value="\${true}" />
</camunda:properties>
</extensionElements>
</sequenceFlow>
<sequenceFlow id="to-join2" sourceRef="start" targetRef="join" />
<parallelGateway id="join" />
<sequenceFlow id="to-task" sourceRef="join" targetRef="task" />
<task id="task" default="to-end2" />
<sequenceFlow id="to-end1" sourceRef="task" targetRef="end">
<conditionExpression xsi:type="tFormalExpression" language="js">
next(null, content.inbound[0].result);
</conditionExpression>
<extensionElements>
<camunda:properties>
<camunda:property name="prop2" value="\${true}" />
</camunda:properties>
</extensionElements>
</sequenceFlow>
<sequenceFlow id="to-end2" sourceRef="task" targetRef="end" />
<endEvent id="end" />
</process>
</definitions>`;

flow = await testHelpers.getOnifyFlow(source, {
types: {
SequenceFlow: OnifySequenceFlow,
},
});
});

let sourceActivity;
let sourceEnd;
When('ran', async () => {
sourceActivity = flow.getActivityById('start');

sourceEnd = sourceActivity.waitFor('end');

end = flow.waitFor('end');
await flow.run();
});

Then('expected sequence flow was taken', async () => {
await end;
expect(sourceActivity.outbound[0].counters).to.have.property('take', 1);
});

And('sequence flow source end message kept property values', async () => {
const source = await sourceEnd;
expect(source.content.properties).to.have.property('prop1', false);
});

And('second sequence flow that address first sequence flow result was taken', () => {
expect(flow.getActivityById('join').outbound[0].counters).to.have.property('take', 1);
});

Given('a flow with malformatted sequence flow property expression', async () => {
const source = `<?xml version="1.0" encoding="UTF-8"?>
<definitions id="def_0" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" targetNamespace="http://bpmn.io/schema/bpmn">
Expand Down Expand Up @@ -288,6 +361,85 @@ Feature('Sequence flow', () => {
});
});

Scenario('a flow with sequence flow property expression function', () => {
let source;
let options;
let flow;
Given('a flow matching scenario', () => {
source = `<?xml version="1.0" encoding="UTF-8"?>
<definitions id="def_0" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" targetNamespace="http://bpmn.io/schema/bpmn">
<process id="my-process" isExecutable="true">
<startEvent id="start" />
<sequenceFlow id="to-end" sourceRef="start" targetRef="end">
<extensionElements>
<camunda:properties>
<camunda:property name="prop1" value="\${environment.services.propfn(environment.variables)}" />
</camunda:properties>
</extensionElements>
</sequenceFlow>
<endEvent id="end" />
</process>
</definitions>`;

options = {
services: {
propfn(variables) {
const count = ++variables.count;
if (count > 2) throw new Error('Testing');
return true;
},
},
variables: {
count: 0,
},
types: {
SequenceFlow: OnifySequenceFlow,
},
};
});

let end;
When('ran', async () => {
flow = await testHelpers.getOnifyFlow(source);
end = flow.waitFor('end');
await flow.run();
});

Then('flow completes', () => {
return end;
});

When('ran again and expression property function fails when resolving properties', async () => {
flow = await testHelpers.getOnifyFlow(source, options);

flow.environment.variables.count = 2;

end = flow.waitFor('end').catch((err) => err);

await flow.run();
});

Then('flow run fails due to expression throwing error', async () => {
const error = await end;
expect(error).to.match(/Testing/);
});

When('ran again and expression property function fails when resolving result', async () => {
flow = await testHelpers.getOnifyFlow(source, options);

flow.environment.variables.count = 1;

end = flow.waitFor('end').catch((err) => err);

await flow.run();
});

Then('flow run fails due to expression throwing error', async () => {
const error = await end;
expect(error).to.match(/Testing/);
});
});

Scenario('Sequence flow with execution listeners', () => {
let flow;
const calls = [];
Expand Down
Loading

0 comments on commit e10132c

Please sign in to comment.