diff --git a/lib/camunda-cloud/FormsBehavior.js b/lib/camunda-cloud/FormsBehavior.js index 8e7faf7..c4e8564 100644 --- a/lib/camunda-cloud/FormsBehavior.js +++ b/lib/camunda-cloud/FormsBehavior.js @@ -5,13 +5,11 @@ import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; import { createElement } from '../util/ElementUtil'; import { getExtensionElementsList } from '../util/ExtensionElementsUtil'; -import { - getBusinessObject, - is -} from 'bpmn-js/lib/util/ModelUtil'; +import { is } from 'bpmn-js/lib/util/ModelUtil'; import { createUserTaskFormId, + formKeyToUserTaskFormId, getFormDefinition, getRootElement, getUserTaskForm, @@ -24,7 +22,7 @@ import { * Zeebe BPMN specific forms behavior. */ export default class FormsBehavior extends CommandInterceptor { - constructor(bpmnFactory, eventBus, modeling) { + constructor(bpmnFactory, elementRegistry, eventBus, modeling) { super(eventBus); this._modeling = modeling; @@ -67,67 +65,79 @@ export default class FormsBehavior extends CommandInterceptor { /** - * Create new zeebe:FormDefinition and zeebe:UserTaskForm on user task created. + * Create and reference new zeebe:UserTaskForm when user task is created + * that references existing zeebe:UserTaskForm that is already referenced by + * existing user task. */ this.postExecute('shape.create', function(context) { const { shape } = context; + if (!is(shape, 'bpmn:UserTask')) { + return; + } + const oldFormDefinition = getFormDefinition(shape); - if (!is(shape, 'bpmn:UserTask') || !oldFormDefinition) { + if (!oldFormDefinition) { return; } const oldUserTaskForm = getUserTaskForm(shape); - const rootElement = getRootElement(shape); - - const businessObject = getBusinessObject(shape); + if (!oldUserTaskForm) { + return; + } - const extensionElements = businessObject.get('extensionElements'); + const isReferenced = elementRegistry.filter(element => { + if (element === shape) { + return false; + } - let rootExtensionElements = rootElement.get('extensionElements'); + const formDefinition = getFormDefinition(element); - // (1) ensure extension elements exists - if (!rootExtensionElements) { - rootExtensionElements = createElement('bpmn:ExtensionElements', { values: [] }, rootElement, bpmnFactory); + return formDefinition + && formDefinition.get('formKey') + && formKeyToUserTaskFormId(formDefinition.get('formKey')) === oldUserTaskForm.get('id'); + }); - modeling.updateModdleProperties(shape, rootElement, { extensionElements: rootExtensionElements }); + if (!isReferenced.length) { + return; } - // (2) remove existing form definition - let values = extensionElements.get('values').filter((element) => { - return element !== oldFormDefinition; - }); + const rootElement = getRootElement(shape); - // (3) create new form definition - const userTaskFormId = createUserTaskFormId(); + let extensionElements = rootElement.get('extensionElements'); - const newFormDefinition = createElement('zeebe:FormDefinition', { - formKey: userTaskFormIdToFormKey(userTaskFormId) - }, extensionElements, bpmnFactory); + // (1) ensure extension elements exist + if (!extensionElements) { + extensionElements = createElement('bpmn:ExtensionElements', { + values: [] + }, rootElement, bpmnFactory); - values = [ - ...values, - newFormDefinition - ]; + modeling.updateModdleProperties(shape, rootElement, { + extensionElements + }); + } - modeling.updateModdleProperties(shape, extensionElements, { - values - }); + // (2) create new user task form + const userTaskFormId = createUserTaskFormId(); - // (4) create new user task form const userTaskForm = createElement('zeebe:UserTaskForm', { id: userTaskFormId, - body: oldUserTaskForm ? oldUserTaskForm.get('body') : '' - }, rootExtensionElements, bpmnFactory); + body: oldUserTaskForm.get('body') + }, extensionElements, bpmnFactory); - modeling.updateModdleProperties(shape, rootExtensionElements, { + modeling.updateModdleProperties(shape, extensionElements, { values: [ - ...(rootExtensionElements.get('values') || []), + ...(extensionElements.get('values') || []), userTaskForm ] }); + + // (3) reference new user task form + modeling.updateModdleProperties(shape, oldFormDefinition, { + formKey: userTaskFormIdToFormKey(userTaskFormId) + }); }, true); @@ -277,6 +287,7 @@ export default class FormsBehavior extends CommandInterceptor { FormsBehavior.$inject = [ 'bpmnFactory', + 'elementRegistry', 'eventBus', 'modeling' ]; diff --git a/test/camunda-cloud/FormsBehaviorSpec.js b/test/camunda-cloud/FormsBehaviorSpec.js index 969e35b..4c1b83c 100644 --- a/test/camunda-cloud/FormsBehaviorSpec.js +++ b/test/camunda-cloud/FormsBehaviorSpec.js @@ -12,7 +12,8 @@ import { getBusinessObject, is } from 'bpmn-js/lib/util/ModelUtil'; import { getFormDefinition, - getUserTaskForm + getUserTaskForm, + userTaskFormIdToFormKey } from 'lib/camunda-cloud/util/FormsUtil'; import diagramXML from './process-user-tasks.bpmn'; @@ -156,11 +157,16 @@ describe('camunda-cloud/features/modeling - FormsBehavior', function() { // given const rootElement = canvas.getRootElement(); - const userTask1 = elementRegistry.get('UserTask_1'), - userTask2 = elementRegistry.get('UserTask_2'); + const userTask1 = elementRegistry.get('UserTask_1'); + + modeling.updateModdleProperties(rootElement, getBusinessObject(rootElement).get('extensionElements'), { + values: [ + getUserTaskForm(userTask1) + ] + }); // when - modeling.removeElements([ userTask1, userTask2 ]); + modeling.removeElements([ userTask1 ]); // then const extensionElements = getBusinessObject(rootElement).get('extensionElements'); @@ -171,97 +177,172 @@ describe('camunda-cloud/features/modeling - FormsBehavior', function() { }); - describe('create user task form', function() { + describe('create and reference new user task form', function() { - describe('on copy user task', function() { + describe('no existing form definition or user task form', function() { - it('should execute', inject(function(canvas, copyPaste, elementRegistry) { + it('should not create and reference new user task form', inject(function(canvas, elementFactory, modeling) { // given const rootElement = canvas.getRootElement(); - const element = elementRegistry.get('UserTask_1'); + const element = elementFactory.createShape({ + type: 'bpmn:UserTask' + }); + + // when + modeling.createShape(element, { x: 100, y: 100 }, rootElement); - const oldUserTaskForm = getUserTaskForm(element); + // then + expect(getUserTaskForm(element)).not.to.exist; + expect(getUserTaskForms(canvas.getRootElement())).to.have.length(3); + })); - const oldFormDefinition = getFormDefinition(element); + }); - // when - copyPaste.copy([ element ]); - const newElements = copyPaste.paste({ - element: rootElement, - point: { - x: 1000, - y: 1000 - } + describe('existing form definition, no user task form', function() { + + it('should not create and reference new form definition', inject(function(bpmnFactory, canvas, elementFactory, modeling) { + + // given + const rootElement = canvas.getRootElement(); + + const formDefinition = bpmnFactory.create('zeebe:FormDefinition', { + formId: 'foo' }); - const newElement = newElements[0]; + const extensionElements = bpmnFactory.create('bpmn:ExtensionElements', { + values: [ formDefinition ] + }); - const formDefinition = getFormDefinition(newElement); + formDefinition.$parent = extensionElements; - const userTaskForm = getUserTaskForm(newElement); + const businessObject = bpmnFactory.create('bpmn:UserTask', { + extensionElements + }); - // then - expect(formDefinition).not.to.eql(oldFormDefinition); + extensionElements.$parent = businessObject; + + const element = elementFactory.createShape({ + type: 'bpmn:UserTask', + businessObject + }); - expect(userTaskForm).not.to.eql(oldUserTaskForm); + // when + modeling.createShape(element, { x: 100, y: 100 }, rootElement); + + // then + expect(getUserTaskForm(element)).not.to.exist; + expect(getUserTaskForms(canvas.getRootElement())).to.have.length(3); })); + }); + - it('should undo', inject(function(canvas, commandStack, copyPaste, elementRegistry) { + describe('existing form definition, user task form not referenced', function() { + + it('should not create and reference new form definition', inject(function(bpmnFactory, canvas, elementFactory, modeling) { // given const rootElement = canvas.getRootElement(); - const element = elementRegistry.get('UserTask_1'); + const formDefinition = bpmnFactory.create('zeebe:FormDefinition', { + formKey: userTaskFormIdToFormKey('UserTaskForm_3') + }); + + const extensionElements = bpmnFactory.create('bpmn:ExtensionElements', { + values: [ formDefinition ] + }); - copyPaste.copy([ element ]); + formDefinition.$parent = extensionElements; - copyPaste.paste({ - element: rootElement, - point: { - x: 1000, - y: 1000 - } + const businessObject = bpmnFactory.create('bpmn:UserTask', { + extensionElements }); - // when - commandStack.undo(); + extensionElements.$parent = businessObject; - const userTaskForms = getUserTaskForms(rootElement); + const element = elementFactory.createShape({ + type: 'bpmn:UserTask', + businessObject + }); + + // when + modeling.createShape(element, { x: 100, y: 100 }, rootElement); // then - expect(userTaskForms).to.have.length(2); + expect(getUserTaskForm(element)).to.exist; + expect(getUserTaskForms(canvas.getRootElement())).to.have.length(3); })); + }); + + + describe('existing form definition, user task form referenced', function() { - it('should redo', inject(function(canvas, commandStack, copyPaste, elementRegistry) { + let element; + + beforeEach(inject(function(canvas, bpmnFactory, elementFactory, modeling) { // given const rootElement = canvas.getRootElement(); - const element = elementRegistry.get('UserTask_1'); + const formDefinition = bpmnFactory.create('zeebe:FormDefinition', { + formKey: userTaskFormIdToFormKey('UserTaskForm_1') + }); + + const extensionElements = bpmnFactory.create('bpmn:ExtensionElements', { + values: [ formDefinition ] + }); - copyPaste.copy([ element ]); + formDefinition.$parent = extensionElements; - copyPaste.paste({ - element: rootElement, - point: { - x: 1000, - y: 1000 - } + const businessObject = bpmnFactory.create('bpmn:UserTask', { + extensionElements + }); + + extensionElements.$parent = businessObject; + + element = elementFactory.createShape({ + type: 'bpmn:UserTask', + businessObject }); + // when + modeling.createShape(element, { x: 100, y: 100 }, rootElement); + })); + + + it('should create and reference new user task form (execute)', inject(function(canvas) { + + // then + expect(getUserTaskForm(element)).to.exist; + expect(getUserTaskForm(element).get('id')).to.not.equal('UserTaskForm_1'); + expect(getUserTaskForms(canvas.getRootElement())).to.have.length(4); + })); + + + it('should create and reference new user task form (undo)', inject(function(canvas, commandStack) { + // when commandStack.undo(); - commandStack.redo(); - const userTaskForms = getUserTaskForms(rootElement); + // then + expect(getUserTaskForms(canvas.getRootElement())).to.have.length(3); + })); + + + it('should create and reference new user task form (redo)', inject(function(canvas, commandStack) { + + // when + commandStack.undo(); + commandStack.redo(); // then - expect(userTaskForms).to.have.length(3); + expect(getUserTaskForm(element)).to.exist; + expect(getUserTaskForm(element).get('id')).to.not.equal('UserTaskForm_1'); + expect(getUserTaskForms(canvas.getRootElement())).to.have.length(4); })); }); @@ -316,10 +397,15 @@ describe('camunda-cloud/features/modeling - FormsBehavior', function() { it('should remove extension elements', inject(function(canvas, elementRegistry, modeling) { // given - const userTask1 = elementRegistry.get('UserTask_1'), - userTask2 = elementRegistry.get('UserTask_2'); + const rootElement = canvas.getRootElement(); - modeling.removeElements([ userTask2 ]); + const userTask1 = elementRegistry.get('UserTask_1'); + + modeling.updateModdleProperties(rootElement, getBusinessObject(rootElement).get('extensionElements'), { + values: [ + getUserTaskForm(userTask1) + ] + }); const formDefinition = getFormDefinition(userTask1); @@ -331,8 +417,6 @@ describe('camunda-cloud/features/modeling - FormsBehavior', function() { // then expect(formDefinition.get('formKey')).not.to.exist; - const rootElement = canvas.getRootElement(); - const extensionElements = getBusinessObject(rootElement).get('extensionElements'); expect(extensionElements).not.to.exist; @@ -403,10 +487,15 @@ describe('camunda-cloud/features/modeling - FormsBehavior', function() { it('should remove extension elements', inject(function(canvas, elementRegistry, modeling) { // given - const userTask1 = elementRegistry.get('UserTask_1'), - userTask2 = elementRegistry.get('UserTask_2'); + const rootElement = canvas.getRootElement(); - modeling.removeElements([ userTask2 ]); + const userTask1 = elementRegistry.get('UserTask_1'); + + modeling.updateModdleProperties(rootElement, getBusinessObject(rootElement).get('extensionElements'), { + values: [ + getUserTaskForm(userTask1) + ] + }); const formDefinition = getFormDefinition(userTask1); @@ -416,8 +505,6 @@ describe('camunda-cloud/features/modeling - FormsBehavior', function() { }); // then - const rootElement = canvas.getRootElement(); - const extensionElements = getBusinessObject(rootElement).get('extensionElements'); expect(extensionElements).not.to.exist; diff --git a/test/camunda-cloud/process-user-tasks.bpmn b/test/camunda-cloud/process-user-tasks.bpmn index 86d1c31..09a72da 100644 --- a/test/camunda-cloud/process-user-tasks.bpmn +++ b/test/camunda-cloud/process-user-tasks.bpmn @@ -2,8 +2,9 @@ - { components: [ { label: "field", key: "field" } ] } - { components: [ { label: "anotherField", key: "field" } ] } + { components: [ { label: "foo", key: "field" } ] } + { components: [ { label: "bar", key: "field" } ] } + { components: [ { label: "baz", key: "field" } ] }