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 ( +
+ {translate('Define a collection of elements which will be activated when the ad-hoc process is evaluated. ')} + + { translate('Learn more.') } + +
+ ); + }, }; 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