From 9bf5c8cabfd7044a40a306730c26c3e47f11cf25 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 13 Mar 2023 16:53:34 +0100 Subject: [PATCH] feat: add `task-schedule` and `no-task-schedule` rules Closes #84 --- index.js | 9 +- rules/no-task-schedule.js | 19 ++++ rules/task-schedule.js | 68 ++++++++++++++ rules/utils/element.js | 18 ++-- .../integration/no-task-schedule-errors.bpmn | 17 ++++ .../integration/no-task-schedule.bpmn | 13 +++ .../integration/no-task-schedule.spec.js | 73 +++++++++++++++ .../integration/task-schedule-errors.bpmn | 17 ++++ .../integration/task-schedule.bpmn | 17 ++++ .../integration/task-schedule.spec.js | 68 ++++++++++++++ test/camunda-cloud/no-task-schedule.spec.js | 67 +++++++++++++ test/camunda-cloud/task-schedule.spec.js | 93 +++++++++++++++++++ test/config/configs.spec.js | 7 ++ 13 files changed, 477 insertions(+), 9 deletions(-) create mode 100644 rules/no-task-schedule.js create mode 100644 rules/task-schedule.js create mode 100644 test/camunda-cloud/integration/no-task-schedule-errors.bpmn create mode 100644 test/camunda-cloud/integration/no-task-schedule.bpmn create mode 100644 test/camunda-cloud/integration/no-task-schedule.spec.js create mode 100644 test/camunda-cloud/integration/task-schedule-errors.bpmn create mode 100644 test/camunda-cloud/integration/task-schedule.bpmn create mode 100644 test/camunda-cloud/integration/task-schedule.spec.js create mode 100644 test/camunda-cloud/no-task-schedule.spec.js create mode 100644 test/camunda-cloud/task-schedule.spec.js 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' } ] }));