Skip to content

Commit

Permalink
feat: support zeebe:calledElement templating
Browse files Browse the repository at this point in the history
  • Loading branch information
barmac committed Dec 7, 2023
1 parent 3124881 commit 165f3ad
Show file tree
Hide file tree
Showing 21 changed files with 991 additions and 8 deletions.
61 changes: 61 additions & 0 deletions src/cloud-element-templates/CalledElementBehavior.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from 'bpmn-js/lib/util/ModelUtil';

import { findExtension } from './Helper';

/**
* Enforces no variable propagation for templated call activities.
*/
export class CalledElementBehavior extends CommandInterceptor {

/**
* @param {*} eventBus
* @param {*} modeling
* @param {import('./ElementTemplates').default} elementTemplates
*/
constructor(eventBus, modeling, elementTemplates) {
super(eventBus);

this._modeling = modeling;
this._elementTemplates = elementTemplates;

this.postExecuted([
'element.updateProperties', 'element.updateModdleProperties'
], this._ensureNoPropagation, true, this);
}

_ensureNoPropagation(context) {
const { element } = context;

if (!this._elementTemplates.get(element)) {
return;
}

if (!is(element, 'bpmn:CallActivity')) {
return;
}

const calledElement = findExtension(element, 'zeebe:CalledElement');

if (!calledElement) {
return;
}

for (const property of [
'propagateAllChildVariables',
'propagateAllParentVariables'
]) {
if (calledElement.get(property) !== false) {
this._modeling.updateModdleProperties(element, calledElement, {
[property]: false
});
}
}
}
}

CalledElementBehavior.$inject = [
'eventBus',
'modeling',
'elementTemplates'
];
12 changes: 12 additions & 0 deletions src/cloud-element-templates/CreateHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ export function createZeebeProperty(binding, value = '', bpmnFactory) {
});
}

/**
* Create a called element representing the given value.
*
* @param {object} attrs
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
export function createCalledElement(attrs = {}, bpmnFactory) {
return bpmnFactory.create('zeebe:CalledElement', attrs);
}

/**
* Retrieves whether an element should be updated for a given property.
*
Expand Down
107 changes: 106 additions & 1 deletion src/cloud-element-templates/cmd/ChangeElementTemplateHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '../Helper';

import {
createCalledElement,
createInputParameter,
createOutputParameter,
createTaskDefinition,
Expand All @@ -29,7 +30,8 @@ import {
MESSAGE_BINDING_TYPES,
MESSAGE_PROPERTY_TYPE,
MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE,
TASK_DEFINITION_TYPES
TASK_DEFINITION_TYPES,
ZEEBE_CALLED_ELEMENT
} from '../util/bindingTypes';

import {
Expand Down Expand Up @@ -125,6 +127,8 @@ export default class ChangeElementTemplateHandler {
this._updateZeebePropertyProperties(element, oldTemplate, newTemplate);

this._updateMessage(element, oldTemplate, newTemplate);

this._updateCalledElement(element, oldTemplate, newTemplate);
}
}

Expand Down Expand Up @@ -902,6 +906,107 @@ export default class ChangeElementTemplateHandler {
}



/**
* Update `zeebe:CalledElement` properties of specified business object. This
* can only exist in `bpmn:ExtensionElements`.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateCalledElement(element, oldTemplate, newTemplate) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStackWrapper;

const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;

return newBindingType === ZEEBE_CALLED_ELEMENT;
});

const businessObject = this._getOrCreateExtensionElements(element);
let calledElement = findExtension(businessObject, 'zeebe:CalledElement');

// (1) remove old called element if no new properties specified
if (!newProperties.length) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
values: without(businessObject.get('values'), calledElement)
}
});

return;
}


newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty(oldTemplate, newProperty),
newPropertyValue = getDefaultValue(newProperty),
propertyName = newProperty.binding.property;

// (2) update old called element
if (calledElement) {

if (!shouldKeepValue(calledElement, oldProperty, newProperty)) {
const properties = {
[propertyName]: newPropertyValue
};

commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: calledElement,
properties
});
}
}

// (3) add new called element
else {
const properties = {
[propertyName]: newPropertyValue
};

calledElement = createCalledElement(properties, bpmnFactory);

calledElement.$parent = businessObject;

commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
values: [ ...businessObject.get('values'), calledElement ]
}
});
}
});

// (4) remove properties no longer templated
const oldProperties = oldTemplate && oldTemplate.properties.filter((oldProperty) => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;

return oldBindingType === ZEEBE_CALLED_ELEMENT && !newProperties.find(
(newProperty) => newProperty.binding.property === oldProperty.binding.property
);
}) || [];

oldProperties.forEach((oldProperty) => {
const properties = {
[oldProperty.binding.property]: undefined
};

commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: calledElement,
properties
});
});
}

/**
* Replaces the element with the specified elementType.
* Takes into account the eventDefinition for events.
Expand Down
36 changes: 36 additions & 0 deletions src/cloud-element-templates/create/CalledElementBindingProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
ensureExtension
} from '../CreateHelper';
import { getDefaultValue } from '../Helper';


export class CalledElementBindingProvider {
static create(element, options) {
const {
property,
bpmnFactory
} = options;

const {
binding
} = property;

const {
property: propertyName
} = binding;

const value = getDefaultValue(property);

const calledElement = ensureExtension(element, 'zeebe:CalledElement', bpmnFactory);

// TODO(@barmac): remove if we decide to support propagation in templates
ensureNoPropagation(calledElement);

calledElement.set(propertyName, value);
}
}

function ensureNoPropagation(calledElement) {
calledElement.set('propagateAllChildVariables', false);
calledElement.set('propagateAllParentVariables', false);
}
7 changes: 5 additions & 2 deletions src/cloud-element-templates/create/TemplateElementFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import TaskHeaderBindingProvider from './TaskHeaderBindingProvider';
import ZeebePropertiesProvider from './ZeebePropertiesProvider';
import { MessagePropertyBindingProvider } from './MessagePropertyBindingProvider';
import { MessageZeebeSubscriptionBindingProvider } from './MessageZeebeSubscriptionBindingProvider';
import { CalledElementBindingProvider } from './CalledElementBindingProvider';

import {
MESSAGE_PROPERTY_TYPE,
Expand All @@ -22,7 +23,8 @@ import {
ZEBBE_INPUT_TYPE,
ZEEBE_OUTPUT_TYPE,
ZEEBE_TASK_HEADER_TYPE,
ZEBBE_PROPERTY_TYPE
ZEBBE_PROPERTY_TYPE,
ZEEBE_CALLED_ELEMENT
} from '../util/bindingTypes';

import {
Expand All @@ -44,7 +46,8 @@ export default class TemplateElementFactory {
[ZEEBE_OUTPUT_TYPE]: OutputBindingProvider,
[ZEEBE_TASK_HEADER_TYPE]: TaskHeaderBindingProvider,
[MESSAGE_PROPERTY_TYPE]: MessagePropertyBindingProvider,
[MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE]: MessageZeebeSubscriptionBindingProvider
[MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE]: MessageZeebeSubscriptionBindingProvider,
[ZEEBE_CALLED_ELEMENT]: CalledElementBindingProvider
};
}

Expand Down
7 changes: 5 additions & 2 deletions src/cloud-element-templates/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ElementTemplatesPropertiesProvider from './ElementTemplatesPropertiesProv
import UpdateTemplatePropertiesOrder from './UpdateTemplatePropertiesOrder';
import { ReferencedElementBehavior } from './ReferencedElementBehavior';
import { GeneratedValueBehavior } from './GeneratedValueBehavior';
import { CalledElementBehavior } from './CalledElementBehavior';


export default {
Expand All @@ -25,7 +26,8 @@ export default {
'elementTemplatesConditionChecker',
'generatedValueBehavior',
'referencedElementBehavior',
'updateTemplatePropertiesOrder'
'updateTemplatePropertiesOrder',
'calledElementBehavior'
],
elementTemplates: [ 'type', ElementTemplates ],
elementTemplatesLoader: [ 'type', ElementTemplatesLoader ],
Expand All @@ -34,5 +36,6 @@ export default {
elementTemplatesConditionChecker: [ 'type', ElementTemplatesConditionChecker ],
generatedValueBehavior: [ 'type', GeneratedValueBehavior ],
referencedElementBehavior: [ 'type', ReferencedElementBehavior ],
updateTemplatePropertiesOrder: [ 'type', UpdateTemplatePropertiesOrder ]
updateTemplatePropertiesOrder: [ 'type', UpdateTemplatePropertiesOrder ],
calledElementBehavior: [ 'type', CalledElementBehavior ]
};
4 changes: 3 additions & 1 deletion src/cloud-element-templates/util/bindingTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const ZEEBE_TASK_DEFINITION = 'zeebe:taskDefinition';
export const ZEEBE_TASK_HEADER_TYPE = 'zeebe:taskHeader';
export const MESSAGE_PROPERTY_TYPE = 'bpmn:Message#property';
export const MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE = 'bpmn:Message#zeebe:subscription#property';
export const ZEEBE_CALLED_ELEMENT = 'zeebe:calledElement';

export const EXTENSION_BINDING_TYPES = [
MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE,
Expand All @@ -17,7 +18,8 @@ export const EXTENSION_BINDING_TYPES = [
ZEEBE_PROPERTY_TYPE,
ZEEBE_TASK_DEFINITION_TYPE_TYPE,
ZEEBE_TASK_DEFINITION,
ZEEBE_TASK_HEADER_TYPE
ZEEBE_TASK_HEADER_TYPE,
ZEEBE_CALLED_ELEMENT
];

export const TASK_DEFINITION_TYPES = [
Expand Down
42 changes: 41 additions & 1 deletion src/cloud-element-templates/util/propertyUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {
ZEBBE_INPUT_TYPE,
ZEEBE_OUTPUT_TYPE,
ZEEBE_PROPERTY_TYPE,
ZEEBE_TASK_HEADER_TYPE
ZEEBE_TASK_HEADER_TYPE,
ZEEBE_CALLED_ELEMENT
} from './bindingTypes';

import {
Expand Down Expand Up @@ -194,6 +195,13 @@ export function getPropertyValue(element, property, scope) {
return defaultValue;
}

// zeebe:calledElement
if (type === ZEEBE_CALLED_ELEMENT) {
const calledElement = findExtension(businessObject, 'zeebe:CalledElement');

return calledElement ? calledElement.get(bindingProperty) : defaultValue;
}

// should never throw as templates are validated beforehand
throw unknownBindingError(element, property);
}
Expand Down Expand Up @@ -528,6 +536,38 @@ export function setPropertyValue(bpmnFactory, commandStack, element, property, v
}
}

// zeebe:calledElement
if (type === ZEEBE_CALLED_ELEMENT) {
let calledElement = findExtension(element, 'zeebe:CalledElement');
const propertyName = binding.property;

const properties = {
[ propertyName ]: value || ''
};

if (calledElement) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
properties,
moddleElement: calledElement
}
});
} else {
calledElement = createElement('zeebe:CalledElement', properties, extensionElements, bpmnFactory);

commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: extensionElements,
properties: { values: [ ...extensionElements.get('values'), calledElement ] }
}
});
}
}

if (commands.length) {
const commandsToExecute = commands.filter((command) => command !== NO_OP);

Expand Down
Loading

0 comments on commit 165f3ad

Please sign in to comment.