diff --git a/src/contextProvider/zeebe/TooltipProvider.js b/src/contextProvider/zeebe/TooltipProvider.js index f77c9403e..092498ff0 100644 --- a/src/contextProvider/zeebe/TooltipProvider.js +++ b/src/contextProvider/zeebe/TooltipProvider.js @@ -297,16 +297,41 @@ const TooltipProvider = { ); }, 'versionTag': (element) => { - const translate = useService('translate'); - return ( -
-

- { translate('Specifying a version tag will allow you to reference this process in another process.') } -

-
- ); + if (is(element, 'bpmn:Process')) { + return ( +
+

+ { translate('Version tag by which this process can be referenced.') } +

+
+ ); + } else if (is(element, 'bpmn:CallActivity')) { + return ( +
+

+ { translate('Version tag by which the called process will be referenced.') } +

+
+ ); + } else if (is(element, 'bpmn:BusinessRuleTask')) { + return ( +
+

+ { translate('Version tag by which the called decision will be referenced.') } +

+
+ ); + } else if (is(element, 'bpmn:UserTask')) { + return ( +
+

+ { translate('Version tag by which the linked form will be referenced.') } +

+
+ ); + } }, 'priorityDefinitionPriority': (element) => { diff --git a/src/provider/zeebe/properties/CalledDecisionProps.js b/src/provider/zeebe/properties/CalledDecisionProps.js index 83609ed7f..252365649 100644 --- a/src/provider/zeebe/properties/CalledDecisionProps.js +++ b/src/provider/zeebe/properties/CalledDecisionProps.js @@ -10,7 +10,8 @@ import { TextFieldEntry } from '@bpmn-io/properties-panel'; -import Binding from './shared/Binding'; +import Binding, { getBindingType } from './shared/Binding'; +import VersionTag from './shared/VersionTag.js'; import { getExtensionElementsList @@ -36,7 +37,7 @@ export function CalledDecisionProps(props) { return []; } - return [ + const entries = [ { id: 'decisionId', component: DecisionID, @@ -46,13 +47,24 @@ export function CalledDecisionProps(props) { id: 'bindingType', component: withProps(Binding, { type: 'zeebe:CalledDecision' }), isEdited: isSelectEntryEdited - }, - { - id: 'resultVariable', - component: ResultVariable, - isEdited: isTextFieldEntryEdited } ]; + + if (getBindingType(element, 'zeebe:CalledDecision') === 'versionTag') { + entries.push({ + id: 'versionTag', + component: withProps(VersionTag, { type: 'zeebe:CalledDecision' }), + isEdited: isTextFieldEntryEdited + }); + } + + entries.push({ + id: 'resultVariable', + component: ResultVariable, + isEdited: isTextFieldEntryEdited + }); + + return entries; } function DecisionID(props) { diff --git a/src/provider/zeebe/properties/FormProps.js b/src/provider/zeebe/properties/FormProps.js index d863317d8..284979f10 100644 --- a/src/provider/zeebe/properties/FormProps.js +++ b/src/provider/zeebe/properties/FormProps.js @@ -18,7 +18,8 @@ import { isTextAreaEntryEdited } from '@bpmn-io/properties-panel'; -import Binding from './shared/Binding'; +import Binding, { getBindingType } from './shared/Binding'; +import VersionTag from './shared/VersionTag'; import { FeelEntryWithVariableContext } from '../../../entries/FeelEntryWithContext'; @@ -89,6 +90,14 @@ export function FormProps(props) { component: withProps(Binding, { type: 'zeebe:FormDefinition' }), isEdited: isSelectEntryEdited }); + + if (getBindingType(element, 'zeebe:FormDefinition') === 'versionTag') { + entries.push({ + id: 'versionTag', + component: withProps(VersionTag, { type: 'zeebe:FormDefinition' }), + isEdited: isTextFieldEntryEdited + }); + } } return entries; diff --git a/src/provider/zeebe/properties/TargetProps.js b/src/provider/zeebe/properties/TargetProps.js index 98dce5e86..2ef720744 100644 --- a/src/provider/zeebe/properties/TargetProps.js +++ b/src/provider/zeebe/properties/TargetProps.js @@ -5,10 +5,12 @@ import { import { isFeelEntryEdited, - isSelectEntryEdited + isSelectEntryEdited, + isTextFieldEntryEdited } from '@bpmn-io/properties-panel'; -import Binding from './shared/Binding'; +import Binding, { getBindingType } from './shared/Binding'; +import VersionTag from './shared/VersionTag.js'; import { createElement @@ -35,7 +37,7 @@ export function TargetProps(props) { return []; } - return [ + const entries = [ { id: 'targetProcessId', component: TargetProcessId, @@ -47,6 +49,16 @@ export function TargetProps(props) { isEdited: isSelectEntryEdited } ]; + + if (getBindingType(element, 'zeebe:CalledElement') === 'versionTag') { + entries.push({ + id: 'versionTag', + component: withProps(VersionTag, { type: 'zeebe:CalledElement' }), + isEdited: isTextFieldEntryEdited + }); + } + + return entries; } function TargetProcessId(props) { diff --git a/src/provider/zeebe/properties/shared/Binding.js b/src/provider/zeebe/properties/shared/Binding.js index 7a563d889..c19235682 100644 --- a/src/provider/zeebe/properties/shared/Binding.js +++ b/src/provider/zeebe/properties/shared/Binding.js @@ -18,17 +18,7 @@ export default function Binding(props) { 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 getValue = () => getBindingType(element, type); const setValue = value => { const commands = []; @@ -98,7 +88,8 @@ export default function Binding(props) { const getOptions = () => ([ { value: 'latest', label: translate('latest') }, - { value: 'deployment', label: translate('deployment') } + { value: 'deployment', label: translate('deployment') }, + { value: 'versionTag', label: translate('version tag') } ]); return ; +} + +export function getBindingType(element, type) { + const businessObject = getBusinessObject(element); + + const extensionElement = getExtensionElementsList(businessObject, type)[ 0 ]; + + if (!extensionElement) { + return 'latest'; + } + + return extensionElement.get('bindingType'); } \ No newline at end of file diff --git a/src/provider/zeebe/properties/shared/VersionTag.js b/src/provider/zeebe/properties/shared/VersionTag.js new file mode 100644 index 000000000..502be0d5c --- /dev/null +++ b/src/provider/zeebe/properties/shared/VersionTag.js @@ -0,0 +1,110 @@ +import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil'; + +import { TextFieldEntry } from '@bpmn-io/properties-panel'; + +import { createElement } from '../../../../utils/ElementUtil'; + +import { useService } from '../../../../hooks'; + +import { getExtensionElementsList } from '../../../../utils/ExtensionElementsUtil'; + +export default function VersionTag(props) { + const { + element, + type + } = props; + + const bpmnFactory = useService('bpmnFactory'), + commandStack = useService('commandStack'), + debounce = useService('debounceInput'), + translate = useService('translate'); + + const getValue = () => getVersionTag(element, type); + + 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 versionTag attribute + commands.push({ + cmd: 'element.updateModdleProperties', + context: { + element, + moddleElement: extensionElement, + properties: { + versionTag: value + } + } + }); + + // (4) Execute the commands + commandStack.execute('properties-panel.multi-command-executor', commands); + }; + + return TextFieldEntry({ + element, + id: 'versionTag', + label: translate('Version tag'), + getValue, + setValue, + debounce + }); +} + +export function getVersionTag(element, type) { + const businessObject = getBusinessObject(element); + + const extensionElement = getExtensionElementsList(businessObject, type)[ 0 ]; + + if (!extensionElement) { + return ''; + } + + return extensionElement.get('versionTag') || ''; +} \ No newline at end of file diff --git a/test/spec/provider/zeebe/CalledDecisionProps.bpmn b/test/spec/provider/zeebe/CalledDecisionProps.bpmn index 50760104a..c83f35831 100644 --- a/test/spec/provider/zeebe/CalledDecisionProps.bpmn +++ b/test/spec/provider/zeebe/CalledDecisionProps.bpmn @@ -7,7 +7,11 @@ - + + + + + @@ -22,4 +26,4 @@ - + \ No newline at end of file diff --git a/test/spec/provider/zeebe/CalledDecisionProps.spec.js b/test/spec/provider/zeebe/CalledDecisionProps.spec.js index f92724327..a373f167c 100644 --- a/test/spec/provider/zeebe/CalledDecisionProps.spec.js +++ b/test/spec/provider/zeebe/CalledDecisionProps.spec.js @@ -245,6 +245,98 @@ describe('provider/zeebe - CalledDecisionProps', function() { }); + describe('#calledDecision.versionTag', function() { + + it('should display', inject(async function(elementRegistry, selection) { + + // given + const businessRuleTask = elementRegistry.get('BusinessRuleTask_2'); + + // assume + const versionTag = getVersionTag(businessRuleTask); + + expect(versionTag).to.equal('v1.0.0'); + + // when + await act(() => { + selection.select(businessRuleTask); + }); + + const versionTagInput = domQuery('input[name=versionTag]', container); + + // then + expect(versionTagInput).to.exist; + + expect(versionTagInput.value).to.equal('v1.0.0'); + })); + + + it('should not display', inject(async function(elementRegistry, selection) { + + // given + const task = elementRegistry.get('Task_1'); + + // when + await act(() => { + selection.select(task); + }); + + const versionTagInput = domQuery('input[name=versionTag]', container); + + // then + expect(versionTagInput).not.to.exist; + })); + + + it('should update', inject(async function(elementRegistry, selection) { + + // given + const businessRuleTask = elementRegistry.get('BusinessRuleTask_2'); + + await act(() => { + selection.select(businessRuleTask); + }); + + const versionTagInput = domQuery('input[name=versionTag]', container); + + // when + changeInput(versionTagInput, 'v2.0.0'); + + // then + const versionTag = getVersionTag(businessRuleTask); + + expect(versionTag).to.equal('v2.0.0'); + })); + + + it('should update on external change', + inject(async function(elementRegistry, selection, commandStack) { + + // given + const businessRuleTask = elementRegistry.get('BusinessRuleTask_2'), + originalValue = getVersionTag(businessRuleTask); + + await act(() => { + selection.select(businessRuleTask); + }); + + const versionTagInput = domQuery('input[name=versionTag]', container); + + changeInput(versionTagInput, 'v2.0.0'); + + // when + await act(() => { + commandStack.undo(); + }); + + // then + expect(getVersionTag(businessRuleTask)).to.eql(originalValue); + }) + ); + + }); + + describe('#calledDecision.resultVariable', function() { it('should display', inject(async function(elementRegistry, selection) { @@ -353,6 +445,12 @@ export function getBindingType(element) { return calledDecision ? calledDecision.get('bindingType') : ''; } +export function getVersionTag(element) { + const calledDecision = getCalledDecision(element); + + return calledDecision ? calledDecision.get('versionTag') : ''; +} + export function getResultVariable(element) { const calledDecision = getCalledDecision(element); diff --git a/test/spec/provider/zeebe/Forms.bpmn b/test/spec/provider/zeebe/Forms.bpmn index 87717bbec..c5d6f52b0 100644 --- a/test/spec/provider/zeebe/Forms.bpmn +++ b/test/spec/provider/zeebe/Forms.bpmn @@ -1,5 +1,5 @@ - + {} @@ -37,37 +37,45 @@ + + + + + - - - - - + - - - - - + - - + + - + - + + + + + + + + + + + + diff --git a/test/spec/provider/zeebe/Forms.spec.js b/test/spec/provider/zeebe/Forms.spec.js index f1baf1b54..4e9de85a8 100644 --- a/test/spec/provider/zeebe/Forms.spec.js +++ b/test/spec/provider/zeebe/Forms.spec.js @@ -540,6 +540,70 @@ describe('provider/zeebe - Forms', function() { }); + + describe('version tag', function() { + + it('should display', inject(async function(elementRegistry, selection) { + + // given + const userTask = elementRegistry.get('CAMUNDA_FORM_LINKED_VERSION_TAG'); + + // when + await act(() => { + selection.select(userTask); + }); + + const versionTagInput = getVersionTagInput(container); + + // then + expect(versionTagInput).to.exist; + })); + + + it('should update', inject(async function(elementRegistry, selection) { + + // given + const userTask = elementRegistry.get('CAMUNDA_FORM_LINKED_VERSION_TAG'); + + await act(() => { + selection.select(userTask); + }); + + const versionTagInput = getVersionTagInput(container); + + // when + changeInput(versionTagInput, 'v2.0.0'); + + // then + expectVersionTag(userTask, 'v2.0.0'); + })); + + + it('should update on external change', inject(async function(commandStack, elementRegistry, selection) { + + // given + const userTask = elementRegistry.get('CAMUNDA_FORM_LINKED_VERSION_TAG'); + + await act(() => { + selection.select(userTask); + }); + + const versionTagInput = getVersionTagInput(container); + const initialVersionTag = versionTagInput.value; + + changeInput(versionTagInput, 'v2.0.0'); + expectVersionTag(userTask, 'v2.0.0'); + + // when + await act(() => { + commandStack.undo(); + }); + + expectVersionTag(userTask, initialVersionTag); + })); + + }); + }); @@ -769,6 +833,10 @@ function getBindingTypeSelect(container) { return domQuery('select[name=bindingType]', container); } +function getVersionTagInput(container) { + return domQuery('input[name=versionTag]', container); +} + function expectFormId(element, expected) { const formDefinition = getFormDefinition(element); @@ -783,6 +851,13 @@ function expectBindingType(element, expected) { expect(formDefinition.get('bindingType')).to.eql(expected); } +function expectVersionTag(element, expected) { + const formDefinition = getFormDefinition(element); + + expect(formDefinition).to.exist; + expect(formDefinition.get('versionTag')).to.eql(expected); +} + function expectFormKey(element, expected) { const formDefinition = getFormDefinition(element); diff --git a/test/spec/provider/zeebe/TargetProps.bpmn b/test/spec/provider/zeebe/TargetProps.bpmn index 7d60401d4..76e3831a7 100644 --- a/test/spec/provider/zeebe/TargetProps.bpmn +++ b/test/spec/provider/zeebe/TargetProps.bpmn @@ -1,5 +1,5 @@ - + @@ -20,7 +20,7 @@ - + diff --git a/test/spec/provider/zeebe/TargetProps.spec.js b/test/spec/provider/zeebe/TargetProps.spec.js index 78156e561..3c5fe548e 100644 --- a/test/spec/provider/zeebe/TargetProps.spec.js +++ b/test/spec/provider/zeebe/TargetProps.spec.js @@ -28,6 +28,8 @@ import { getProcessId } from 'src/provider/zeebe/utils/CalledElementUtil.js'; +import { getVersionTag } from 'src/provider/zeebe/properties/shared/VersionTag'; + import BpmnPropertiesPanel from 'src/render'; import CoreModule from 'bpmn-js/lib/core'; import ModelingModule from 'bpmn-js/lib/features/modeling'; @@ -316,4 +318,95 @@ describe('provider/zeebe - TargetProps', function() { }); + + describe('bpmn:CallActivity#calledElement.versionTag', function() { + + it('should display', inject(async function(elementRegistry, selection) { + + // given + const callActivity = elementRegistry.get('CallActivity_4'); + + // assume + const versionTag = getVersionTag(callActivity); + + expect(versionTag).to.equal('v1.0.0'); + + // when + await act(() => { + selection.select(callActivity); + }); + + const versionTagInput = domQuery('input[name=versionTag]', container); + + // then + expect(versionTagInput).to.exist; + + expect(versionTagInput.value).to.equal('v1.0.0'); + })); + + + it('should not display', inject(async function(elementRegistry, selection) { + + // given + const task = elementRegistry.get('Task_1'); + + // when + await act(() => { + selection.select(task); + }); + + const versionTagInput = domQuery('input[name=versionTag]', container); + + // then + expect(versionTagInput).not.to.exist; + })); + + + it('should update', inject(async function(elementRegistry, selection) { + + // given + const callActivity = elementRegistry.get('CallActivity_4'); + + await act(() => { + selection.select(callActivity); + }); + + const versionTagInput = domQuery('input[name=versionTag]', container); + + // when + changeInput(versionTagInput, 'v2.0.0'); + + // then + const versionTag = getVersionTag(callActivity); + + expect(versionTag).to.equal('v2.0.0'); + })); + + + it('should update on external change', + inject(async function(elementRegistry, selection, commandStack) { + + // given + const callActivity = elementRegistry.get('CallActivity_4'), + originalValue = getVersionTag(callActivity); + + await act(() => { + selection.select(callActivity); + }); + + const versionTagInput = domQuery('input[name=versionTag]', container); + changeInput(versionTagInput, 'v2.0.0'); + + // when + await act(() => { + commandStack.undo(); + }); + + // then + expect(getVersionTag(callActivity)).to.eql(originalValue); + }) + ); + + }); + });