Skip to content

Commit

Permalink
feat: add execution listeners for Zeebe
Browse files Browse the repository at this point in the history
  • Loading branch information
barmac committed Jul 5, 2024
1 parent 2c3bfd1 commit 623deab
Show file tree
Hide file tree
Showing 4 changed files with 337 additions and 0 deletions.
18 changes: 18 additions & 0 deletions src/provider/zeebe/ZeebePropertiesProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ConditionProps,
ErrorProps,
EscalationProps,
ExecutionListenersProps,
FormProps,
HeaderProps,
InputPropagationProps,
Expand Down Expand Up @@ -51,6 +52,7 @@ const ZEEBE_GROUPS = [
OutputPropagationGroup,
OutputGroup,
HeaderGroup,
ExecutionListenersGroup,
ExtensionPropertiesGroup
];

Expand Down Expand Up @@ -299,6 +301,22 @@ function AssignmentDefinitionGroup(element, injector) {
return group.entries.length ? group : null;
}

function ExecutionListenersGroup(element, injector) {
const translate = injector.get('translate');
const group = {
label: translate('Execution listeners'),
id: 'Zeebe__ExecutionListeners',
component: ListGroup,
...ExecutionListenersProps({ element, injector })
};

if (group.items) {
return group;
}

return null;
}

function ExtensionPropertiesGroup(element, injector) {
const translate = injector.get('translate');
const group = {
Expand Down
140 changes: 140 additions & 0 deletions src/provider/zeebe/properties/ExecutionListener.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { SelectEntry } from '@bpmn-io/properties-panel';

import {
useService
} from '../../../hooks';

import { FeelEntryWithVariableContext } from '../../../entries/FeelEntryWithContext';


export default function ExecutionListener(props) {

const {
idPrefix,
listener
} = props;

const entries = [
{
id: idPrefix + '-eventType',
component: EventType,
idPrefix,
listener
},
{
id: idPrefix + '-listenerType',
component: ListenerType,
idPrefix,
listener
},
{
id: idPrefix + '-retries',
component: Retries,
idPrefix,
listener
}
];

return entries;
}

function EventType(props) {
const {
idPrefix,
element,
listener
} = props;

const modeling = useService('modeling');
const translate = useService('translate');

const getOptions = () => {
return [
{ value: 'start', label: translate('Start') },
{ value: 'end', label: translate('End') }
];
};

const setValue = (value) => {
modeling.updateModdleProperties(element, listener, {
eventType: value
});
};

const getValue = () => {
return listener.get('eventType');
};

return SelectEntry({
element: listener,
id: idPrefix + '-eventType',
label: translate('Event type'),
getValue,
setValue,
getOptions
});
}

function ListenerType(props) {
const {
idPrefix,
element,
listener
} = props;

const modeling = useService('modeling');
const translate = useService('translate');
const debounce = useService('debounceInput');

const setValue = (value) => {
modeling.updateModdleProperties(element, listener, {
type: value
});
};

const getValue = () => {
return listener.get('type');
};

return FeelEntryWithVariableContext({
element: listener,
id: idPrefix + '-listenerType',
label: translate('Listener type'),
getValue,
setValue,
debounce,
feel: 'optional'
});
}

function Retries(props) {
const {
idPrefix,
element,
listener
} = props;

const modeling = useService('modeling');
const translate = useService('translate');
const debounce = useService('debounceInput');

const setValue = (value) => {
modeling.updateModdleProperties(element, listener, {
retries: value
});
};

const getValue = () => {
return listener.get('retries');
};

return FeelEntryWithVariableContext({
element: listener,
id: idPrefix + '-retries',
label: translate('Retries'),
getValue,
setValue,
debounce,
feel: 'optional'
});
}
178 changes: 178 additions & 0 deletions src/provider/zeebe/properties/ExecutionListenersProps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import {
getBusinessObject,
is
} from 'bpmn-js/lib/util/ModelUtil';

import ExecutionListenerProperty from './ExecutionListener';

import {
createElement
} from '../../../utils/ElementUtil';

import {
getExtensionElementsList
} from '../../../utils/ExtensionElementsUtil';

import { without } from 'min-dash';

const EVENT_TO_LABEL = {
'start': 'Start',
'end': 'End'
};

const DEFAULT_LISTENER_PROPS = {
eventType: 'start'
};


export function ExecutionListenersProps({ element, injector }) {
let businessObject = getRelevantBusinessObject(element);

// do not offer for empty pools
if (!businessObject) {
return;
}

const listeners = getListenersList(businessObject) || [];

const bpmnFactory = injector.get('bpmnFactory'),
commandStack = injector.get('commandStack'),
modeling = injector.get('modeling'),
translate = injector.get('translate');

const items = listeners.map((listener, index) => {
const id = element.id + '-executionListener-' + index;
const type = listener.get('type') || '<no type>';

return {
id,
label: translate(`${EVENT_TO_LABEL[listener.get('eventType')]}: {type}`, { type }),
entries: ExecutionListenerProperty({
idPrefix: id,
element,
listener
}),
autoFocusEntry: id + '-eventType',
remove: removeFactory({ modeling, element, listener })
};
});

return {
items,
add: addFactory({ bpmnFactory, commandStack, element }),
shouldSort: false
};
}

function removeFactory({ modeling, element, listener }) {
return function(event) {
event.stopPropagation();

const businessObject = getRelevantBusinessObject(element);
const container = getExecutionListenersContainer(businessObject);

if (!container) {
return;
}

const listeners = without(container.get('listeners'), listener);

modeling.updateModdleProperties(element, container, { listeners });
};
}

function addFactory({ bpmnFactory, commandStack, element }) {
return function(event) {
event.stopPropagation();

let commands = [];

const businessObject = getRelevantBusinessObject(element);

let extensionElements = businessObject.get('extensionElements');

// (1) ensure extension elements
if (!extensionElements) {
extensionElements = createElement(
'bpmn:ExtensionElements',
{ values: [] },
businessObject,
bpmnFactory
);

commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
moddleElement: businessObject,
properties: { extensionElements }
}
});
}

// (2) ensure zeebe:ExecutionListeners
let executionListeners = getExecutionListenersContainer(businessObject);

if (!executionListeners) {
const parent = extensionElements;

executionListeners = createElement('zeebe:ExecutionListeners', {
listeners: []
}, parent, bpmnFactory);

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

// (3) create zeebe:ExecutionListener
const executionListener = createElement('zeebe:ExecutionListener', DEFAULT_LISTENER_PROPS, executionListeners, bpmnFactory);

// (4) add executionListener to list
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
moddleElement: executionListeners,
properties: {
listeners: [ ...executionListeners.get('listeners'), executionListener ]
}
}
});

// (5) commit all updates
commandStack.execute('properties-panel.multi-command-executor', commands);
};
}


// helper //////////////////

export function getRelevantBusinessObject(element) {
let businessObject = getBusinessObject(element);

if (is(element, 'bpmn:Participant')) {
return businessObject.get('processRef');
}

return businessObject;
}

export function getExecutionListenersContainer(element) {
const executionListeners = getExtensionElementsList(element, 'zeebe:ExecutionListeners');

return executionListeners && executionListeners[0];
}

export function getListenersList(element) {
const executionListeners = getExecutionListenersContainer(element);

return executionListeners && executionListeners.get('listeners');
}
1 change: 1 addition & 0 deletions src/provider/zeebe/properties/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { CalledDecisionProps } from './CalledDecisionProps';
export { ConditionProps } from './ConditionProps';
export { ErrorProps } from './ErrorProps';
export { EscalationProps } from './EscalationProps';
export { ExecutionListenersProps } from './ExecutionListenersProps';
export { FormProps } from './FormProps';
export { HeaderProps } from './HeaderProps';
export { InputPropagationProps } from './InputPropagationProps';
Expand Down

0 comments on commit 623deab

Please sign in to comment.