diff --git a/src/contextProvider/zeebe/TooltipProvider.js b/src/contextProvider/zeebe/TooltipProvider.js
index f2eb24f3..963597b5 100644
--- a/src/contextProvider/zeebe/TooltipProvider.js
+++ b/src/contextProvider/zeebe/TooltipProvider.js
@@ -348,7 +348,19 @@ const TooltipProvider = {
{ translate('If unset, the default value is 50.') }
);
- }
+ },
+ 'group-activeElements': (element) => {
+ const translate = useService('translate');
+
+ return (
+
+ );
+ },
};
export default TooltipProvider;
diff --git a/src/provider/zeebe/ZeebePropertiesProvider.js b/src/provider/zeebe/ZeebePropertiesProvider.js
index f559bba6..2b1cc1b0 100644
--- a/src/provider/zeebe/ZeebePropertiesProvider.js
+++ b/src/provider/zeebe/ZeebePropertiesProvider.js
@@ -3,6 +3,7 @@ import { Group, ListGroup } from '@bpmn-io/properties-panel';
import { findIndex } from 'min-dash';
import {
+ ActiveElementsProps,
AssignmentDefinitionProps,
BusinessRuleImplementationProps,
CalledDecisionProps,
@@ -49,6 +50,7 @@ const ZEEBE_GROUPS = [
UserTaskImplementationGroup,
TaskDefinitionGroup,
AssignmentDefinitionGroup,
+ ActiveElementsGroup,
FormGroup,
ConditionGroup,
TargetGroup,
@@ -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 = {
diff --git a/src/provider/zeebe/properties/ActiveElementsProps.js b/src/provider/zeebe/properties/ActiveElementsProps.js
new file mode 100644
index 00000000..df5bb724
--- /dev/null
+++ b/src/provider/zeebe/properties/ActiveElementsProps.js
@@ -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];
+}
+
diff --git a/src/provider/zeebe/properties/index.js b/src/provider/zeebe/properties/index.js
index 200cbff7..3597a39f 100644
--- a/src/provider/zeebe/properties/index.js
+++ b/src/provider/zeebe/properties/index.js
@@ -1,3 +1,4 @@
+export { ActiveElementsProps } from './ActiveElementsProps';
export { AssignmentDefinitionProps } from './AssignmentDefinitionProps';
export { BusinessRuleImplementationProps } from './BusinessRuleImplementationProps';
export { CalledDecisionProps } from './CalledDecisionProps';
diff --git a/test/spec/provider/zeebe/ActiveElementsCollectionProps.bpmn b/test/spec/provider/zeebe/ActiveElementsCollectionProps.bpmn
new file mode 100644
index 00000000..f437b137
--- /dev/null
+++ b/test/spec/provider/zeebe/ActiveElementsCollectionProps.bpmn
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/spec/provider/zeebe/ActiveElementsCollectionProps.spec.js b/test/spec/provider/zeebe/ActiveElementsCollectionProps.spec.js
new file mode 100644
index 00000000..cb3ae2e7
--- /dev/null
+++ b/test/spec/provider/zeebe/ActiveElementsCollectionProps.spec.js
@@ -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');
+}
\ No newline at end of file