diff --git a/index.js b/index.js
index 24144299..a7e2a8d0 100644
--- a/index.js
+++ b/index.js
@@ -12,6 +12,7 @@ const camundaCloud10Rules = withConfig({
'message-reference': 'error',
'no-candidate-users': 'error',
'no-expression': 'error',
+ 'no-task-schedule': 'error',
'no-template': 'error',
'no-zeebe-properties': 'error',
'sequence-flow-condition': 'error',
@@ -37,8 +38,12 @@ const camundaCloud81Rules = withConfig({
}, { version: '8.1' });
const camundaCloud82Rules = withConfig({
- ...omit(camundaCloud81Rules, 'no-candidate-users'),
- 'escalation-reference': 'error'
+ ...omit(camundaCloud81Rules, [
+ 'no-candidate-users',
+ 'no-task-schedule'
+ ]),
+ 'escalation-reference': 'error',
+ 'task-schedule': 'error'
}, { version: '8.2' });
const camundaPlatform719Rules = withConfig({
diff --git a/rules/no-task-schedule.js b/rules/no-task-schedule.js
new file mode 100644
index 00000000..7b164a23
--- /dev/null
+++ b/rules/no-task-schedule.js
@@ -0,0 +1,19 @@
+const { hasNoExtensionElement } = require('./utils/element');
+
+const { reportErrors } = require('./utils/reporter');
+
+const { skipInNonExecutableProcess } = require('./utils/rule');
+
+module.exports = skipInNonExecutableProcess(function() {
+ function check(node, reporter) {
+ const errors = hasNoExtensionElement(node, 'zeebe:TaskSchedule', node, '8.2');
+
+ if (errors && errors.length) {
+ reportErrors(node, reporter, errors);
+ }
+ }
+
+ return {
+ check
+ };
+});
\ No newline at end of file
diff --git a/rules/task-schedule.js b/rules/task-schedule.js
new file mode 100644
index 00000000..dba2c0a0
--- /dev/null
+++ b/rules/task-schedule.js
@@ -0,0 +1,68 @@
+const { is } = require('bpmnlint-utils');
+
+const { isDefined } = require('min-dash');
+
+const {
+ findExtensionElement,
+ hasExpression
+} = require('./utils/element');
+
+const { validateDate: validateISO8601Date } = require('./utils/iso8601');
+
+const { reportErrors } = require('./utils/reporter');
+
+const { skipInNonExecutableProcess } = require('./utils/rule');
+
+module.exports = skipInNonExecutableProcess(function() {
+ function check(node, reporter) {
+ if (!is(node, 'bpmn:UserTask')) {
+ return;
+ }
+
+ const taskSchedule = findExtensionElement(node, 'zeebe:TaskSchedule');
+
+ if (!taskSchedule) {
+ return;
+ }
+
+ const dueDate = taskSchedule.get('dueDate');
+
+ let errors = [];
+
+ if (isDefined(dueDate)) {
+ errors = [
+ ...errors,
+ ...hasExpression(taskSchedule, 'dueDate', {
+ allowed: date => isValidDate(date)
+ }, node)
+ ];
+ }
+
+ const followUpDate = taskSchedule.get('followUpDate');
+
+ if (isDefined(followUpDate)) {
+ errors = [
+ ...errors,
+ ...hasExpression(taskSchedule, 'followUpDate', {
+ allowed: date => isValidDate(date)
+ }, node)
+ ];
+ }
+
+ if (errors.length) {
+ reportErrors(node, reporter, errors);
+ }
+ }
+
+ return {
+ check
+ };
+});
+
+function isValidDate(value) {
+ return isExpression(value) || validateISO8601Date(value);
+}
+
+function isExpression(value) {
+ return value.startsWith('=');
+}
\ No newline at end of file
diff --git a/rules/utils/element.js b/rules/utils/element.js
index cd002ce5..2306f07b 100644
--- a/rules/utils/element.js
+++ b/rules/utils/element.js
@@ -363,11 +363,15 @@ module.exports.hasExpression = function(node, propertyName, check, parentNode =
throw new Error('Expression not found');
}
- const body = expression.get('body');
+ let propertyValue = expression;
+
+ if (is(expression, 'bpmn:Expression')) {
+ propertyValue = expression.get('body');
+ }
const path = getPath(node, parentNode);
- if (!body) {
+ if (!propertyValue) {
if (check.required !== false) {
return [
{
@@ -377,7 +381,7 @@ module.exports.hasExpression = function(node, propertyName, check, parentNode =
: null,
data: {
type: ERROR_TYPES.EXPRESSION_REQUIRED,
- node: expression,
+ node: is(expression, 'bpmn:Expression') ? expression : node,
parentNode,
property: propertyName
}
@@ -388,7 +392,7 @@ module.exports.hasExpression = function(node, propertyName, check, parentNode =
return [];
}
- const allowed = check.allowed(body);
+ const allowed = check.allowed(propertyValue);
if (allowed !== true) {
let allowedVersion = null;
@@ -400,14 +404,14 @@ module.exports.hasExpression = function(node, propertyName, check, parentNode =
return [
{
message: allowedVersion
- ? `Expression value of <${ body }> only allowed by Camunda Platform ${ allowedVersion }`
- : `Expression value of <${ body }> not allowed`,
+ ? `Expression value of <${ propertyValue }> only allowed by Camunda Platform ${ allowedVersion }`
+ : `Expression value of <${ propertyValue }> not allowed`,
path: path
? [ ...path, propertyName ]
: null,
data: addAllowedVersion({
type: ERROR_TYPES.EXPRESSION_VALUE_NOT_ALLOWED,
- node: expression,
+ node: is(expression, 'bpmn:Expression') ? expression : node,
parentNode,
property: propertyName
}, allowedVersion)
diff --git a/test/camunda-cloud/integration/no-task-schedule-errors.bpmn b/test/camunda-cloud/integration/no-task-schedule-errors.bpmn
new file mode 100644
index 00000000..ea454b38
--- /dev/null
+++ b/test/camunda-cloud/integration/no-task-schedule-errors.bpmn
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/camunda-cloud/integration/no-task-schedule.bpmn b/test/camunda-cloud/integration/no-task-schedule.bpmn
new file mode 100644
index 00000000..0d820b9a
--- /dev/null
+++ b/test/camunda-cloud/integration/no-task-schedule.bpmn
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/camunda-cloud/integration/no-task-schedule.spec.js b/test/camunda-cloud/integration/no-task-schedule.spec.js
new file mode 100644
index 00000000..87c34d93
--- /dev/null
+++ b/test/camunda-cloud/integration/no-task-schedule.spec.js
@@ -0,0 +1,73 @@
+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'
+];
+
+describe('integration - no-task-schedule', 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/no-task-schedule.bpmn');
+
+ // when
+ const reports = await linter.lint(root);
+
+ // then
+ expect(reports[ 'camunda-compat/no-task-schedule' ]).not.to.exist;
+ });
+
+ });
+
+
+ describe('errors', function() {
+
+ it('should have errors', async function() {
+
+ // given
+ const { root } = await readModdle('test/camunda-cloud/integration/no-task-schedule-errors.bpmn');
+
+ // when
+ const reports = await linter.lint(root);
+
+ // then
+ expect(reports[ 'camunda-compat/no-task-schedule' ]).to.exist;
+ });
+
+ });
+
+ });
+
+ });
+
+});
\ No newline at end of file
diff --git a/test/camunda-cloud/integration/task-schedule-errors.bpmn b/test/camunda-cloud/integration/task-schedule-errors.bpmn
new file mode 100644
index 00000000..e57a2856
--- /dev/null
+++ b/test/camunda-cloud/integration/task-schedule-errors.bpmn
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/camunda-cloud/integration/task-schedule.bpmn b/test/camunda-cloud/integration/task-schedule.bpmn
new file mode 100644
index 00000000..ea454b38
--- /dev/null
+++ b/test/camunda-cloud/integration/task-schedule.bpmn
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/camunda-cloud/integration/task-schedule.spec.js b/test/camunda-cloud/integration/task-schedule.spec.js
new file mode 100644
index 00000000..04acc4b5
--- /dev/null
+++ b/test/camunda-cloud/integration/task-schedule.spec.js
@@ -0,0 +1,68 @@
+const { expect } = require('chai');
+
+const Linter = require('bpmnlint/lib/linter');
+
+const NodeResolver = require('bpmnlint/lib/resolver/node-resolver');
+
+const { readModdle } = require('../../helper');
+
+const versions = [
+ '8.2'
+];
+
+describe('integration - task schedule', 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/task-schedule.bpmn');
+
+ // when
+ const reports = await linter.lint(root);
+
+ // then
+ expect(reports[ 'camunda-compat/task-schedule' ]).not.to.exist;
+ });
+
+ });
+
+
+ describe('errors', function() {
+
+ it('should have errors', async function() {
+
+ // given
+ const { root } = await readModdle('test/camunda-cloud/integration/task-schedule-errors.bpmn');
+
+ // when
+ const reports = await linter.lint(root);
+
+ // then
+ expect(reports[ 'camunda-compat/task-schedule' ]).to.exist;
+ });
+
+ });
+
+ });
+
+ });
+
+});
\ No newline at end of file
diff --git a/test/camunda-cloud/no-task-schedule.spec.js b/test/camunda-cloud/no-task-schedule.spec.js
new file mode 100644
index 00000000..204a6a33
--- /dev/null
+++ b/test/camunda-cloud/no-task-schedule.spec.js
@@ -0,0 +1,67 @@
+const RuleTester = require('bpmnlint/lib/testers/rule-tester');
+
+const rule = require('../../rules/no-task-schedule');
+
+const {
+ createModdle,
+ createProcess,
+ createDefinitions
+} = require('../helper');
+
+const { ERROR_TYPES } = require('../../rules/utils/element');
+
+const valid = [
+ {
+ name: 'user task',
+ moddleElement: createModdle(createProcess(`
+
+ `))
+ },
+ {
+ name: 'user task (non-executable process)',
+ config: { version: '8.2' },
+ moddleElement: createModdle(createDefinitions(`
+
+
+
+
+
+
+
+ `))
+ }
+];
+
+const invalid = [
+ {
+ name: 'user task',
+ moddleElement: createModdle(createProcess(`
+
+
+
+
+
+ `)),
+ report: {
+ id: 'UserTask_1',
+ message: 'Extension element of type only allowed by Camunda Platform 8.2',
+ path: [
+ 'extensionElements',
+ 'values',
+ 0
+ ],
+ data: {
+ type: ERROR_TYPES.EXTENSION_ELEMENT_NOT_ALLOWED,
+ node: 'UserTask_1',
+ parentNode: null,
+ extensionElement: 'zeebe:TaskSchedule',
+ allowedVersion: '8.2'
+ }
+ }
+ }
+];
+
+RuleTester.verify('no-task-schedule', rule, {
+ valid,
+ invalid
+});
\ No newline at end of file
diff --git a/test/camunda-cloud/task-schedule.spec.js b/test/camunda-cloud/task-schedule.spec.js
new file mode 100644
index 00000000..8c41b222
--- /dev/null
+++ b/test/camunda-cloud/task-schedule.spec.js
@@ -0,0 +1,93 @@
+const RuleTester = require('bpmnlint/lib/testers/rule-tester');
+
+const rule = require('../../rules/task-schedule');
+
+const {
+ createModdle,
+ createProcess
+} = require('../helper');
+
+const { ERROR_TYPES } = require('../../rules/utils/element');
+
+const valid = [
+ {
+ name: 'user task (due date)',
+ moddleElement: createModdle(createProcess(`
+
+
+
+
+
+ `))
+ },
+ {
+ name: 'user task (follow-up date)',
+ moddleElement: createModdle(createProcess(`
+
+
+
+
+
+ `))
+ }
+];
+
+const invalid = [
+ {
+ name: 'user task (invalid due date)',
+ moddleElement: createModdle(createProcess(`
+
+
+
+
+
+ `)),
+ report: {
+ id: 'UserTask_1',
+ message: 'Expression value of not allowed',
+ path: [
+ 'extensionElements',
+ 'values',
+ 0,
+ 'dueDate'
+ ],
+ data: {
+ type: ERROR_TYPES.EXPRESSION_VALUE_NOT_ALLOWED,
+ node: 'zeebe:TaskSchedule',
+ parentNode: 'UserTask_1',
+ property: 'dueDate'
+ }
+ }
+ },
+ {
+ name: 'user task (invalid follow-up date)',
+ moddleElement: createModdle(createProcess(`
+
+
+
+
+
+ `)),
+ report: {
+ id: 'UserTask_1',
+ message: 'Expression value of not allowed',
+ path: [
+ 'extensionElements',
+ 'values',
+ 0,
+ 'followUpDate'
+ ],
+ data: {
+ type: ERROR_TYPES.EXPRESSION_VALUE_NOT_ALLOWED,
+ node: 'zeebe:TaskSchedule',
+ parentNode: 'UserTask_1',
+ property: 'followUpDate'
+ }
+ }
+ }
+];
+
+RuleTester.verify('task-schedule', rule, {
+ valid,
+ invalid
+});
\ No newline at end of file
diff --git a/test/config/configs.spec.js b/test/config/configs.spec.js
index 8b200e2b..9d6e4691 100644
--- a/test/config/configs.spec.js
+++ b/test/config/configs.spec.js
@@ -17,6 +17,7 @@ describe('configs', function() {
'message-reference': [ 'error', { version: '1.0' } ],
'no-candidate-users': [ 'error', { version: '1.0' } ],
'no-expression': [ 'error', { version: '1.0' } ],
+ 'no-task-schedule': [ 'error', { version: '1.0' } ],
'no-template': [ 'error', { version: '1.0' } ],
'no-zeebe-properties': [ 'error', { version: '1.0' } ],
'sequence-flow-condition': [ 'error', { version: '1.0' } ],
@@ -39,6 +40,7 @@ describe('configs', function() {
'message-reference': [ 'error', { version: '1.1' } ],
'no-candidate-users': [ 'error', { version: '1.1' } ],
'no-expression': [ 'error', { version: '1.1' } ],
+ 'no-task-schedule': [ 'error', { version: '1.1' } ],
'no-template': [ 'error', { version: '1.1' } ],
'no-zeebe-properties': [ 'error', { version: '1.1' } ],
'sequence-flow-condition': [ 'error', { version: '1.1' } ],
@@ -61,6 +63,7 @@ describe('configs', function() {
'message-reference': [ 'error', { version: '1.2' } ],
'no-candidate-users': [ 'error', { version: '1.2' } ],
'no-expression': [ 'error', { version: '1.2' } ],
+ 'no-task-schedule': [ 'error', { version: '1.2' } ],
'no-template': [ 'error', { version: '1.2' } ],
'no-zeebe-properties': [ 'error', { version: '1.2' } ],
'sequence-flow-condition': [ 'error', { version: '1.2' } ],
@@ -83,6 +86,7 @@ describe('configs', function() {
'message-reference': [ 'error', { version: '1.3' } ],
'no-candidate-users': [ 'error', { version: '1.3' } ],
'no-expression': [ 'error', { version: '1.3' } ],
+ 'no-task-schedule': [ 'error', { version: '1.3' } ],
'no-template': [ 'error', { version: '1.3' } ],
'no-zeebe-properties': [ 'error', { version: '1.3' } ],
'sequence-flow-condition': [ 'error', { version: '1.3' } ],
@@ -105,6 +109,7 @@ describe('configs', function() {
'message-reference': [ 'error', { version: '8.0' } ],
'no-candidate-users': [ 'error', { version: '8.0' } ],
'no-expression': [ 'error', { version: '8.0' } ],
+ 'no-task-schedule': [ 'error', { version: '8.0' } ],
'no-zeebe-properties': [ 'error', { version: '8.0' } ],
'sequence-flow-condition': [ 'error', { version: '8.0' } ],
'subscription': [ 'error', { version: '8.0' } ],
@@ -127,6 +132,7 @@ describe('configs', function() {
'message-reference': [ 'error', { version: '8.1' } ],
'no-candidate-users': [ 'error', { version: '8.1' } ],
'no-expression': [ 'error', { version: '8.1' } ],
+ 'no-task-schedule': [ 'error', { version: '8.1' } ],
'sequence-flow-condition': [ 'error', { version: '8.1' } ],
'subscription': [ 'error', { version: '8.1' } ],
'timer': [ 'error', { version: '8.1' } ],
@@ -150,6 +156,7 @@ describe('configs', function() {
'no-expression': [ 'error', { version: '8.2' } ],
'sequence-flow-condition': [ 'error', { version: '8.2' } ],
'subscription': [ 'error', { version: '8.2' } ],
+ 'task-schedule': [ 'error', { version: '8.2' } ],
'timer': [ 'error', { version: '8.2' } ],
'user-task-form': [ 'error', { version: '8.2' } ]
}));