From 66618e92198d9990d323d02a799c30dde1ba3c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Konopski?= Date: Mon, 11 Nov 2024 15:48:54 +0100 Subject: [PATCH] feat: support `zeebe:TaskListeners` related to https://github.com/camunda/camunda-modeler/issues/4590 --- .../CleanUpTaskListenersBehavior.js | 76 +++++++ lib/camunda-cloud/index.js | 3 + .../CleanUpTaskListenersBehaviorSpec.js | 191 ++++++++++++++++++ test/camunda-cloud/task-listeners.bpmn | 42 ++++ 4 files changed, 312 insertions(+) create mode 100644 lib/camunda-cloud/CleanUpTaskListenersBehavior.js create mode 100644 test/camunda-cloud/CleanUpTaskListenersBehaviorSpec.js create mode 100644 test/camunda-cloud/task-listeners.bpmn diff --git a/lib/camunda-cloud/CleanUpTaskListenersBehavior.js b/lib/camunda-cloud/CleanUpTaskListenersBehavior.js new file mode 100644 index 0000000..401236a --- /dev/null +++ b/lib/camunda-cloud/CleanUpTaskListenersBehavior.js @@ -0,0 +1,76 @@ +import { is } from 'bpmn-js/lib/util/ModelUtil'; +import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; +import { without } from 'min-dash'; + +import { getExtensionElementsList } from '../util/ExtensionElementsUtil'; + +const ALLOWED_EVENT_TYPES = [ 'complete', 'assignment' ]; + +export default class CleanUpTaskListenersBehavior extends CommandInterceptor { + constructor(eventBus, modeling) { + super(eventBus); + + // remove task listeners of disallowed type + this.postExecuted('shape.replace', function(event) { + const element = event.context.newShape; + const taskListenersContainer = getTaskListenersContainer(element); + if (!taskListenersContainer) { + return; + } + + const listeners = taskListenersContainer.get('listeners'); + const newListeners = withoutDisallowedListeners(element, listeners); + + if (newListeners.length !== listeners.length) { + modeling.updateModdleProperties(element, taskListenersContainer, { listeners: newListeners }); + } + }); + + // remove empty task listener container + this.postExecuted('element.updateModdleProperties', function(event) { + const { + element, + moddleElement + } = event.context; + + if (!is(moddleElement, 'zeebe:TaskListeners')) { + return; + } + + const listeners = moddleElement.get('listeners'); + if (listeners.length) { + return; + } + + const extensionElements = moddleElement.$parent; + modeling.updateModdleProperties(element, extensionElements, { values: without(extensionElements.get('values'), moddleElement) }); + }); + } +} + +CleanUpTaskListenersBehavior.$inject = [ + 'eventBus', + 'modeling' +]; + +// helpers ////////// +function withoutDisallowedListeners(element, listeners) { + return listeners.filter(listener => { + if ( + !is(element, 'bpmn:UserTask') || + !ALLOWED_EVENT_TYPES.includes(listener.eventType) || + !hasZeebeTaskExtensionElement(element) + ) { + return false; + } + return true; + }); +} + +function getTaskListenersContainer(element) { + return getExtensionElementsList(element, 'zeebe:TaskListeners')[0]; +} + +function hasZeebeTaskExtensionElement(element) { + return getExtensionElementsList(element, 'zeebe:UserTask').length > 0; +} diff --git a/lib/camunda-cloud/index.js b/lib/camunda-cloud/index.js index ae2c534..515e69c 100644 --- a/lib/camunda-cloud/index.js +++ b/lib/camunda-cloud/index.js @@ -1,6 +1,7 @@ import CleanUpBusinessRuleTaskBehavior from './CleanUpBusinessRuleTaskBehavior'; import CleanUpEndEventBehavior from './CleanUpEndEventBehavior'; import CleanUpExecutionListenersBehavior from './CleanUpExecutionListenersBehavior'; +import CleanUpTaskListenersBehavior from './CleanUpTaskListenersBehavior'; import CleanUpSubscriptionBehavior from './CleanUpSubscriptionBehavior'; import CleanUpTimerExpressionBehavior from './CleanUpTimerExpressionBehavior'; import CopyPasteBehavior from './CopyPasteBehavior'; @@ -16,6 +17,7 @@ export default { 'cleanUpBusinessRuleTaskBehavior', 'cleanUpEndEventBehavior', 'cleanUpExecutionListenersBehavior', + 'cleanUpTaskListenersBehavior', 'cleanUpSubscriptionBehavior', 'cleanUpTimerExpressionBehavior', 'copyPasteBehavior', @@ -29,6 +31,7 @@ export default { cleanUpBusinessRuleTaskBehavior: [ 'type', CleanUpBusinessRuleTaskBehavior ], cleanUpEndEventBehavior: [ 'type', CleanUpEndEventBehavior ], cleanUpExecutionListenersBehavior: [ 'type', CleanUpExecutionListenersBehavior ], + cleanUpTaskListenersBehavior: [ 'type', CleanUpTaskListenersBehavior ], cleanUpSubscriptionBehavior: [ 'type', CleanUpSubscriptionBehavior ], cleanUpTimerExpressionBehavior: [ 'type', CleanUpTimerExpressionBehavior ], copyPasteBehavior: [ 'type', CopyPasteBehavior ], diff --git a/test/camunda-cloud/CleanUpTaskListenersBehaviorSpec.js b/test/camunda-cloud/CleanUpTaskListenersBehaviorSpec.js new file mode 100644 index 0000000..2cf397b --- /dev/null +++ b/test/camunda-cloud/CleanUpTaskListenersBehaviorSpec.js @@ -0,0 +1,191 @@ +import { + bootstrapCamundaCloudModeler, + inject +} from 'test/TestHelper'; + +import { getExtensionElementsList } from 'lib/util/ExtensionElementsUtil'; + +import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil'; + +import diagramXML from './task-listeners.bpmn'; + + +describe('camunda-cloud/features/modeling - CleanUpTaskListenersBehavior', function() { + + beforeEach(bootstrapCamundaCloudModeler(diagramXML)); + + describe('remove execution listeners if disallowed in element type', function() { + + const testCases = [ + { + title: 'User Task -> Start Event', + element: 'UserTask', + target: { + type: 'bpmn:StartEvent' + } + }, + { + title: 'User Task -> Gateway', + element: 'UserTask', + target: { + type: 'bpmn:Gateway' + } + } + ]; + + for (const { title, element, target } of testCases) { + + describe(title, function() { + + it('should execute', inject(function(bpmnReplace, elementRegistry) { + + // given + let el = elementRegistry.get(element); + + // when + bpmnReplace.replaceElement(el, target); + + // then + el = elementRegistry.get(element); + + const executionListenersContainer = getTaskListenersContainer(el); + + expect(executionListenersContainer).not.to.exist; + })); + + + it('should undo', inject(function(bpmnReplace, commandStack, elementRegistry) { + + // given + let el = elementRegistry.get(element); + + // when + bpmnReplace.replaceElement(el, target); + + commandStack.undo(); + + // then + el = elementRegistry.get(element); + const extensionElements = getBusinessObject(el).get('extensionElements'); + + expect(extensionElements.get('values')).to.have.lengthOf(2); + })); + + + it('should redo', inject(function(bpmnReplace, commandStack, elementRegistry) { + + // given + let el = elementRegistry.get(element); + + // when + bpmnReplace.replaceElement(el, target); + + commandStack.undo(); + commandStack.redo(); + + // then + el = elementRegistry.get(element); + const executionListenersContainer = getTaskListenersContainer(el); + + expect(executionListenersContainer).not.to.exist; + })); + }); + } + }); + + describe('should remove execution listeners of disallowed type', function() { + + it('should execute', inject(function(bpmnReplace, elementRegistry) { + + // given + let el = elementRegistry.get('UserTaskWrongType'); + let targetEl = elementRegistry.get('UserTask'); + + // when + bpmnReplace.replaceElement(el, targetEl); + + // then + el = elementRegistry.get('UserTaskWrongType'); + const container = getExtensionElementsList(getBusinessObject(el), 'zeebe:TaskListeners')[0]; + + expect(container.get('listeners')).to.have.lengthOf(1); + })); + + + it('should undo', inject(function(bpmnReplace, commandStack, elementRegistry) { + + // given + let el = elementRegistry.get('UserTaskWrongType'); + let targetEl = elementRegistry.get('UserTask'); + + // when + bpmnReplace.replaceElement(el, targetEl); + + commandStack.undo(); + + // then + el = elementRegistry.get('UserTaskWrongType'); + const container = getExtensionElementsList(getBusinessObject(el), 'zeebe:TaskListeners')[0]; + + expect(container.get('listeners')).to.have.lengthOf(2); + })); + + + it('should redo', inject(function(bpmnReplace, commandStack, elementRegistry) { + + // given + let el = elementRegistry.get('UserTaskWrongType'); + let targetEl = elementRegistry.get('UserTask'); + + // when + bpmnReplace.replaceElement(el, targetEl); + + commandStack.undo(); + commandStack.redo(); + + // then + el = elementRegistry.get('UserTaskWrongType'); + const container = getExtensionElementsList(getBusinessObject(el), 'zeebe:TaskListeners')[0]; + + expect(container.get('listeners')).to.have.lengthOf(1); + })); + + + it('should remove zeebe:TaskListeners for non zeebe user task', inject(function(bpmnReplace, elementRegistry) { + + // given + let el = elementRegistry.get('NonZeebeUserTask'); + const targetEl = elementRegistry.get('UserTask'); + + // when + bpmnReplace.replaceElement(el, targetEl); + + // then + el = elementRegistry.get('NonZeebeUserTask'); + const extensionElements = getBusinessObject(el).get('extensionElements'); + console.log(extensionElements.get('values')); + + expect(extensionElements.get('values')).to.have.lengthOf(0); + })); + + + it('should remove zeebe:TaskListeners', inject(function(elementRegistry, modeling) { + + // given + const el = elementRegistry.get('UserTask'); + const listenersContainer = getTaskListenersContainer(el); + + // when + modeling.updateModdleProperties(el, listenersContainer, { listeners: [] }); + + // then + const extensionElements = getBusinessObject(el).get('extensionElements'); + + expect(extensionElements.get('values')).to.have.lengthOf(1); + })); + }); +}); + +function getTaskListenersContainer(element) { + return getExtensionElementsList(getBusinessObject(element), 'zeebe:TaskListeners')[0]; +} diff --git a/test/camunda-cloud/task-listeners.bpmn b/test/camunda-cloud/task-listeners.bpmn new file mode 100644 index 0000000..b6d425e --- /dev/null +++ b/test/camunda-cloud/task-listeners.bpmn @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file