diff --git a/src/contextProvider/zeebe/TooltipProvider.js b/src/contextProvider/zeebe/TooltipProvider.js
index 4bcee49ea..90a0c70da 100644
--- a/src/contextProvider/zeebe/TooltipProvider.js
+++ b/src/contextProvider/zeebe/TooltipProvider.js
@@ -278,6 +278,18 @@ const TooltipProvider = {
);
+ },
+ 'versionTag': (element) => {
+
+ const translate = useService('translate');
+
+ return (
+
+
+ { translate('Specifying a version tag will allow you to reference this process in another process.') }
+
+
+ );
}
};
diff --git a/src/provider/zeebe/ZeebePropertiesProvider.js b/src/provider/zeebe/ZeebePropertiesProvider.js
index c9faac1c5..51cbf5acc 100644
--- a/src/provider/zeebe/ZeebePropertiesProvider.js
+++ b/src/provider/zeebe/ZeebePropertiesProvider.js
@@ -1,5 +1,7 @@
import { Group, ListGroup } from '@bpmn-io/properties-panel';
+import { findIndex } from 'min-dash';
+
import {
AssignmentDefinitionProps,
BusinessRuleImplementationProps,
@@ -22,7 +24,8 @@ import {
TaskDefinitionProps,
TaskScheduleProps,
TimerProps,
- UserTaskImplementationProps
+ UserTaskImplementationProps,
+ VersionTagProps
} from './properties';
import { ExtensionPropertiesProps } from '../shared/ExtensionPropertiesProps';
@@ -69,6 +72,7 @@ export default class ZeebePropertiesProvider {
groups = groups.concat(this._getGroups(element));
// (2) update existing groups with zeebe specific properties
+ updateGeneralGroup(groups, element);
updateErrorGroup(groups, element);
updateEscalationGroup(groups, element);
updateMessageGroup(groups, element);
@@ -315,6 +319,22 @@ function ExtensionPropertiesGroup(element, injector) {
return null;
}
+function updateGeneralGroup(groups, element) {
+
+ const generalGroup = findGroup(groups, 'general');
+
+ if (!generalGroup) {
+ return;
+ }
+
+ const { entries } = generalGroup;
+
+ const executableEntry = findIndex(entries, (entry) => entry.id === 'isExecutable');
+ const insertIndex = executableEntry >= 0 ? executableEntry : entries.length;
+
+ entries.splice(insertIndex, 0, ...VersionTagProps({ element }));
+}
+
function updateErrorGroup(groups, element) {
const errorGroup = findGroup(groups, 'error');
diff --git a/src/provider/zeebe/properties/VersionTagProps.js b/src/provider/zeebe/properties/VersionTagProps.js
new file mode 100644
index 000000000..cc76b3918
--- /dev/null
+++ b/src/provider/zeebe/properties/VersionTagProps.js
@@ -0,0 +1,139 @@
+import {
+ getBusinessObject,
+ is
+} from 'bpmn-js/lib/util/ModelUtil';
+
+import { TextFieldEntry, isTextFieldEntryEdited } from '@bpmn-io/properties-panel';
+
+import {
+ useService
+} from '../../../hooks';
+
+import { createElement } from '../../../utils/ElementUtil';
+
+import { getExtensionElementsList } from '../../../utils/ExtensionElementsUtil';
+
+
+export function VersionTagProps(props) {
+ const {
+ element
+ } = props;
+
+ const businessObject = getBusinessObject(element);
+
+ if (!is(element, 'bpmn:Process') &&
+ !(is(element, 'bpmn:Participant') && businessObject.get('processRef'))) {
+ return [];
+ }
+
+ return [
+ {
+ id: 'versionTag',
+ component: VersionTag,
+ isEdited: isTextFieldEntryEdited
+ },
+ ];
+}
+
+function VersionTag(props) {
+ const { element } = props;
+
+ const bpmnFactory = useService('bpmnFactory');
+ const commandStack = useService('commandStack');
+ const debounce = useService('debounceInput');
+ const translate = useService('translate');
+
+ const getValue = () => {
+ const versionTag = getVersionTag(element);
+
+ if (versionTag) {
+ return versionTag.get('value');
+ }
+ };
+
+ const setValue = (value) => {
+ let commands = [];
+
+ const businessObject = getProcess(element);
+
+ let extensionElements = businessObject.get('extensionElements');
+
+ // (1) ensure extension elements
+ if (!extensionElements) {
+ extensionElements = createElement(
+ 'bpmn:ExtensionElements',
+ { values: [] },
+ businessObject,
+ bpmnFactory
+ );
+
+ commands.push({
+ cmd: 'element.updateModdleProperties',
+ context: {
+ element,
+ moddleElement: businessObject,
+ properties: { extensionElements }
+ }
+ });
+ }
+
+ // (2) ensure version tag
+ let versionTag = getVersionTag(element);
+
+ if (!versionTag) {
+ versionTag = createElement(
+ 'zeebe:VersionTag',
+ {},
+ extensionElements,
+ bpmnFactory
+ );
+
+ commands.push({
+ cmd: 'element.updateModdleProperties',
+ context: {
+ element,
+ moddleElement: extensionElements,
+ properties: {
+ values: [ ...extensionElements.get('values'), versionTag ]
+ }
+ }
+ });
+ }
+
+ // (3) update version tag value
+ commands.push({
+ cmd: 'element.updateModdleProperties',
+ context: {
+ element,
+ moddleElement: versionTag,
+ properties: { value }
+ }
+ });
+
+ commandStack.execute('properties-panel.multi-command-executor', commands);
+ };
+
+ return TextFieldEntry({
+ element,
+ id: 'versionTag',
+ label: translate('Version tag'),
+ getValue,
+ setValue,
+ debounce
+ });
+}
+
+
+// helper //////////////////
+
+function getProcess(element) {
+ return is(element, 'bpmn:Process') ?
+ getBusinessObject(element) :
+ getBusinessObject(element).get('processRef');
+}
+
+function getVersionTag(element) {
+ const businessObject = getProcess(element);
+
+ return getExtensionElementsList(businessObject, 'zeebe:VersionTag')[ 0 ];
+}
\ No newline at end of file
diff --git a/src/provider/zeebe/properties/index.js b/src/provider/zeebe/properties/index.js
index c8dbc34c4..bc1c7e57f 100644
--- a/src/provider/zeebe/properties/index.js
+++ b/src/provider/zeebe/properties/index.js
@@ -19,4 +19,5 @@ export { TargetProps } from './TargetProps';
export { TaskDefinitionProps } from './TaskDefinitionProps';
export { TaskScheduleProps } from './TaskScheduleProps';
export { TimerProps } from './TimerProps';
-export { UserTaskImplementationProps } from './UserTaskImplementationProps';
\ No newline at end of file
+export { UserTaskImplementationProps } from './UserTaskImplementationProps';
+export { VersionTagProps } from './VersionTagProps';
\ No newline at end of file
diff --git a/test/spec/provider/zeebe/VersionTagProps-collaboration.bpmn b/test/spec/provider/zeebe/VersionTagProps-collaboration.bpmn
new file mode 100644
index 000000000..855750419
--- /dev/null
+++ b/test/spec/provider/zeebe/VersionTagProps-collaboration.bpmn
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/spec/provider/zeebe/VersionTagProps-process.bpmn b/test/spec/provider/zeebe/VersionTagProps-process.bpmn
new file mode 100644
index 000000000..c137c16ff
--- /dev/null
+++ b/test/spec/provider/zeebe/VersionTagProps-process.bpmn
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/spec/provider/zeebe/VersionTagProps.spec.js b/test/spec/provider/zeebe/VersionTagProps.spec.js
new file mode 100644
index 000000000..5011d792d
--- /dev/null
+++ b/test/spec/provider/zeebe/VersionTagProps.spec.js
@@ -0,0 +1,273 @@
+import TestContainer from 'mocha-test-container-support';
+
+import {
+ act
+} from '@testing-library/preact';
+
+import {
+ bootstrapPropertiesPanel,
+ changeInput,
+ inject,
+ mouseEnter
+} from 'test/TestHelper';
+
+import {
+ query as domQuery
+} from 'min-dom';
+
+import {
+ getBusinessObject
+} from 'bpmn-js/lib/util/ModelUtil';
+
+import CoreModule from 'bpmn-js/lib/core';
+import SelectionModule from 'diagram-js/lib/features/selection';
+import ModelingModule from 'bpmn-js/lib/features/modeling';
+
+import { is } from 'bpmn-js/lib/util/ModelUtil';
+
+import BpmnPropertiesPanel from 'src/render';
+
+import BpmnPropertiesProvider from 'src/provider/bpmn';
+import ZeebePropertiesProvider from 'src/provider/zeebe';
+
+import zeebeModdleExtensions from 'zeebe-bpmn-moddle/resources/zeebe';
+
+import TooltipProvider from 'src/contextProvider/zeebe/TooltipProvider';
+
+import {
+ getExtensionElementsList
+} from 'src/utils/ExtensionElementsUtil';
+
+import processDiagramXML from './VersionTagProps-process.bpmn';
+import collaborationDiagramXML from './VersionTagProps-collaboration.bpmn';
+
+
+describe('provider/zeebe - TaskDefinitionProps', function() {
+
+ const testModules = [
+ CoreModule, SelectionModule, ModelingModule,
+ BpmnPropertiesPanel,
+ BpmnPropertiesProvider,
+ ZeebePropertiesProvider
+ ];
+
+ const moddleExtensions = {
+ zeebe: zeebeModdleExtensions
+ };
+
+ let container, clock;
+
+ beforeEach(function() {
+ container = TestContainer.get(this);
+ clock = sinon.useFakeTimers();
+ });
+
+ afterEach(function() {
+ clock.restore();
+ });
+
+ function openTooltip() {
+ return act(() => {
+ const wrapper = domQuery('.bio-properties-panel-tooltip-wrapper', container);
+ mouseEnter(wrapper);
+ clock.tick(200);
+ });
+ }
+
+ [
+ [ 'process', 'Process_1', processDiagramXML ],
+ [ 'collaboration', 'Participant_1', collaborationDiagramXML ]
+ ].forEach(([ title, elementId, diagramXML ]) => {
+
+ describe(title, function() {
+
+ beforeEach(bootstrapPropertiesPanel(diagramXML, {
+ modules: testModules,
+ moddleExtensions,
+ propertiesPanel: {
+ tooltip: TooltipProvider
+ },
+ debounceInput: false
+ }));
+
+
+ it('should NOT display for task', inject(async function(elementRegistry, selection) {
+
+ // given
+ const task = elementRegistry.get('Task_1');
+
+ await act(() => {
+ selection.select(task);
+ });
+
+ // when
+ const versionTagInput = domQuery('input[name=versionTag]', container);
+
+ // then
+ expect(versionTagInput).to.not.exist;
+ }));
+
+
+ it('should display for process', inject(async function(elementRegistry, selection) {
+
+ // given
+ const element = elementRegistry.get(elementId);
+
+ await act(() => {
+ selection.select(element);
+ });
+
+ // when
+ const versionTagInput = domQuery('input[name=versionTag]', container);
+
+ // then
+ expect(versionTagInput).to.exist;
+ expect(versionTagInput.value).to.eql(getVersionTag(element).get('value'));
+ }));
+
+
+ it('should update', inject(async function(elementRegistry, selection) {
+
+ // given
+ const element = elementRegistry.get(elementId);
+
+ await act(() => {
+ selection.select(element);
+ });
+
+ // when
+ const versionTagInput = domQuery('input[name=versionTag]', container);
+
+ changeInput(versionTagInput, 'v2.0.0');
+
+ // then
+ expect(getVersionTag(element).get('value')).to.eql('v2.0.0');
+ }));
+
+
+ it('should update on external change',
+ inject(async function(elementRegistry, selection, commandStack) {
+
+ // given
+ const element = elementRegistry.get(elementId);
+
+ const originalValue = getVersionTag(element).get('value');
+
+ await act(() => {
+ selection.select(element);
+ });
+
+ const versionTagInput = domQuery('input[name=versionTag]', container);
+
+ changeInput(versionTagInput, 'v2.0.0');
+
+ // when
+ await act(() => {
+ commandStack.undo();
+ });
+
+ // then
+ expect(versionTagInput.value).to.eql(originalValue);
+ })
+ );
+
+
+ it('should create non existing extension elements',
+ inject(async function(elementRegistry, modeling, selection) {
+
+ // given
+ const element = elementRegistry.get(elementId);
+
+ modeling.updateModdleProperties(element, getProcess(element), { extensionElements: undefined });
+
+ // assume
+ expect(getProcess(element).get('extensionElements')).to.not.exist;
+
+ await act(() => {
+ selection.select(element);
+ });
+
+ // when
+ const versionTagInput = domQuery('input[name=versionTag]', container);
+
+ changeInput(versionTagInput, 'v1.0.0');
+
+ // then
+ expect(getProcess(element).get('extensionElements')).to.exist;
+ expect(getVersionTag(element).get('value')).to.eql('v1.0.0');
+ })
+ );
+
+
+ it('should create non existing version tag',
+ inject(async function(elementRegistry, modeling, selection) {
+
+ // given
+ const element = elementRegistry.get(elementId);
+
+ modeling.updateModdleProperties(
+ element,
+ getProcess(element).get('extensionElements'),
+ { values: [] }
+ );
+
+ // assume
+ expect(getProcess(element).get('extensionElements')).to.exist;
+ expect(getVersionTag(element)).not.to.exist;
+
+ await act(() => {
+ selection.select(element);
+ });
+
+ // when
+ const versionTagInput = domQuery('input[name=versionTag]', container);
+
+ changeInput(versionTagInput, 'v1.0.0');
+
+ // then
+ expect(getVersionTag(element)).to.exist;
+ expect(getVersionTag(element).get('value')).to.eql('v1.0.0');
+ })
+ );
+
+
+ it('should display correct documentation', inject(async function(elementRegistry, selection) {
+
+ // given
+ const element = elementRegistry.get(elementId);
+
+ await act(() => {
+ selection.select(element);
+ });
+
+ // when
+ await openTooltip();
+
+ const documentationLinkGroup = domQuery('.bio-properties-panel-tooltip-content p', container);
+
+
+ // then
+ expect(documentationLinkGroup).to.exist;
+ expect(documentationLinkGroup.textContent).to.equal('Specifying a version tag will allow you to reference this process in another process.');
+ }));
+
+ });
+
+ });
+
+});
+
+
+// helper //////////////////
+
+function getProcess(element) {
+ return is(element, 'bpmn:Process') ?
+ getBusinessObject(element) :
+ getBusinessObject(element).get('processRef');
+}
+
+function getVersionTag(element) {
+ const businessObject = getProcess(element);
+
+ return getExtensionElementsList(businessObject, 'zeebe:VersionTag')[ 0 ];
+}
\ No newline at end of file