diff --git a/src/contextProvider/zeebe/TooltipProvider.js b/src/contextProvider/zeebe/TooltipProvider.js index 4bcee49ea..843dc7320 100644 --- a/src/contextProvider/zeebe/TooltipProvider.js +++ b/src/contextProvider/zeebe/TooltipProvider.js @@ -278,6 +278,19 @@ const TooltipProvider = {

); + }, + 'bindingType': (element) => { + + const translate = useService('translate'); + + return ( +
+

+ { translate('The type of the binding. The default is ') }latest{ translate('.') } +

+ { translate('Learn more.') } +
+ ); } }; diff --git a/src/provider/HOCs/index.js b/src/provider/HOCs/index.js index 11b425820..6cc2f0eb3 100644 --- a/src/provider/HOCs/index.js +++ b/src/provider/HOCs/index.js @@ -1,2 +1,3 @@ +export { withProps } from './withProps'; export { withVariableContext } from './withVariableContext'; export { withTooltipContainer } from './withTooltipContainer'; \ No newline at end of file diff --git a/src/provider/HOCs/withProps.js b/src/provider/HOCs/withProps.js new file mode 100644 index 000000000..a54301acb --- /dev/null +++ b/src/provider/HOCs/withProps.js @@ -0,0 +1,5 @@ +export function withProps(Component, otherProps) { + return props => { + return ; + }; +} \ No newline at end of file diff --git a/src/provider/zeebe/properties/CalledDecisionProps.js b/src/provider/zeebe/properties/CalledDecisionProps.js index da7bdd355..83609ed7f 100644 --- a/src/provider/zeebe/properties/CalledDecisionProps.js +++ b/src/provider/zeebe/properties/CalledDecisionProps.js @@ -4,10 +4,14 @@ import { } from 'bpmn-js/lib/util/ModelUtil'; import { - TextFieldEntry, isTextFieldEntryEdited, - isFeelEntryEdited + isFeelEntryEdited, + isSelectEntryEdited, + isTextFieldEntryEdited, + TextFieldEntry } from '@bpmn-io/properties-panel'; +import Binding from './shared/Binding'; + import { getExtensionElementsList } from '../../../utils/ExtensionElementsUtil'; @@ -20,6 +24,7 @@ import { useService } from '../../../hooks'; import { FeelEntryWithVariableContext } from '../../../entries/FeelEntryWithContext'; +import { withProps } from '../../HOCs/withProps.js'; export function CalledDecisionProps(props) { @@ -37,6 +42,11 @@ export function CalledDecisionProps(props) { component: DecisionID, isEdited: isFeelEntryEdited }, + { + id: 'bindingType', + component: withProps(Binding, { type: 'zeebe:CalledDecision' }), + isEdited: isSelectEntryEdited + }, { id: 'resultVariable', component: ResultVariable, diff --git a/src/provider/zeebe/properties/FormProps.js b/src/provider/zeebe/properties/FormProps.js index 531a02b27..d863317d8 100644 --- a/src/provider/zeebe/properties/FormProps.js +++ b/src/provider/zeebe/properties/FormProps.js @@ -13,10 +13,13 @@ import { TextFieldEntry, TextAreaEntry, isFeelEntryEdited, + isSelectEntryEdited, isTextFieldEntryEdited, isTextAreaEntryEdited } from '@bpmn-io/properties-panel'; +import Binding from './shared/Binding'; + import { FeelEntryWithVariableContext } from '../../../entries/FeelEntryWithContext'; import { createElement } from '../../../utils/ElementUtil'; @@ -34,6 +37,8 @@ import { userTaskFormIdToFormKey } from '../utils/FormUtil'; +import { withProps } from '../../HOCs'; + const NONE_VALUE = 'none'; @@ -78,6 +83,14 @@ export function FormProps(props) { }); } + if (formType === FORM_TYPES.CAMUNDA_FORM_LINKED) { + entries.push({ + id: 'bindingType', + component: withProps(Binding, { type: 'zeebe:FormDefinition' }), + isEdited: isSelectEntryEdited + }); + } + return entries; } diff --git a/src/provider/zeebe/properties/TargetProps.js b/src/provider/zeebe/properties/TargetProps.js index 2cbca801e..98dce5e86 100644 --- a/src/provider/zeebe/properties/TargetProps.js +++ b/src/provider/zeebe/properties/TargetProps.js @@ -4,9 +4,12 @@ import { } from 'bpmn-js/lib/util/ModelUtil'; import { - isFeelEntryEdited + isFeelEntryEdited, + isSelectEntryEdited } from '@bpmn-io/properties-panel'; +import Binding from './shared/Binding'; + import { createElement } from '../../../utils/ElementUtil'; @@ -20,6 +23,8 @@ import { useService } from '../../../hooks'; import { FeelEntryWithVariableContext } from '../../../entries/FeelEntryWithContext'; +import { withProps } from '../../HOCs/withProps.js'; + export function TargetProps(props) { const { @@ -35,6 +40,11 @@ export function TargetProps(props) { id: 'targetProcessId', component: TargetProcessId, isEdited: isFeelEntryEdited + }, + { + id: 'bindingType', + component: withProps(Binding, { type: 'zeebe:CalledElement' }), + isEdited: isSelectEntryEdited } ]; } @@ -128,4 +138,4 @@ function TargetProcessId(props) { setValue, debounce }); -} +} \ No newline at end of file diff --git a/src/provider/zeebe/properties/shared/Binding.js b/src/provider/zeebe/properties/shared/Binding.js new file mode 100644 index 000000000..7a563d889 --- /dev/null +++ b/src/provider/zeebe/properties/shared/Binding.js @@ -0,0 +1,112 @@ +import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil'; + +import { SelectEntry } from '@bpmn-io/properties-panel'; + +import { createElement } from '../../../../utils/ElementUtil'; + +import { useService } from '../../../../hooks'; + +import { getExtensionElementsList } from '../../../../utils/ExtensionElementsUtil'; + +export default function Binding(props) { + const { + element, + type + } = props; + + const bpmnFactory = useService('bpmnFactory'), + commandStack = useService('commandStack'), + translate = useService('translate'); + + const getValue = () => { + const businessObject = getBusinessObject(element); + + const extensionElement = getExtensionElementsList(businessObject, type)[ 0 ]; + + if (!extensionElement) { + return 'latest'; + } + + return extensionElement.get('bindingType'); + }; + + const setValue = value => { + const commands = []; + + const businessObject = getBusinessObject(element); + + // (1) ensure extension elements + let extensionElements = businessObject.get('extensionElements'); + + if (!extensionElements) { + extensionElements = createElement( + 'bpmn:ExtensionElements', + { values: [] }, + businessObject, + bpmnFactory + ); + + commands.push({ + cmd: 'element.updateModdleProperties', + context: { + element, + moddleElement: businessObject, + properties: { extensionElements } + } + }); + } + + // (2) ensure extension element + let extensionElement = getExtensionElementsList(businessObject, type)[ 0 ]; + + if (!extensionElement) { + extensionElement = createElement( + type, + {}, + extensionElements, + bpmnFactory + ); + + commands.push({ + cmd: 'element.updateModdleProperties', + context: { + element, + moddleElement: extensionElements, + properties: { + values: [ ...extensionElements.get('values'), extensionElement ] + } + } + }); + + } + + // (3) Update bindingType attribute + commands.push({ + cmd: 'element.updateModdleProperties', + context: { + element, + moddleElement: extensionElement, + properties: { + bindingType: value + } + } + }); + + // (4) Execute the commands + commandStack.execute('properties-panel.multi-command-executor', commands); + }; + + const getOptions = () => ([ + { value: 'latest', label: translate('latest') }, + { value: 'deployment', label: translate('deployment') } + ]); + + return ; +} \ No newline at end of file diff --git a/src/provider/zeebe/utils/CalledElementUtil.js b/src/provider/zeebe/utils/CalledElementUtil.js index 5e1990523..90be6254c 100644 --- a/src/provider/zeebe/utils/CalledElementUtil.js +++ b/src/provider/zeebe/utils/CalledElementUtil.js @@ -25,6 +25,12 @@ export function getProcessId(element) { return calledElement ? calledElement.get('processId') : ''; } +export function getBindingType(element) { + const calledElement = getCalledElement(element); + + return calledElement ? calledElement.get('bindingType') : ''; +} + export function getCalledElement(element) { const calledElements = getCalledElements(element); return calledElements[0]; diff --git a/test/spec/provider/zeebe/CalledDecisionProps.spec.js b/test/spec/provider/zeebe/CalledDecisionProps.spec.js index ecbd9d603..f92724327 100644 --- a/test/spec/provider/zeebe/CalledDecisionProps.spec.js +++ b/test/spec/provider/zeebe/CalledDecisionProps.spec.js @@ -153,6 +153,98 @@ describe('provider/zeebe - CalledDecisionProps', function() { }); + describe('#calledDecision.bindingType', function() { + + it('should display', inject(async function(elementRegistry, selection) { + + // given + const businessRuleTask = elementRegistry.get('BusinessRuleTask_1'); + + // assume + const bindingType = getBindingType(businessRuleTask); + + expect(bindingType).to.equal('latest'); + + // when + await act(() => { + selection.select(businessRuleTask); + }); + + const bindingTypeSelect = domQuery('select[name=bindingType]', container); + + // then + expect(bindingTypeSelect).to.exist; + + expect(bindingTypeSelect.value).to.equal('latest'); + })); + + + it('should not display', inject(async function(elementRegistry, selection) { + + // given + const task = elementRegistry.get('Task_1'); + + // when + await act(() => { + selection.select(task); + }); + + const bindingTypeSelect = domQuery('select[name=bindingType]', container); + + // then + expect(bindingTypeSelect).not.to.exist; + })); + + + it('should update', inject(async function(elementRegistry, selection) { + + // given + const businessRuleTask = elementRegistry.get('BusinessRuleTask_1'); + + await act(() => { + selection.select(businessRuleTask); + }); + + const bindingTypeSelect = domQuery('select[name=bindingType]', container); + + // when + changeInput(bindingTypeSelect, 'deployment'); + + // then + const bindingType = getBindingType(businessRuleTask); + + expect(bindingType).to.equal('deployment'); + })); + + + it('should update on external change', + inject(async function(elementRegistry, selection, commandStack) { + + // given + const businessRuleTask = elementRegistry.get('BusinessRuleTask_1'), + originalValue = getBindingType(businessRuleTask); + + await act(() => { + selection.select(businessRuleTask); + }); + + const bindingTypeSelect = domQuery('select[name=bindingType]', container); + + changeInput(bindingTypeSelect, 'deployment'); + + // when + await act(() => { + commandStack.undo(); + }); + + // then + expect(getBindingType(businessRuleTask)).to.eql(originalValue); + }) + ); + + }); + + describe('#calledDecision.resultVariable', function() { it('should display', inject(async function(elementRegistry, selection) { @@ -255,6 +347,12 @@ export function getDecisionId(element) { return calledDecision ? calledDecision.get('decisionId') : ''; } +export function getBindingType(element) { + const calledDecision = getCalledDecision(element); + + return calledDecision ? calledDecision.get('bindingType') : ''; +} + export function getResultVariable(element) { const calledDecision = getCalledDecision(element); diff --git a/test/spec/provider/zeebe/Forms.spec.js b/test/spec/provider/zeebe/Forms.spec.js index 207f40a70..f1baf1b54 100644 --- a/test/spec/provider/zeebe/Forms.spec.js +++ b/test/spec/provider/zeebe/Forms.spec.js @@ -476,6 +476,70 @@ describe('provider/zeebe - Forms', function() { expectFormId(userTask, initialFormId); })); + + describe('binding type', function() { + + it('should display', inject(async function(elementRegistry, selection) { + + // given + const userTask = elementRegistry.get('CAMUNDA_FORM_LINKED'); + + // when + await act(() => { + selection.select(userTask); + }); + + const bindingTypeSelect = getBindingTypeSelect(container); + + // then + expect(bindingTypeSelect).to.exist; + })); + + + it('should update', inject(async function(elementRegistry, selection) { + + // given + const userTask = elementRegistry.get('CAMUNDA_FORM_LINKED'); + + await act(() => { + selection.select(userTask); + }); + + const bindingTypeSelect = getBindingTypeSelect(container); + + // when + changeInput(bindingTypeSelect, 'deployment'); + + // then + expectBindingType(userTask, 'deployment'); + })); + + + it('should update on external change', inject(async function(commandStack, elementRegistry, selection) { + + // given + const userTask = elementRegistry.get('CAMUNDA_FORM_LINKED'); + + await act(() => { + selection.select(userTask); + }); + + const bindingTypeSelect = getBindingTypeSelect(container); + const initialBindingType = bindingTypeSelect.value; + + changeInput(bindingTypeSelect, 'deployment'); + expectBindingType(userTask, 'deployment'); + + // when + await act(() => { + commandStack.undo(); + }); + + expectBindingType(userTask, initialBindingType); + })); + + }); + }); @@ -675,6 +739,7 @@ describe('provider/zeebe - Forms', function() { expectExternalReference(userTask, initialFormKey); })); }); + }); @@ -700,6 +765,10 @@ function getFormTypeSelect(container) { return domQuery('select[name=formType]', container); } +function getBindingTypeSelect(container) { + return domQuery('select[name=bindingType]', container); +} + function expectFormId(element, expected) { const formDefinition = getFormDefinition(element); @@ -707,6 +776,13 @@ function expectFormId(element, expected) { expect(formDefinition.get('formId')).to.eql(expected); } +function expectBindingType(element, expected) { + const formDefinition = getFormDefinition(element); + + expect(formDefinition).to.exist; + expect(formDefinition.get('bindingType')).to.eql(expected); +} + function expectFormKey(element, expected) { const formDefinition = getFormDefinition(element); diff --git a/test/spec/provider/zeebe/TargetProps.spec.js b/test/spec/provider/zeebe/TargetProps.spec.js index ac39d2a71..78156e561 100644 --- a/test/spec/provider/zeebe/TargetProps.spec.js +++ b/test/spec/provider/zeebe/TargetProps.spec.js @@ -23,6 +23,7 @@ import { } from 'src/utils/ExtensionElementsUtil.js'; import { + getBindingType, getCalledElement, getProcessId } from 'src/provider/zeebe/utils/CalledElementUtil.js'; @@ -224,4 +225,95 @@ describe('provider/zeebe - TargetProps', function() { }); + + describe('bpmn:CallActivity#calledElement.bindingType', function() { + + it('should display', inject(async function(elementRegistry, selection) { + + // given + const callActivity = elementRegistry.get('CallActivity_1'); + + // assume + const bindingType = getBindingType(callActivity); + + expect(bindingType).to.equal('latest'); + + // when + await act(() => { + selection.select(callActivity); + }); + + const bindingTypeSelect = domQuery('select[name=bindingType]', container); + + // then + expect(bindingTypeSelect).to.exist; + + expect(bindingTypeSelect.value).to.equal('latest'); + })); + + + it('should not display', inject(async function(elementRegistry, selection) { + + // given + const task = elementRegistry.get('Task_1'); + + // when + await act(() => { + selection.select(task); + }); + + const bindingTypeSelect = domQuery('select[name=bindingType]', container); + + // then + expect(bindingTypeSelect).not.to.exist; + })); + + + it('should update', inject(async function(elementRegistry, selection) { + + // given + const callActivity = elementRegistry.get('CallActivity_1'); + + await act(() => { + selection.select(callActivity); + }); + + const bindingTypeSelect = domQuery('select[name=bindingType]', container); + + // when + changeInput(bindingTypeSelect, 'deployment'); + + // then + const bindingType = getBindingType(callActivity); + + expect(bindingType).to.equal('deployment'); + })); + + + it('should update on external change', + inject(async function(elementRegistry, selection, commandStack) { + + // given + const callActivity = elementRegistry.get('CallActivity_1'), + originalValue = getBindingType(callActivity); + + await act(() => { + selection.select(callActivity); + }); + + const bindingTypeSelect = domQuery('select[name=bindingType]', container); + changeInput(bindingTypeSelect, 'deployment'); + + // when + await act(() => { + commandStack.undo(); + }); + + // then + expect(getBindingType(callActivity)).to.eql(originalValue); + }) + ); + + }); + });