From e270ba0936d8a010953d366130a1a4a69886ada5 Mon Sep 17 00:00:00 2001 From: Martin Stamm Date: Fri, 1 Dec 2023 14:10:30 +0100 Subject: [PATCH] fix(Condition): use update logic for applying condtions fixes #32 --- .../ElementTemplatesConditionChecker.js | 96 +++--- src/cloud-element-templates/Helper.js | 4 + .../cmd/ChangeElementTemplateHandler.js | 279 ++++++++++++++---- .../util/rootElementUtil.js | 5 + .../ElementTemplateConditionChecker.spec.js | 44 ++- .../cmd/ChangeElementTemplateHandler.spec.js | 106 ++++++- .../fixtures/condition-update.json | 75 +++++ 7 files changed, 502 insertions(+), 107 deletions(-) create mode 100644 test/spec/cloud-element-templates/fixtures/condition-update.json diff --git a/src/cloud-element-templates/ElementTemplatesConditionChecker.js b/src/cloud-element-templates/ElementTemplatesConditionChecker.js index df50843..7cbca76 100644 --- a/src/cloud-element-templates/ElementTemplatesConditionChecker.js +++ b/src/cloud-element-templates/ElementTemplatesConditionChecker.js @@ -5,9 +5,9 @@ import { import { isObject } from 'min-dash'; import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; -import { setPropertyValue, unsetProperty } from './util/propertyUtil'; -import { MESSAGE_BINDING_TYPES, ZEEBE_TASK_DEFINITION, ZEEBE_TASK_DEFINITION_TYPE_TYPE } from './util/bindingTypes'; -import { removeMessage } from './util/rootElementUtil'; +import { ZEEBE_TASK_DEFINITION, ZEEBE_TASK_DEFINITION_TYPE_TYPE } from './util/bindingTypes'; + +const HIGH_PRIORITY = 2500; /** * Checks the conditions of an element template and sets/resets the @@ -33,6 +33,23 @@ export default class ElementTemplatesConditionChecker extends CommandInterceptor 'propertiesPanel.zeebe.changeTemplate', 'element.move' ], this._applyConditions, true, this); + + // Apply Conditions before changing properties. This persists the template so we can check if conditions apply + // after upgrading the template. + this.preExecute([ 'propertiesPanel.zeebe.changeTemplate' ], HIGH_PRIORITY, this._applyCondtions , true, this); + } + + _applyCondtions(context) { + const { + element, + newTemplate + } = context; + + if (!element || !newTemplate) { + return; + } + + context.newTemplate = applyConditions(context.element, context.newTemplate); } _saveConditionalState(context) { @@ -52,35 +69,38 @@ export default class ElementTemplatesConditionChecker extends CommandInterceptor _applyConditions(context) { const { element, - oldTemplate + hints = {} } = context; + + if (hints.skipConditionUpdate) { + return; + } + const template = this._elementTemplates.get(element); + // New Template is persisted before applying default values, + // new conditions might apply after the defaults are present. + const oldTemplate = context.oldTemplate || context.newTemplate; + if (!template || !oldTemplate || template.id !== oldTemplate.id) { return; } const newTemplate = applyConditions(element, template); - const propertiesToAdd = getMissingProperties(oldTemplate, newTemplate); - const propertiesToRemove = getPropertiesToRemove(newTemplate, oldTemplate); - - this._updateReferencedElement(element, oldTemplate, newTemplate); - - propertiesToAdd.forEach(property => - setPropertyValue(this._bpmnFactory, this._commandStack, element, property, property.value) - ); + if (!hasDifferentPropertyBindings(newTemplate, oldTemplate)) { + return; + } - propertiesToRemove.forEach(property => - unsetProperty(this._commandStack, element, property) - ); - } + const changeContext = { + element, + newTemplate, + oldTemplate, + hints: { skipConditionUpdate: true } + }; - _updateReferencedElement(element, oldTemplate, newTemplate) { - if (hasMessageProperties(oldTemplate) && !hasMessageProperties(newTemplate)) { - removeMessage(element, this._injector); - } + this._commandStack.execute('propertiesPanel.zeebe.changeTemplate', changeContext); } } @@ -96,6 +116,28 @@ ElementTemplatesConditionChecker.$inject = [ // helpers +function hasDifferentPropertyBindings(sourceTemplate, targetTemplate) { + return hasNewProperties(sourceTemplate, targetTemplate) || hasRemovedProperties(sourceTemplate, targetTemplate); +} + +function hasNewProperties(sourceTemplate, targetTemplate) { + let properties = targetTemplate.properties; + + return properties.some(targetProp =>!( + sourceTemplate.properties.find(sourceProp => compareProps(sourceProp, targetProp)) + )); +} + +function hasRemovedProperties(oldTemplate, newTemplate) { + const oldProperties = getMissingProperties(newTemplate, oldTemplate); + + // ensure XML properties are mantained for properties with + // different conditions but same bindings + return oldProperties.some(property => + !findPropertyWithBinding(newTemplate, property) + ); +} + function getMissingProperties(sourceTemplate, targetTemplate) { let properties = targetTemplate.properties; @@ -118,16 +160,6 @@ function findPropertyWithBinding(template, prop1) { ); } -function getPropertiesToRemove(newTemplate, oldTemplate) { - const oldProperties = getMissingProperties(newTemplate, oldTemplate); - - // ensure XML properties are mantained for properties with - // different conditions but same bindings - return oldProperties.filter(property => - !findPropertyWithBinding(newTemplate, property) - ); -} - function normalizeReplacer(key, value) { if (isObject(value)) { @@ -168,7 +200,3 @@ function normalizeBinding(binding) { function equals(a, b) { return JSON.stringify(a, normalizeReplacer) === JSON.stringify(b, normalizeReplacer); } - -function hasMessageProperties(template) { - return template.properties.some(p => MESSAGE_BINDING_TYPES.includes(p.binding.type)); -} diff --git a/src/cloud-element-templates/Helper.js b/src/cloud-element-templates/Helper.js index 19adc24..a36955c 100644 --- a/src/cloud-element-templates/Helper.js +++ b/src/cloud-element-templates/Helper.js @@ -143,6 +143,10 @@ export function findMessage(businessObject) { businessObject = eventDefinitions[0]; } + if (!businessObject) { + return; + } + return businessObject.get('messageRef'); } diff --git a/src/cloud-element-templates/cmd/ChangeElementTemplateHandler.js b/src/cloud-element-templates/cmd/ChangeElementTemplateHandler.js index a27893e..e720b6c 100644 --- a/src/cloud-element-templates/cmd/ChangeElementTemplateHandler.js +++ b/src/cloud-element-templates/cmd/ChangeElementTemplateHandler.js @@ -25,9 +25,8 @@ import { without } from 'min-dash'; -import { applyConditions } from '../Condition'; - import { + MESSAGE_BINDING_TYPES, MESSAGE_PROPERTY_TYPE, MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE, TASK_DEFINITION_TYPES @@ -41,17 +40,45 @@ import { createElement, getRoot } from '../../utils/ElementUtil'; +import { removeMessage } from '../util/rootElementUtil'; /** * Applies an element template to an element. Sets `zeebe:modelerTemplate` and * `zeebe:modelerTemplateVersion`. */ export default class ChangeElementTemplateHandler { - constructor(bpmnFactory, bpmnReplace, commandStack, modeling) { + constructor(bpmnFactory, bpmnReplace, commandStack, injector) { this._bpmnFactory = bpmnFactory; this._bpmnReplace = bpmnReplace; - this._commandStack = commandStack; - this._modeling = modeling; + + // Wrap commandStack and modeling to add hints to all commands + this._commandStackWrapper = { + execute: (event, context, ...rest) => { + commandStack.execute( + event, + { + hints: { skipConditionUpdate: true }, + ...context + }, + ...rest + ); + } + }; + this._modelingWrapper = { + updateModdleProperties: (element, moddleElement, properties) => + this._commandStackWrapper.execute('element.updateModdleProperties', { + element, + moddleElement, + properties + }), + updateProperties: (element, properties) => + this._commandStackWrapper.execute('element.updateProperties', { + element, + properties + }) + }; + + this._injector = injector; } /** @@ -79,11 +106,8 @@ export default class ChangeElementTemplateHandler { if (newTemplate) { - // do not apply properties that don't meet conditions - newTemplate = applyConditions(element, newTemplate); - // update element type - element = context.element = this._updateElementType(element, newTemplate); + element = context.element = this._updateElementType(element, oldTemplate, newTemplate); // update properties this._updateProperties(element, oldTemplate, newTemplate); @@ -100,19 +124,13 @@ export default class ChangeElementTemplateHandler { // update zeebe:Property properties this._updateZeebePropertyProperties(element, oldTemplate, newTemplate); - // update bpmn:Message properties - this._updateMessageProperties(element, oldTemplate, newTemplate); - - // update bpmn:Message zeebe:subscription properties - this._updateMessageZeebeSubscriptionProperties(element, oldTemplate, newTemplate); - - this._updateZeebeModelerTemplateOnReferencedElement(element, oldTemplate, newTemplate); + this._updateMessage(element, oldTemplate, newTemplate); } } _getOrCreateExtensionElements(element, businessObject = getBusinessObject(element)) { const bpmnFactory = this._bpmnFactory, - modeling = this._modeling; + modeling = this._modelingWrapper; let extensionElements = businessObject.get('extensionElements'); @@ -132,7 +150,7 @@ export default class ChangeElementTemplateHandler { } _updateZeebeModelerTemplate(element, newTemplate) { - const modeling = this._modeling; + const modeling = this._modelingWrapper; modeling.updateProperties(element, { 'zeebe:modelerTemplate': newTemplate && newTemplate.id, @@ -141,18 +159,18 @@ export default class ChangeElementTemplateHandler { } _updateZeebeModelerTemplateIcon(element, newTemplate) { - const modeling = this._modeling; + const modeling = this._modelingWrapper; const icon = newTemplate && newTemplate.icon; - modeling.updateProperties(element, { 'zeebe:modelerTemplateIcon': icon && icon.contents }); } _updateProperties(element, oldTemplate, newTemplate) { - const commandStack = this._commandStack; + const commandStack = this._commandStackWrapper; + const businessObject = getBusinessObject(element); const newProperties = newTemplate.properties.filter((newProperty) => { const newBinding = newProperty.binding, @@ -161,12 +179,31 @@ export default class ChangeElementTemplateHandler { return newBindingType === 'property'; }); + // Remove old Properties if no new Properties specified + const propertiesToRemove = oldTemplate && oldTemplate.properties.filter((oldProperty) => { + const oldBinding = oldProperty.binding, + oldBindingType = oldBinding.type; + + return oldBindingType === 'property' && !newProperties.find((newProperty) => newProperty.binding.name === oldProperty.binding.name); + }) || []; + + if (propertiesToRemove.length) { + const payload = propertiesToRemove.reduce((properties, property) => { + properties[property.binding.name] = undefined; + return properties; + }, {}); + + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: payload + }); + } + if (!newProperties.length) { return; } - const businessObject = getBusinessObject(element); - newProperties.forEach((newProperty) => { const oldProperty = findOldProperty(oldTemplate, newProperty), newBinding = newProperty.binding, @@ -200,7 +237,7 @@ export default class ChangeElementTemplateHandler { */ _updateZeebeTaskDefinition(element, oldTemplate, newTemplate) { const bpmnFactory = this._bpmnFactory, - commandStack = this._commandStack; + commandStack = this._commandStackWrapper; const newProperties = newTemplate.properties.filter((newProperty) => { const newBinding = newProperty.binding, @@ -209,31 +246,41 @@ export default class ChangeElementTemplateHandler { return TASK_DEFINITION_TYPES.includes(newBindingType); }); - // (1) do not override old task definition if no new properties specified + const businessObject = this._getOrCreateExtensionElements(element); + let taskDefinition = findExtension(businessObject, 'zeebe:TaskDefinition'); + + // (1) remove old task definition if no new properties specified + if (!newProperties.length) { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: { + values: without(businessObject.get('values'), taskDefinition) + } + }); + return; } - const businessObject = this._getOrCreateExtensionElements(element); newProperties.forEach((newProperty) => { const oldProperty = findOldProperty(oldTemplate, newProperty), - oldTaskDefinition = findBusinessObject(businessObject, newProperty), newPropertyValue = getDefaultValue(newProperty), newBinding = newProperty.binding, propertyName = getTaskDefinitionPropertyName(newBinding); // (2) update old task definition - if (oldTaskDefinition) { + if (taskDefinition) { - if (!shouldKeepValue(oldTaskDefinition, oldProperty, newProperty)) { + if (!shouldKeepValue(taskDefinition, oldProperty, newProperty)) { const properties = { [propertyName]: newPropertyValue }; commandStack.execute('element.updateModdleProperties', { element, - moddleElement: oldTaskDefinition, + moddleElement: taskDefinition, properties }); } @@ -245,19 +292,39 @@ export default class ChangeElementTemplateHandler { [propertyName]: newPropertyValue }; - const newTaskDefinition = createTaskDefinition(properties, bpmnFactory); + taskDefinition = createTaskDefinition(properties, bpmnFactory); - newTaskDefinition.$parent = businessObject; + taskDefinition.$parent = businessObject; commandStack.execute('element.updateModdleProperties', { element, moddleElement: businessObject, properties: { - values: [ ...businessObject.get('values'), newTaskDefinition ] + values: [ ...businessObject.get('values'), taskDefinition ] } }); } }); + + // (4) remove properties no longer templated + const oldProperties = oldTemplate && oldTemplate.properties.filter((oldProperty) => { + const oldBinding = oldProperty.binding, + oldBindingType = oldBinding.type; + + return TASK_DEFINITION_TYPES.includes(oldBindingType) && !newProperties.find((newProperty) => newProperty.binding.property === oldProperty.binding.property); + }) || []; + + oldProperties.forEach((oldProperty) => { + const properties = { + [oldProperty.binding.property]: undefined + }; + + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: taskDefinition, + properties + }); + }); } /** @@ -270,7 +337,7 @@ export default class ChangeElementTemplateHandler { */ _updateZeebeInputOutputParameterProperties(element, oldTemplate, newTemplate) { const bpmnFactory = this._bpmnFactory, - commandStack = this._commandStack; + commandStack = this._commandStackWrapper; const newProperties = newTemplate.properties.filter((newProperty) => { const newBinding = newProperty.binding, @@ -427,7 +494,7 @@ export default class ChangeElementTemplateHandler { */ _updateZeebeTaskHeaderProperties(element, oldTemplate, newTemplate) { const bpmnFactory = this._bpmnFactory, - commandStack = this._commandStack; + commandStack = this._commandStackWrapper; const newProperties = newTemplate.properties.filter((newProperty) => { const newBinding = newProperty.binding, @@ -534,7 +601,7 @@ export default class ChangeElementTemplateHandler { */ _updateZeebePropertyProperties(element, oldTemplate, newTemplate) { const bpmnFactory = this._bpmnFactory, - commandStack = this._commandStack; + commandStack = this._commandStackWrapper; const newProperties = newTemplate.properties.filter((newProperty) => { const newBinding = newProperty.binding, @@ -632,6 +699,21 @@ export default class ChangeElementTemplateHandler { } } + _updateMessage(element, oldTemplate, newTemplate) { + + // update bpmn:Message properties + this._updateMessageProperties(element, oldTemplate, newTemplate); + + // update bpmn:Message zeebe:subscription properties + this._updateMessageZeebeSubscriptionProperties(element, oldTemplate, newTemplate); + + this._updateZeebeModelerTemplateOnReferencedElement(element, oldTemplate, newTemplate); + + if (!hasMessageProperties(newTemplate)) { + removeMessage(element, this._injector); + } + } + /** * Update bpmn:Message properties. * @@ -647,11 +729,26 @@ export default class ChangeElementTemplateHandler { return newBindingType === MESSAGE_PROPERTY_TYPE; }); + const removedProperties = oldTemplate && oldTemplate.properties.filter((oldProperty) => { + const oldBinding = oldProperty.binding, + oldBindingType = oldBinding.type; + + return oldBindingType === MESSAGE_PROPERTY_TYPE && !newProperties.find((newProperty) => newProperty.binding.name === oldProperty.binding.name); + }) || []; + + let message = this._getMessage(element); + message && removedProperties.forEach((removedProperty) => { + + this._modelingWrapper.updateModdleProperties(element, message, { + [removedProperty.binding.name]: undefined + }); + }); + if (!newProperties.length) { return; } - const message = this._getOrCreateMessage(element, newTemplate); + message = this._getOrCreateMessage(element, newTemplate); newProperties.forEach((newProperty) => { const oldProperty = findOldProperty(oldTemplate, newProperty), @@ -668,7 +765,7 @@ export default class ChangeElementTemplateHandler { properties[ newBindingName ] = newPropertyValue; - this._modeling.updateModdleProperties(element, changedElement, properties); + this._modelingWrapper.updateModdleProperties(element, changedElement, properties); }); } @@ -687,30 +784,63 @@ export default class ChangeElementTemplateHandler { return newBindingType === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE; }); - if (!newProperties.length) { + const removedProperties = oldTemplate && oldTemplate.properties.filter((oldProperty) => { + const oldBinding = oldProperty.binding, + oldBindingType = oldBinding.type; + + return oldBindingType === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE && !newProperties.find((newProperty) => newProperty.binding.name === oldProperty.binding.name); + }) || []; + + if (!newProperties.length && !removedProperties.length) { return; } const message = this._getOrCreateMessage(element, newTemplate); - const zeebeSubscription = this._getOrCreateExtension(element, message, 'zeebe:Subscription'); + const messageExtensionElements = this._getOrCreateExtensionElements(element, message); + const zeebeSubscription = this._getSubscription(element, message); - newProperties.forEach((newProperty) => { + const propertiesToSet = newProperties.reduce((properties, newProperty) => { const oldProperty = findOldProperty(oldTemplate, newProperty), newBinding = newProperty.binding, newBindingName = newBinding.name, newPropertyValue = getDefaultValue(newProperty), changedElement = zeebeSubscription; - let properties = {}; - if (shouldKeepValue(changedElement, oldProperty, newProperty)) { - return; + return properties; } properties[ newBindingName ] = newPropertyValue; + return properties; + }, {}); + + // Update zeebe Subscription + if (zeebeSubscription) { + this._modelingWrapper.updateModdleProperties(element, zeebeSubscription, + propertiesToSet + ); + } else { - this._modeling.updateModdleProperties(element, changedElement, properties); - }); + // create new Subscription + const newSubscription = createElement('zeebe:Subscription', propertiesToSet, message, this._bpmnFactory); + this._modelingWrapper.updateModdleProperties(element, messageExtensionElements, { + values: [ ...messageExtensionElements.get('values'), newSubscription ] + }); + } + + // Remove old properties + if (!oldTemplate || !zeebeSubscription) { + return; + } + + const propertiesToRemove = removedProperties.reduce((properties, removedProperty) => { + properties[ removedProperty.binding.name ] = undefined; + return properties; + }, {}); + + this._modelingWrapper.updateModdleProperties(element, zeebeSubscription, + propertiesToRemove + ); } _updateZeebeModelerTemplateOnReferencedElement(element, oldTemplate, newTemplate) { @@ -726,49 +856,52 @@ export default class ChangeElementTemplateHandler { return; } - this._modeling.updateModdleProperties(element, message, { + this._modelingWrapper.updateModdleProperties(element, message, { 'zeebe:modelerTemplate': newTemplate.id }); } - _getOrCreateExtension(element, bo, type,) { + _getSubscription(element, bo) { const extensionElements = this._getOrCreateExtensionElements(element, bo); - const extension = findExtension(extensionElements, type); + const extension = findExtension(extensionElements, 'zeebe:Subscription'); if (extension) { return extension; } - - const newExtension = createElement(type, {}, bo, this._bpmnFactory); - - this._modeling.updateModdleProperties(element, extensionElements, { - values: [ ...extensionElements.get('values'), newExtension ] - }); - - return newExtension; } _getOrCreateMessage(element, template) { + return this._getMessage(element) || this._createMessage(element, template); + } + + _createMessage(element, template) { let bo = getBusinessObject(element); if (is(bo, 'bpmn:Event')) { bo = bo.get('eventDefinitions')[0]; } - let message = bo.get('messageRef'); + const message = this._bpmnFactory.create('bpmn:Message', { 'zeebe:modelerTemplate': template.id }); - if (!message) { - message = this._bpmnFactory.create('bpmn:Message', { 'zeebe:modelerTemplate': template.id }); + message.$parent = getRoot(bo); - message.$parent = getRoot(bo); + this._modelingWrapper.updateModdleProperties(element, bo, { messageRef: message }); - this._modeling.updateModdleProperties(element, bo, { messageRef: message }); + return message; + } + + _getMessage(element) { + let bo = getBusinessObject(element); + + if (is(bo, 'bpmn:Event')) { + bo = bo.get('eventDefinitions')[0]; } - return message; + return bo && bo.get('messageRef'); } + /** * Replaces the element with the specified elementType. * Takes into account the eventDefinition for events. @@ -776,7 +909,7 @@ export default class ChangeElementTemplateHandler { * @param {djs.model.Base} element * @param {Object} newTemplate */ - _updateElementType(element, newTemplate) { + _updateElementType(element, oldTemplate, newTemplate) { // determine new task type const newType = newTemplate.elementType; @@ -785,6 +918,13 @@ export default class ChangeElementTemplateHandler { return element; } + const oldType = oldTemplate && oldTemplate.elementType; + + // Do not replace if the element type did not change + if (oldType && oldType.value === newType.value && oldType.eventDefinition === newType.eventDefinition) { + return element; + } + const replacement = { type: newType.value }; if (newType.eventDefinition) { @@ -801,7 +941,7 @@ ChangeElementTemplateHandler.$inject = [ 'bpmnFactory', 'bpmnReplace', 'commandStack', - 'modeling' + 'injector' ]; @@ -1045,6 +1185,10 @@ function propertyChanged(element, oldProperty) { function getPropertyValue(element, property) { const businessObject = getBusinessObject(element); + if (!businessObject) { + return; + } + const binding = property.binding, bindingName = binding.name, bindingType = binding.type; @@ -1094,3 +1238,8 @@ function remove(array, item) { return array; } + + +function hasMessageProperties(template) { + return template.properties.some(p => MESSAGE_BINDING_TYPES.includes(p.binding.type)); +} diff --git a/src/cloud-element-templates/util/rootElementUtil.js b/src/cloud-element-templates/util/rootElementUtil.js index 5fe64cb..59fdb14 100644 --- a/src/cloud-element-templates/util/rootElementUtil.js +++ b/src/cloud-element-templates/util/rootElementUtil.js @@ -36,6 +36,11 @@ export function removeMessage(element, injector) { const bo = getReferringElement(element); + // Event does not have an event definition + if (!bo) { + return; + } + const message = findMessage(bo); if (!message) { diff --git a/test/spec/cloud-element-templates/ElementTemplateConditionChecker.spec.js b/test/spec/cloud-element-templates/ElementTemplateConditionChecker.spec.js index b4f3713..1a3b575 100644 --- a/test/spec/cloud-element-templates/ElementTemplateConditionChecker.spec.js +++ b/test/spec/cloud-element-templates/ElementTemplateConditionChecker.spec.js @@ -21,6 +21,8 @@ import messageDiagramXML from './fixtures/condition-message.bpmn'; import messageCorrelationDiagramXML from './fixtures/message-correlation-key.bpmn'; import template from './fixtures/condition.json'; +import updateTemplates from './fixtures/condition-update.json'; + import messageTemplates from './fixtures/condition-message.json'; import messageCorrelationTemplate from './fixtures/message-correlation-key.json'; @@ -78,8 +80,8 @@ describe('provider/cloud-element-templates - ElementTemplatesConditionChecker', // then expect(businessObject.get('customProperty')).to.exist; - expect(businessObject.get('noDefaultProperty')).to.exist; - expect(businessObject.get('noDefaultProperty')).to.equal(''); + // empty values are not persisted in XML + expect(businessObject.get('noDefaultProperty')).not.to.exist; expect(businessObject.get('isActiveCondition')).to.exist; expect(businessObject.get('isActiveCondition')).to.equal('otherProperty visible'); @@ -1189,6 +1191,44 @@ describe('provider/cloud-element-templates - ElementTemplatesConditionChecker', }) ); }); + + + describe('update template', function() { + + it('should keep property value when condition property is still active', inject(function(elementRegistry, modeling) { + + // given + const element = elementRegistry.get('Task_1'); + changeTemplate(element, updateTemplates[0]); + + modeling.updateProperties(element, { + name: 'foo' + }); + + const businessObject = getBusinessObject(element); + + // assume + expect(businessObject.get('customProperty')).to.exist; + expect(businessObject.get('customProperty')).to.eql('defaultValue'); + + + // when + modeling.updateProperties(element, { + customProperty: 'customValue' + }); + + // assume + expect(businessObject.get('customProperty')).to.eql('customValue'); + + // when + changeTemplate(element, updateTemplates[1], updateTemplates[0]); + + // then + expect(businessObject.get('customProperty')).to.eql('customValue'); + + })); + + }); }); diff --git a/test/spec/cloud-element-templates/cmd/ChangeElementTemplateHandler.spec.js b/test/spec/cloud-element-templates/cmd/ChangeElementTemplateHandler.spec.js index 70bc8ec..29adc24 100644 --- a/test/spec/cloud-element-templates/cmd/ChangeElementTemplateHandler.spec.js +++ b/test/spec/cloud-element-templates/cmd/ChangeElementTemplateHandler.spec.js @@ -10,6 +10,7 @@ import CoreModule from 'bpmn-js/lib/core'; import ElementTemplatesModule from 'src/cloud-element-templates'; import ModelingModule from 'bpmn-js/lib/features/modeling'; import { BpmnPropertiesPanelModule } from 'bpmn-js-properties-panel'; +import ZeebeBehaviorsModule from 'camunda-bpmn-js-behaviors/lib/camunda-cloud'; import zeebeModdlePackage from 'zeebe-bpmn-moddle/resources/zeebe'; @@ -48,7 +49,8 @@ const modules = [ BpmnPropertiesPanelModule, { propertiesPanel: [ 'value', { registerProvider() {} } ] - } + }, + ZeebeBehaviorsModule ]; const moddleExtensions = { @@ -359,7 +361,7 @@ describe('cloud-element-templates/cmd - ChangeElementTemplateHandler', function( const newTemplate = require('./task-template-no-properties.json'); - it('should not override existing', inject(function(elementRegistry) { + it('should remove task definition', inject(function(elementRegistry) { // given const task = elementRegistry.get('Task_1'); @@ -372,8 +374,7 @@ describe('cloud-element-templates/cmd - ChangeElementTemplateHandler', function( const taskDefinition = findExtension(task, 'zeebe:TaskDefinition'); - expect(taskDefinition).to.exist; - expect(taskDefinition.get('type')).to.equal('task-type-old'); + expect(taskDefinition).not.to.exist; })); }); @@ -1498,7 +1499,6 @@ describe('cloud-element-templates/cmd - ChangeElementTemplateHandler', function( const newTemplate = require('./event-template-1.json'); - it('execute', inject(function(bpmnjs, elementRegistry) { // given @@ -1561,6 +1561,24 @@ describe('cloud-element-templates/cmd - ChangeElementTemplateHandler', function( expect(message.$parent).to.equal(bpmnjs.getDefinitions()); })); + + it('should remove bpmn:Message if bpmn:Message#property not specified', inject(function(bpmnjs, elementRegistry) { + + // given + let event = elementRegistry.get('Event_1'); + event = changeTemplate(event, newTemplate); + + // when + const emptyTemplate = createTemplate([]); + changeTemplate(event, emptyTemplate, newTemplate); + + // then + event = elementRegistry.get('Event_1'); + const message = findMessage(getBusinessObject(event)); + + expect(message).not.to.exist; + })); + }); @@ -1630,6 +1648,55 @@ describe('cloud-element-templates/cmd - ChangeElementTemplateHandler', function( expect(subscription).to.exist; expect(subscription.get('correlationKey')).to.equal('correlationKey'); })); + + + it('should remove message if no Message property is set', inject(function(elementRegistry) { + + // given + let event = elementRegistry.get('Event_1'); + + const emptyTemplate = createTemplate([]); + event = changeTemplate(event, newTemplate); + + // when + changeTemplate(event, emptyTemplate, newTemplate); + + // then + event = elementRegistry.get('Event_1'); + + const message = findMessage(getBusinessObject(event)); + expect(message).not.to.exist; + })); + + + it('should remove Subscription if no subscription property is set', inject(function(elementRegistry) { + + // given + let event = elementRegistry.get('Event_1'); + + const noSubscription = createTemplate({ + 'value': 'foobar', + 'binding': { + 'type': 'bpmn:Message#property', + 'name': 'name' + } + }); + + event = changeTemplate(event, newTemplate); + + // when + changeTemplate(event, noSubscription, newTemplate); + + // then + event = elementRegistry.get('Event_1'); + + const message = findMessage(getBusinessObject(event)); + const subscription = findZeebeSubscription(message); + + expect(message).to.exist; + expect(subscription).not.to.exist; + })); + }); @@ -1867,7 +1934,6 @@ describe('cloud-element-templates/cmd - ChangeElementTemplateHandler', function( beforeEach(bootstrap(require('./task.bpmn').default)); - it('property changed', inject(function(elementRegistry) { // given @@ -1943,6 +2009,34 @@ describe('cloud-element-templates/cmd - ChangeElementTemplateHandler', function( expect(name).to.equal('task-new-name'); })); + + it('property removed', inject(function(elementRegistry) { + + // given + const task = elementRegistry.get('Task_1'), + businessObject = getBusinessObject(task); + + const oldTemplate = createTemplate({ + value: 'task-old-name', + binding: { + type: 'property', + name: 'name' + } + }); + + const newTemplate = createTemplate([]); + + changeTemplate('Task_1', oldTemplate); + + // when + changeTemplate('Task_1', newTemplate, oldTemplate); + + // then + const name = businessObject.get('bpmn:name'); + + expect(name).not.to.exist; + })); + }); }); diff --git a/test/spec/cloud-element-templates/fixtures/condition-update.json b/test/spec/cloud-element-templates/fixtures/condition-update.json new file mode 100644 index 0000000..8e9d0f4 --- /dev/null +++ b/test/spec/cloud-element-templates/fixtures/condition-update.json @@ -0,0 +1,75 @@ +[ + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "name": "Condition", + "id": "example.com.condition", + "version": "1", + "description": "A conditional template.", + "appliesTo": ["bpmn:Task"], + "properties": [ + { + "id": "nameProp", + "label": "name", + "type": "String", + "binding": { + "type": "property", + "name": "name" + } + }, + { + "id": "otherProperty", + "label": "property", + "type": "String", + "value": "defaultValue", + "binding": { + "type": "property", + "name": "customProperty" + }, + "condition": { + "type": "simple", + "property": "nameProp", + "oneOf": [ + "foo" + ] + } + } + ] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "name": "Condition", + "version": "2", + "id": "example.com.condition", + "description": "A conditional template.", + "appliesTo": ["bpmn:Task"], + "properties": [ + { + "id": "nameProp", + "label": "name", + "type": "String", + "binding": { + "type": "property", + "name": "name" + } + }, + { + "id": "otherProperty", + "label": "property", + "type": "String", + "value": "defaultValue", + "binding": { + "type": "property", + "name": "customProperty" + }, + "condition": { + "type": "simple", + "property": "nameProp", + "oneOf": [ + "foo", + "bar" + ] + } + } + ] + } +] \ No newline at end of file