Skip to content

Commit

Permalink
feat: add task listeners for Zeebe
Browse files Browse the repository at this point in the history
  • Loading branch information
misiekhardcore committed Nov 7, 2024
1 parent 291b4c5 commit 5357aae
Show file tree
Hide file tree
Showing 6 changed files with 362 additions and 65 deletions.
18 changes: 18 additions & 0 deletions src/provider/zeebe/ZeebePropertiesProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
SignalProps,
TargetProps,
TaskDefinitionProps,
TaskListenersProps,
TaskScheduleProps,
TimerProps,
UserTaskImplementationProps,
Expand Down Expand Up @@ -56,6 +57,7 @@ const ZEEBE_GROUPS = [
OutputPropagationGroup,
OutputGroup,
HeaderGroup,
TaskListenersGroup,
ExecutionListenersGroup,
ExtensionPropertiesGroup
];
Expand Down Expand Up @@ -323,6 +325,22 @@ function ExecutionListenersGroup(element, injector) {
return null;
}

function TaskListenersGroup(element, injector) {
const translate = injector.get('translate');
const group = {
label: translate('Task listeners'),
id: 'Zeebe__TaskListeners',
component: ListGroup,
...TaskListenersProps({ element, injector })
};

if (group.items) {
return group;
}

return null;
}

function ExtensionPropertiesGroup(element, injector) {
const translate = injector.get('translate');
const group = {
Expand Down
66 changes: 1 addition & 65 deletions src/provider/zeebe/properties/ExecutionListener.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
getErrorEventDefinition
} from '../../../utils/EventDefinitionUtil';

import { FeelEntryWithVariableContext } from '../../../entries/FeelEntryWithContext';
import { ListenerType, Retries } from './shared/Listener';


export const EVENT_TO_LABEL = {
Expand Down Expand Up @@ -95,70 +95,6 @@ function EventType(props) {
});
}

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,
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,
id: idPrefix + '-retries',
label: translate('Retries'),
getValue,
setValue,
debounce,
feel: 'optional'
});
}

export function getEventTypes(element) {
if (isAny(element, [ 'bpmn:BoundaryEvent', 'bpmn:StartEvent' ])) {
return [ 'end' ];
Expand Down
84 changes: 84 additions & 0 deletions src/provider/zeebe/properties/TaskListener.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { SelectEntry } from '@bpmn-io/properties-panel';

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

import { ListenerType, Retries } from './shared/Listener';

export const EVENT_TYPE = [ 'complete', 'assignment' ];

export const EVENT_TO_LABEL = {
complete: 'Complete',
assignment: 'Assignment'
};

export function TaskListenerEntries(props) {

const {
idPrefix,
listener
} = props;

return [
{
id: idPrefix + '-eventType',
component: EventType,
idPrefix,
listener,
eventTypes: EVENT_TYPE
},
{
id: idPrefix + '-listenerType',
component: ListenerType,
idPrefix,
listener
},
{
id: idPrefix + '-retries',
component: Retries,
idPrefix,
listener
}
];
}

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

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

const getOptions = () => {
return eventTypes.map(eventType => ({
value: eventType,
label: translate(EVENT_TO_LABEL[eventType])
}));
};

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

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

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


191 changes: 191 additions & 0 deletions src/provider/zeebe/properties/TaskListenersProps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import {
getBusinessObject,
is,
isAny
} from 'bpmn-js/lib/util/ModelUtil';

import { without } from 'min-dash';

import { TaskListenerEntries, EVENT_TYPE, EVENT_TO_LABEL } from './TaskListener';

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

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

import { isZeebeUserTask } from '../utils/FormUtil';


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

// not allowed in empty pools
if (!businessObject) {
return;
}

if (!isZeebeUserTask(element)) {
return;
}

const moddle = injector.get('moddle');
if (!canHaveTaskListeners(businessObject, moddle)) {
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 + '-TaskListener-' + index;
const type = listener.get('type') || '<no type>';

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

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

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

const businessObject = getRelevantBusinessObject(element);
const container = getTaskListenersContainer(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:TaskListeners
let TaskListeners = getTaskListenersContainer(businessObject);

if (!TaskListeners) {
const parent = extensionElements;

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

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

// (3) create zeebe:TaskListener
const TaskListener = createElement(
'zeebe:TaskListener', EVENT_TYPE[0], TaskListeners, bpmnFactory
);

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

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


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

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

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

return businessObject;
}

function getTaskListenersContainer(element) {
const TaskListeners = getExtensionElementsList(element, 'zeebe:TaskListeners');

return TaskListeners && TaskListeners[0];
}

function getListenersList(element) {
const TaskListeners = getTaskListenersContainer(element);

return TaskListeners && TaskListeners.get('listeners');
}

function canHaveTaskListeners(bo, moddle) {
const TaskListenersDescriptor = moddle.getTypeDescriptor('zeebe:TaskListeners');

if (!isAny(bo, TaskListenersDescriptor.meta.allowedIn)) {
return false;
}

return true;
}
Loading

0 comments on commit 5357aae

Please sign in to comment.