From aa2d261d5678b02b189b653eca19087c76ed7d8e Mon Sep 17 00:00:00 2001 From: Skaiir Date: Tue, 5 Nov 2024 12:17:38 +0100 Subject: [PATCH] feat: default user tasks with `zeebe:UserTask` extension element Related to https://github.com/camunda/camunda-modeler/issues/4648 --- .../CreateZeebeUserTaskBehavior.js | 66 +++++++++++ lib/camunda-cloud/index.js | 3 + lib/camunda-cloud/util/ZeebeUserTaskUtil.js | 42 +++++++ .../CreateZeebeUserTaskBehaviorSpec.js | 107 ++++++++++++++++++ test/camunda-cloud/FormsBehaviorSpec.js | 2 +- test/camunda-cloud/process-user-tasks.bpmn | 9 ++ 6 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 lib/camunda-cloud/CreateZeebeUserTaskBehavior.js create mode 100644 lib/camunda-cloud/util/ZeebeUserTaskUtil.js create mode 100644 test/camunda-cloud/CreateZeebeUserTaskBehaviorSpec.js diff --git a/lib/camunda-cloud/CreateZeebeUserTaskBehavior.js b/lib/camunda-cloud/CreateZeebeUserTaskBehavior.js new file mode 100644 index 0000000..1ba16be --- /dev/null +++ b/lib/camunda-cloud/CreateZeebeUserTaskBehavior.js @@ -0,0 +1,66 @@ +import { createElement } from '../util/ElementUtil'; +import { getZeebeUserTaskElement } from './util/ZeebeUserTaskUtil'; +import { getBusinessObject, is } from 'bpmn-js/lib/util/ModelUtil'; +import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; + +const HIGH_PRIORITY = 5000; + +/** + * Zeebe BPMN specific behavior for creating user tasks. + */ +export default class CreateZeebeUserTaskBehavior extends CommandInterceptor { + constructor(bpmnFactory, eventBus, modeling) { + super(eventBus); + + /** + * Add zeebe:userTask extension element when creating bpmn:UserTask. + */ + this.postExecuted( + 'shape.create', + HIGH_PRIORITY, + function(context) { + const { shape } = context; + + if (!is(shape, 'bpmn:UserTask')) { + return; + } + + const businessObject = getBusinessObject(shape); + + // Use getZeebeUserTaskElement to check if zeebe:userTask already exists + let userTaskElement = getZeebeUserTaskElement(businessObject); + + if (!userTaskElement) { + let extensionElements = businessObject.get('extensionElements'); + + if (!extensionElements) { + extensionElements = createElement( + 'bpmn:ExtensionElements', + { + values: [], + }, + businessObject, + bpmnFactory + ); + + modeling.updateProperties(shape, { extensionElements }); + } + + userTaskElement = createElement( + 'zeebe:UserTask', + {}, + extensionElements, + bpmnFactory + ); + + modeling.updateModdleProperties(shape, extensionElements, { + values: [ ...(extensionElements.values || []), userTaskElement ], + }); + } + }, + true + ); + } +} + +CreateZeebeUserTaskBehavior.$inject = [ 'bpmnFactory', 'eventBus', 'modeling' ]; diff --git a/lib/camunda-cloud/index.js b/lib/camunda-cloud/index.js index ae2c534..1a8f500 100644 --- a/lib/camunda-cloud/index.js +++ b/lib/camunda-cloud/index.js @@ -5,6 +5,7 @@ import CleanUpSubscriptionBehavior from './CleanUpSubscriptionBehavior'; import CleanUpTimerExpressionBehavior from './CleanUpTimerExpressionBehavior'; import CopyPasteBehavior from './CopyPasteBehavior'; import CreateZeebeCallActivityBehavior from './CreateZeebeCallActivityBehavior'; +import CreateZeebeUserTaskBehavior from './CreateZeebeUserTaskBehavior'; import DeleteParticipantBehaviour from '../shared/DeleteParticipantBehaviour'; import FormsBehavior from './FormsBehavior'; import RemoveAssignmentDefinitionBehavior from './RemoveAssignmentDefinitionBehavior'; @@ -20,6 +21,7 @@ export default { 'cleanUpTimerExpressionBehavior', 'copyPasteBehavior', 'createZeebeCallActivityBehavior', + 'createZeebeUserTaskBehavior', 'deleteParticipantBehaviour', 'formsBehavior', 'removeAssignmentDefinitionBehavior', @@ -33,6 +35,7 @@ export default { cleanUpTimerExpressionBehavior: [ 'type', CleanUpTimerExpressionBehavior ], copyPasteBehavior: [ 'type', CopyPasteBehavior ], createZeebeCallActivityBehavior: [ 'type', CreateZeebeCallActivityBehavior ], + createZeebeUserTaskBehavior: [ 'type', CreateZeebeUserTaskBehavior ], deleteParticipantBehaviour: [ 'type', DeleteParticipantBehaviour ], formsBehavior: [ 'type', FormsBehavior ], removeAssignmentDefinitionBehavior: [ 'type', RemoveAssignmentDefinitionBehavior ], diff --git a/lib/camunda-cloud/util/ZeebeUserTaskUtil.js b/lib/camunda-cloud/util/ZeebeUserTaskUtil.js new file mode 100644 index 0000000..1d49899 --- /dev/null +++ b/lib/camunda-cloud/util/ZeebeUserTaskUtil.js @@ -0,0 +1,42 @@ +import { getExtensionElementsList } from '../../util/ExtensionElementsUtil'; + +import { getBusinessObject, is } from 'bpmn-js/lib/util/ModelUtil'; + +/** + * Get all zeebe:userTask elements of an element. + * + * @param {djs.model.Base|ModdleElement} element + * + * @returns {Array} + */ +export function getZeebeUserTaskElements(element) { + const businessObject = getBusinessObject(element); + return getExtensionElementsList(businessObject, 'zeebe:UserTask'); +} + +/** + * Get the first zeebe:userTask element of an element. + * + * @param {djs.model.Base|ModdleElement} element + * + * @returns {ModdleElement|null} + */ +export function getZeebeUserTaskElement(element) { + const userTaskElements = getZeebeUserTaskElements(element); + return userTaskElements[0] || null; +} + +/** + * Check whether a zeebe:userTask extension element is set on an element. + * + * @param {djs.model.Base|ModdleElement} element + * + * @returns {boolean} + */ +export function hasZeebeUserTaskExtension(element) { + if (!is(element, 'bpmn:UserTask')) { + return false; + } + + return !!getZeebeUserTaskElement(element); +} diff --git a/test/camunda-cloud/CreateZeebeUserTaskBehaviorSpec.js b/test/camunda-cloud/CreateZeebeUserTaskBehaviorSpec.js new file mode 100644 index 0000000..c087a47 --- /dev/null +++ b/test/camunda-cloud/CreateZeebeUserTaskBehaviorSpec.js @@ -0,0 +1,107 @@ +import { bootstrapCamundaCloudModeler, inject } from 'test/TestHelper'; + +import { find } from 'min-dash'; + +import { + getZeebeUserTaskElement, + getZeebeUserTaskElements, +} from '../../lib/camunda-cloud/util/ZeebeUserTaskUtil'; + +import { getBusinessObject, is } from 'bpmn-js/lib/util/ModelUtil'; + +import emptyProcessDiagramXML from './process-empty.bpmn'; +import userTasksXML from './process-user-tasks.bpmn'; + +describe('camunda-cloud/features/modeling - CreateZeebeUserTaskBehavior', function() { + describe('populate zeebe:userTask', function() { + describe('when creating new shapes', function() { + beforeEach(bootstrapCamundaCloudModeler(emptyProcessDiagramXML)); + + it('should execute when creating bpmn:UserTask', inject(function( + canvas, + modeling + ) { + + // given + const rootElement = canvas.getRootElement(); + + // when + const newShape = modeling.createShape( + { type: 'bpmn:UserTask' }, + { x: 100, y: 100 }, + rootElement + ); + + // then + const businessObject = getBusinessObject(newShape), + extensionElements = businessObject.get('extensionElements'), + zeebeUserTaskExtension = getZeebeUserTaskElement(newShape); + + expect(zeebeUserTaskExtension).to.exist; + expect(extensionElements).to.exist; + expect(zeebeUserTaskExtension.$parent).to.equal(extensionElements); + })); + + it('should not execute when creating bpmn:Task', inject(function( + canvas, + modeling + ) { + + // given + const rootElement = canvas.getRootElement(); + + // when + const newShape = modeling.createShape( + { type: 'bpmn:Task' }, + { x: 100, y: 100 }, + rootElement + ); + + // then + const zeebeUserTaskExtension = getZeebeUserTaskElement(newShape); + + expect(zeebeUserTaskExtension).not.to.exist; + })); + }); + + describe('when copying bpmn:UserTask', function() { + beforeEach(bootstrapCamundaCloudModeler(userTasksXML)); + + it('should re-use existing extensionElement', inject(function( + canvas, + copyPaste, + elementRegistry + ) { + + // given + const rootElement = canvas.getRootElement(); + const userTask = elementRegistry.get('withZeebeUserTask'); + + // when + copyPaste.copy(userTask); + + const elements = copyPaste.paste({ + element: rootElement, + point: { + x: 1000, + y: 1000, + }, + }); + + // then + const pastedUserTask = find(elements, (element) => + is(element, 'bpmn:UserTask') + ); + + const businessObject = getBusinessObject(pastedUserTask), + extensionElements = businessObject.get('extensionElements'), + zeebeUserTaskExtensions = getZeebeUserTaskElements(pastedUserTask); + + expect(zeebeUserTaskExtensions).to.exist; + expect(extensionElements).to.exist; + expect(zeebeUserTaskExtensions.length).to.equal(1); + expect(zeebeUserTaskExtensions[0].$parent).to.equal(extensionElements); + })); + }); + }); +}); diff --git a/test/camunda-cloud/FormsBehaviorSpec.js b/test/camunda-cloud/FormsBehaviorSpec.js index 969e35b..6895112 100644 --- a/test/camunda-cloud/FormsBehaviorSpec.js +++ b/test/camunda-cloud/FormsBehaviorSpec.js @@ -261,7 +261,7 @@ describe('camunda-cloud/features/modeling - FormsBehavior', function() { const userTaskForms = getUserTaskForms(rootElement); // then - expect(userTaskForms).to.have.length(3); + expect(userTaskForms).to.have.length(4); })); }); diff --git a/test/camunda-cloud/process-user-tasks.bpmn b/test/camunda-cloud/process-user-tasks.bpmn index 86d1c31..baef13f 100644 --- a/test/camunda-cloud/process-user-tasks.bpmn +++ b/test/camunda-cloud/process-user-tasks.bpmn @@ -83,6 +83,11 @@ + + + + + @@ -155,6 +160,10 @@ + + + +