From 5d05df62ea93c5d60f4367edab84822d7e1980f3 Mon Sep 17 00:00:00 2001 From: Maciej Barelkowski Date: Tue, 20 Feb 2024 09:41:16 +0100 Subject: [PATCH 1/2] deps: update to `zeebe-bpmn-moddle@1.1.0` --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 699d50a..2bbfd46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "sinon": "^17.0.1", "sinon-chai": "^3.7.0", "webpack": "^5.74.0", - "zeebe-bpmn-moddle": "^1.0.0" + "zeebe-bpmn-moddle": "^1.1.0" }, "peerDependencies": { "bpmn-js": ">= 9", @@ -7123,9 +7123,9 @@ } }, "node_modules/zeebe-bpmn-moddle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/zeebe-bpmn-moddle/-/zeebe-bpmn-moddle-1.0.0.tgz", - "integrity": "sha512-ZXEe+0s6Z1jf0hfK4VfRr71p4FcXkYz+MxVx6vMCiey2KVZqY1uj6KCpzK9+tEJzTdxGRS3FymK3oxAkjzs1GA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/zeebe-bpmn-moddle/-/zeebe-bpmn-moddle-1.1.0.tgz", + "integrity": "sha512-ES/UZFO0VmKvAzL4+cD3VcQpKvlmgLtnFKTyiv0DdDcxNrdQg1rI0OmUdrKMiybAbtAgPDkVXZCusE3kkXwEyQ==", "dev": true } }, @@ -12423,9 +12423,9 @@ "dev": true }, "zeebe-bpmn-moddle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/zeebe-bpmn-moddle/-/zeebe-bpmn-moddle-1.0.0.tgz", - "integrity": "sha512-ZXEe+0s6Z1jf0hfK4VfRr71p4FcXkYz+MxVx6vMCiey2KVZqY1uj6KCpzK9+tEJzTdxGRS3FymK3oxAkjzs1GA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/zeebe-bpmn-moddle/-/zeebe-bpmn-moddle-1.1.0.tgz", + "integrity": "sha512-ES/UZFO0VmKvAzL4+cD3VcQpKvlmgLtnFKTyiv0DdDcxNrdQg1rI0OmUdrKMiybAbtAgPDkVXZCusE3kkXwEyQ==", "dev": true } } diff --git a/package.json b/package.json index 09ae04f..a454e95 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "sinon": "^17.0.1", "sinon-chai": "^3.7.0", "webpack": "^5.74.0", - "zeebe-bpmn-moddle": "^1.0.0" + "zeebe-bpmn-moddle": "^1.1.0" }, "peerDependencies": { "bpmn-js": ">= 9", From 0430894f7a42647f76562de6069ea7a1f7cc5da0 Mon Sep 17 00:00:00 2001 From: Maciej Barelkowski Date: Tue, 20 Feb 2024 10:46:21 +0100 Subject: [PATCH 2/2] feat: support `zeebe:UserTask` Related to https://github.com/camunda/camunda-modeler/issues/4087 --- lib/camunda-cloud/FormsBehavior.js | 80 +++++++- .../FormDefinitionBehaviorSpec.js | 192 +++++++++++++++++- test/camunda-cloud/process-user-tasks.bpmn | 50 ++++- 3 files changed, 314 insertions(+), 8 deletions(-) diff --git a/lib/camunda-cloud/FormsBehavior.js b/lib/camunda-cloud/FormsBehavior.js index 57ecf96..b24996f 100644 --- a/lib/camunda-cloud/FormsBehavior.js +++ b/lib/camunda-cloud/FormsBehavior.js @@ -1,8 +1,9 @@ -import { without } from 'min-dash'; +import { isUndefined, without } from 'min-dash'; import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; import { createElement } from '../util/ElementUtil'; +import { getExtensionElementsList } from '../util/ExtensionElementsUtil'; import { getBusinessObject, @@ -26,6 +27,8 @@ export default class FormsBehavior extends CommandInterceptor { constructor(bpmnFactory, eventBus, modeling) { super(eventBus); + this._modeling = modeling; + function removeUserTaskForm(element, moddleElement, userTaskForm) { const extensionElements = moddleElement.get('extensionElements'); @@ -134,6 +137,7 @@ export default class FormsBehavior extends CommandInterceptor { * 1. zeebe:FormDefinition with zeebe:formId (linked Camunda form) * 2. zeebe:FormDefinition with zeebe:formKey in the format of camunda-forms:bpmn:UserTaskForm_1 (embedded Camunda form) * 3. zeebe:FormDefinition with zeebe:formKey (custom form) + * 4. zeebe:FormDefinition with zeebe:externalReference (external form) */ this.preExecute('element.updateModdleProperties', function(context) { const { @@ -144,8 +148,13 @@ export default class FormsBehavior extends CommandInterceptor { if (is(moddleElement, 'zeebe:FormDefinition')) { if ('formId' in properties) { properties.formKey = undefined; + properties.externalReference = undefined; } else if ('formKey' in properties) { properties.formId = undefined; + properties.externalReference = undefined; + } else if ('externalReference' in properties) { + properties.formId = undefined; + properties.formKey = undefined; } } }, true); @@ -188,6 +197,69 @@ export default class FormsBehavior extends CommandInterceptor { } }, true); + this._registerZeebeUserTaskSupport(); + } + + _registerZeebeUserTaskSupport() { + + /** + * Handle `formKey` for `zeebe:UserTask`. + * 1. Remove if embedded form is used. + * 2. Convert to externalReference if custom form key. + */ + this.postExecute('element.updateModdleProperties', ({ element }) => { + + if (!is(element, 'bpmn:UserTask') || !hasZeebeUserTask(element)) { + return; + } + + const formDefinition = getFormDefinition(element); + + if (!formDefinition) { + return; + } + + const formKey = formDefinition.get('formKey'); + + if (isUndefined(formKey)) { + return; + } + + if (isUserTaskFormKey(formKey)) { + this._modeling.updateModdleProperties(element, formDefinition, { formKey: undefined }); + } else { + this._modeling.updateModdleProperties(element, formDefinition, { + externalReference: formKey + }); + } + }, true); + + /** + * Replace `externalReference` with `formKey` for non-`zeebe:UserTask`. + */ + this.postExecute('element.updateModdleProperties', ({ element }) => { + + if (!is(element, 'bpmn:UserTask') || hasZeebeUserTask(element)) { + return; + } + + const formDefinition = getFormDefinition(element); + + if (!formDefinition) { + return; + } + + const externalReference = formDefinition.get('externalReference'); + + if (isUndefined(externalReference)) { + return; + } + + this._modeling.updateModdleProperties(element, formDefinition, { + externalReference: undefined, + formKey: externalReference + }); + }, true); } } @@ -209,4 +281,8 @@ function isExtensionElementRemoved(context, type) { && 'values' in properties && oldProperties.values.find(value => is(value, type)) && !properties.values.find(value => is(value, type)); -} \ No newline at end of file +} + +function hasZeebeUserTask(userTask) { + return getExtensionElementsList(userTask, 'zeebe:UserTask').length; +} diff --git a/test/camunda-cloud/FormDefinitionBehaviorSpec.js b/test/camunda-cloud/FormDefinitionBehaviorSpec.js index f4c5beb..2cc6122 100644 --- a/test/camunda-cloud/FormDefinitionBehaviorSpec.js +++ b/test/camunda-cloud/FormDefinitionBehaviorSpec.js @@ -2,12 +2,13 @@ import { without } from 'min-dash'; import { bootstrapCamundaCloudModeler, + getBpmnJS, inject } from 'test/TestHelper'; import { getExtensionElementsList } from 'lib/util/ExtensionElementsUtil'; -import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil'; +import { getBusinessObject, is } from 'bpmn-js/lib/util/ModelUtil'; import { getFormDefinition, @@ -406,6 +407,26 @@ describe('camunda-cloud/features/modeling - FormsBehavior', function() { }); + describe('set external reference', function() { + + it('should remove form ID', inject(function(elementRegistry, modeling) { + + // given + const userTask = elementRegistry.get('withFormId'); + + const formDefinition = getFormDefinition(userTask); + + // when + modeling.updateModdleProperties(userTask, formDefinition, { + externalReference: 'foobar' + }); + + // then + expect(formDefinition.get('formId')).not.to.exist; + })); + }); + + describe('remove form definition', function() { it('should remove user task form', inject(function(canvas, elementRegistry, modeling) { @@ -449,6 +470,139 @@ describe('camunda-cloud/features/modeling - FormsBehavior', function() { }); + + describe('change to Zeebe User Task', function() { + + it('should remove embedded form', inject(function(elementRegistry) { + + // given + const userTask = elementRegistry.get('UserTask_1'); + + // when + addZeebeUserTask(userTask); + + // then + const userTaskForms = getUserTaskForms(); + + expect(hasUsertaskForm('UserTaskForm_1', userTaskForms)).to.be.false; + + const formDefinition = getFormDefinition(userTask); + expect(formDefinition.formKey).not.to.exist; + })); + + + it('should keep custom form reference as externalReference', inject(function(elementRegistry) { + + // given + const userTask = elementRegistry.get('UserTask_11'); + const originalFormKey = getFormDefinition(userTask).get('formKey'); + + // when + addZeebeUserTask(userTask); + + // then + const formDefinition = getFormDefinition(userTask); + + expect(formDefinition.formKey).not.to.exist; + expect(formDefinition.externalReference).to.eql(originalFormKey); + expect(formDefinition.formId).not.to.exist; + })); + + + it('should keep empty custom form reference as externalReference', inject(function(elementRegistry) { + + // given + const userTask = elementRegistry.get('UserTask_13'); + const originalFormKey = getFormDefinition(userTask).get('formKey'); + + // when + addZeebeUserTask(userTask); + + // then + const formDefinition = getFormDefinition(userTask); + + expect(formDefinition.formKey).not.to.exist; + expect(formDefinition.externalReference).to.eql(originalFormKey); + expect(formDefinition.formId).not.to.exist; + })); + + + it('should keep Camunda Form (linked)', inject(function(elementRegistry) { + + // given + const userTask = elementRegistry.get('UserTask_12'); + const originalFormId = getFormDefinition(userTask).get('formId'); + + // when + addZeebeUserTask(userTask); + + // then + const formDefinition = getFormDefinition(userTask); + + expect(formDefinition.formKey).not.to.exist; + expect(formDefinition.externalReference).not.to.exist; + expect(formDefinition.formId).to.eql(originalFormId); + })); + }); + + + describe('change from Zeebe User Task', function() { + + it('should keep externalReference as formKey', inject(function(elementRegistry) { + + // given + const userTask = elementRegistry.get('withExternalReference'); + const externalReference = getFormDefinition(userTask).get('externalReference'); + + // when + removeZeebeUserTask(userTask); + + // then + const formDefinition = getFormDefinition(userTask); + + expect(formDefinition.externalReference).not.to.exist; + expect(formDefinition.formId).not.to.exist; + expect(formDefinition.formKey).to.eql(externalReference); + })); + + + it('should keep externalReference as formKey (empty value)', inject(function(elementRegistry) { + + // given + const userTask = elementRegistry.get('withEmptyExternalReference'); + const externalReference = getFormDefinition(userTask).get('externalReference'); + + // when + removeZeebeUserTask(userTask); + + // then + const formDefinition = getFormDefinition(userTask); + + expect(formDefinition.externalReference).not.to.exist; + expect(formDefinition.formId).not.to.exist; + expect(formDefinition.formKey).to.eql(externalReference); + })); + + + it('should keep Camunda Form (linked)', inject(function(elementRegistry) { + + // given + const userTask = elementRegistry.get('withFormId'); + const originalFormId = getFormDefinition(userTask).get('formId'); + + // when + removeZeebeUserTask(userTask); + + // then + const formDefinition = getFormDefinition(userTask); + + expect(formDefinition.formKey).not.to.exist; + expect(formDefinition.externalReference).not.to.exist; + expect(formDefinition.formId).to.eql(originalFormId); + })); + + }); + }); }); @@ -456,10 +610,40 @@ describe('camunda-cloud/features/modeling - FormsBehavior', function() { // helpers ////////// -function getUserTaskForms(rootElement) { - const businessObject = getBusinessObject(rootElement); +function getUserTaskForms() { + const BPMN_JS = getBpmnJS(); + + return BPMN_JS.invoke(function(canvas) { + const rootElement = canvas.getRootElement(); + const businessObject = getBusinessObject(rootElement); + + return getExtensionElementsList(businessObject, 'zeebe:UserTaskForm'); + }); +} + +function addZeebeUserTask(element) { + getBpmnJS().invoke(function(bpmnFactory, modeling) { + const extensionElements = getBusinessObject(element).get('extensionElements'), + values = extensionElements.get('values'), + zeebeUserTask = bpmnFactory.create('zeebe:UserTask'); - return getExtensionElementsList(businessObject, 'zeebe:UserTaskForm'); + // when + modeling.updateModdleProperties(element, extensionElements, { + values: values.concat(zeebeUserTask) + }); + }); +} + +function removeZeebeUserTask(element) { + getBpmnJS().invoke(function(modeling) { + const extensionElements = getBusinessObject(element).get('extensionElements'), + values = extensionElements.get('values'); + + // when + modeling.updateModdleProperties(element, extensionElements, { + values: values.filter(value => !is(value, 'zeebe:UserTask')) + }); + }); } function hasUsertaskForm(id, userTaskForms) { diff --git a/test/camunda-cloud/process-user-tasks.bpmn b/test/camunda-cloud/process-user-tasks.bpmn index 0234a9a..905efec 100644 --- a/test/camunda-cloud/process-user-tasks.bpmn +++ b/test/camunda-cloud/process-user-tasks.bpmn @@ -1,5 +1,5 @@ - + { components: [ { label: "field", key: "field" } ] } @@ -68,9 +68,30 @@ + + + + + + + + + + + + + + + + + + + + + @@ -81,6 +102,9 @@ + + + @@ -123,8 +147,24 @@ + + + + + + + + + + + + + + + + - + @@ -141,6 +181,12 @@ + + + + + +