Skip to content

Commit

Permalink
feat: support ad-hoc subprocesses
Browse files Browse the repository at this point in the history
  • Loading branch information
jarekdanielak committed Jan 22, 2025
1 parent 0c3ac41 commit 9df52bd
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 1 deletion.
14 changes: 13 additions & 1 deletion src/contextProvider/zeebe/TooltipProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,19 @@ const TooltipProvider = {
<p>{ translate('If unset, the default value is 50.') }</p>
</div>
);
}
},
'group-activeElements': (element) => {
const translate = useService('translate');

return (
<div>
{translate('Define a collection of elements which will be activated when the ad-hoc subprocess is evaluated. ')}
<a href="https://docs.camunda.io/docs/components/modeler/bpmn/ad-hoc/#activate-an-element" target="_blank" rel="noopener noreferrer" title={ translate('Ad-hoc subprocess documentation') }>
{ translate('Learn more.') }
</a>
</div>
);
},
};

export default TooltipProvider;
16 changes: 16 additions & 0 deletions src/provider/zeebe/ZeebePropertiesProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Group, ListGroup } from '@bpmn-io/properties-panel';
import { findIndex } from 'min-dash';

import {
ActiveElementsProps,
AssignmentDefinitionProps,
BusinessRuleImplementationProps,
CalledDecisionProps,
Expand Down Expand Up @@ -49,6 +50,7 @@ const ZEEBE_GROUPS = [
UserTaskImplementationGroup,
TaskDefinitionGroup,
AssignmentDefinitionGroup,
ActiveElementsGroup,
FormGroup,
ConditionGroup,
TargetGroup,
Expand Down Expand Up @@ -309,6 +311,20 @@ function AssignmentDefinitionGroup(element, injector) {
return group.entries.length ? group : null;
}

function ActiveElementsGroup(element, injector) {
const translate = injector.get('translate');
const group = {
id: 'activeElements',
label: translate('Active elements'),
entries: [
...ActiveElementsProps({ element })
],
component: Group
};

return group.entries.length ? group : null;
}

function ExecutionListenersGroup(element, injector) {
const translate = injector.get('translate');
const group = {
Expand Down
110 changes: 110 additions & 0 deletions src/provider/zeebe/properties/ActiveElementsProps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {
is,
getBusinessObject
} from 'bpmn-js/lib/util/ModelUtil';

import { isFeelEntryEdited } from '@bpmn-io/properties-panel';

import { useService } from 'src/hooks';

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

import {
getExtensionElementsList,
addExtensionElements
} from 'src/utils/ExtensionElementsUtil';

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

export function ActiveElementsProps(props) {
const {
element
} = props;

if (!is(element, 'bpmn:AdHocSubProcess')) {
return [];
}

const entries = [
{
id: 'activeElementsCollection',
component: ActiveElementsCollection,
isEdited: isFeelEntryEdited
}
];

return entries;
}

function ActiveElementsCollection(props) {
const {
element
} = props;

const commandStack = useService('commandStack');
const bpmnFactory = useService('bpmnFactory');
const translate = useService('translate');
const debounce = useService('debounceInput');

const getValue = () => {
return getProperty(element);
};

const setValue = (value) => {
return setProperty(element, value, commandStack, bpmnFactory);
};

return FeelEntryWithVariableContext({
element,
id: 'activeElements-activeElementsCollection',
label: translate('Active elements collection'),
feel: 'required',
getValue,
setValue,
debounce
});
}

function getProperty(element) {
const extensionElement = getExtensionElement(element);
return extensionElement && extensionElement.get('activeElementsCollection');
}

function setProperty(element, value, commandStack, bpmnFactory) {

const extensionElement = getExtensionElement(element);

if (!extensionElement) {

// (1) create extension element
const adHoc = createElement(
'zeebe:AdHoc',
{
activeElementsCollection: value
},
undefined,
bpmnFactory
);

const businessObject = getBusinessObject(element);
addExtensionElements(element, businessObject, adHoc, bpmnFactory, commandStack);

} else {

// (2) update extension element's property
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElement,
properties: {
activeElementsCollection: value
}
});
}
}

function getExtensionElement(element) {
const businessObject = getBusinessObject(element);
const extensions = getExtensionElementsList(businessObject, 'zeebe:AdHoc');
return extensions[0];
}

1 change: 1 addition & 0 deletions src/provider/zeebe/properties/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { ActiveElementsProps } from './ActiveElementsProps';
export { AssignmentDefinitionProps } from './AssignmentDefinitionProps';
export { BusinessRuleImplementationProps } from './BusinessRuleImplementationProps';
export { CalledDecisionProps } from './CalledDecisionProps';
Expand Down
38 changes: 38 additions & 0 deletions test/spec/provider/zeebe/ActiveElementsCollectionProps.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1rk4hoy" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.30.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.6.0">
<bpmn:process id="Process_1sk4u1k" isExecutable="true">
<bpmn:adHocSubProcess id="Subprocess_1">
<bpmn:task id="Activity_167ttdt" />
<bpmn:task id="Activity_0cxw94m" />
</bpmn:adHocSubProcess>
<bpmn:adHocSubProcess id="Subprocess_2">
<bpmn:extensionElements>
<zeebe:adHoc activeElementsCollection="=activeElements" />
</bpmn:extensionElements>
<bpmn:task id="Activity_1qb8yya" />
<bpmn:task id="Activity_1i2ek76" />
</bpmn:adHocSubProcess>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1sk4u1k">
<bpmndi:BPMNShape id="Activity_1hitopu_di" bpmnElement="Subprocess_1" isExpanded="true">
<dc:Bounds x="160" y="70" width="350" height="200" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_167ttdt_di" bpmnElement="Activity_167ttdt">
<dc:Bounds x="210" y="130" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0cxw94m_di" bpmnElement="Activity_0cxw94m">
<dc:Bounds x="360" y="130" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_06ihycz" bpmnElement="Subprocess_2" isExpanded="true">
<dc:Bounds x="540" y="70" width="350" height="200" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_0c1eh57" bpmnElement="Activity_1qb8yya">
<dc:Bounds x="590" y="130" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_1g9skdj" bpmnElement="Activity_1i2ek76">
<dc:Bounds x="740" y="130" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
176 changes: 176 additions & 0 deletions test/spec/provider/zeebe/ActiveElementsCollectionProps.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import TestContainer from 'mocha-test-container-support';

import {
act
} from '@testing-library/preact';

import {
bootstrapPropertiesPanel,
setEditorValue,
inject
} from 'test/TestHelper';

import {
query as domQuery
} from 'min-dom';

import {
getBusinessObject
} from 'bpmn-js/lib/util/ModelUtil';

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

import BpmnPropertiesPanel from 'src/render';
import CoreModule from 'bpmn-js/lib/core';
import ModelingModule from 'bpmn-js/lib/features/modeling';
import SelectionModule from 'diagram-js/lib/features/selection';
import ZeebePropertiesProvider from 'src/provider/zeebe';

import zeebeModdleExtensions from 'zeebe-bpmn-moddle/resources/zeebe';

import diagramXML from './ActiveElementsCollectionProps.bpmn';


describe('provider/zeebe - ActiveElementsCollection', function() {

const testModules = [
BpmnPropertiesPanel,
CoreModule,
ModelingModule,
SelectionModule,
ZeebePropertiesProvider
];

const moddleExtensions = {
zeebe: zeebeModdleExtensions
};

let container;

beforeEach(function() {
container = TestContainer.get(this);
});

beforeEach(bootstrapPropertiesPanel(diagramXML, {
modules: testModules,
moddleExtensions,
debounceInput: false
}));


describe('bpmn:AdHocSubProcess', function() {

describe('#activeElements', function() {

it('should display', inject(async function(elementRegistry, selection) {

// given
const subprocess = elementRegistry.get('Subprocess_1');

// when
await act(() => {
selection.select(subprocess);
});

// then
const group = getGroup(container, 'activeElements');
const input = getInput(container);

expect(group).to.exist;
expect(input).to.exist;
}));


it('should create extension element', inject(async function(elementRegistry, selection) {

// given
const subprocess = elementRegistry.get('Subprocess_1');

await act(() => {
selection.select(subprocess);
});

// assume
expect(getAdHocExtensionElements(subprocess)).to.be.empty;

// when
const input = getInput(container);
await setEditorValue(input, 'value');

// then
const extensionElements = getAdHocExtensionElements(subprocess);
expect(extensionElements).to.have.length(1);
expect(extensionElements[0].get('activeElementsCollection')).to.eql('=value');
}));


it('should update existing extension element', inject(async function(elementRegistry, selection) {

// given
const subprocess = elementRegistry.get('Subprocess_2');

await act(() => {
selection.select(subprocess);
});

// assume
expect(getAdHocExtensionElements(subprocess)).to.have.length(1);

// when
const input = getInput(container);
await setEditorValue(input, 'newValue');

// then
const extensionElements = getAdHocExtensionElements(subprocess);
expect(extensionElements).to.have.length(1);
expect(extensionElements[0].get('activeElementsCollection')).to.eql('=newValue');
}));


it('should update on external change', inject(async function(elementRegistry, selection, commandStack) {

// given
const subprocess = elementRegistry.get('Subprocess_2');

await act(() => {
selection.select(subprocess);
});

// assume
const initialValue = getAdHocExtensionElements(subprocess)[0].get('activeElementsCollection');

// when
const input = getInput(container);
await setEditorValue(input, 'newValue');

await act(() => {
commandStack.undo();
});

// then
const extensionElements = getAdHocExtensionElements(subprocess);
expect(extensionElements).to.have.length(1);
expect(extensionElements[0].get('activeElementsCollection')).to.eql(initialValue);
}));
});

});

});

// helpers

function getGroup(container, id) {
return domQuery(`[data-group-id="group-${id}"`, container);
}

function getInput(container) {
return domQuery('[name=activeElements-activeElementsCollection] [role="textbox"]', container);
}

function getAdHocExtensionElements(element) {
const bo = getBusinessObject(element);
return getExtensionElementsList(bo, 'zeebe:AdHoc');
}

0 comments on commit 9df52bd

Please sign in to comment.