diff --git a/src/provider/zeebe/ZeebePropertiesProvider.js b/src/provider/zeebe/ZeebePropertiesProvider.js
index c552c81d..80fec3db 100644
--- a/src/provider/zeebe/ZeebePropertiesProvider.js
+++ b/src/provider/zeebe/ZeebePropertiesProvider.js
@@ -16,6 +16,7 @@ import {
MultiInstanceProps,
OutputPropagationProps,
OutputProps,
+ PriorityDefinitionProps,
ScriptImplementationProps,
ScriptProps,
SignalProps,
@@ -293,7 +294,8 @@ function AssignmentDefinitionGroup(element, injector) {
label: translate('Assignment'),
entries: [
...AssignmentDefinitionProps({ element }),
- ...TaskScheduleProps({ element })
+ ...TaskScheduleProps({ element }),
+ ...PriorityDefinitionProps({ element })
],
component: Group
};
diff --git a/src/provider/zeebe/properties/PriorityDefinitionProps.js b/src/provider/zeebe/properties/PriorityDefinitionProps.js
new file mode 100644
index 00000000..8d26f340
--- /dev/null
+++ b/src/provider/zeebe/properties/PriorityDefinitionProps.js
@@ -0,0 +1,163 @@
+import {
+ getBusinessObject,
+ is
+} from 'bpmn-js/lib/util/ModelUtil';
+
+import { isFeelEntryEdited } from '@bpmn-io/properties-panel';
+
+import {
+ getExtensionElementsList
+} from '../../../utils/ExtensionElementsUtil';
+
+import {
+ createElement
+} from '../../../utils/ElementUtil';
+
+import {
+ useService
+} from '../../../hooks';
+
+import { FeelEntryWithVariableContext } from '../../../entries/FeelEntryWithContext';
+
+
+export function PriorityDefinitionProps(props) {
+ const {
+ element
+ } = props;
+
+ if (!is(element, 'bpmn:UserTask')) {
+ return [];
+ }
+
+ return [
+ {
+ id: 'priorityDefinitionPriority',
+ component: Priority,
+ isEdited: isFeelEntryEdited
+ }
+ ];
+}
+
+function Priority(props) {
+ const {
+ element
+ } = props;
+
+ const commandStack = useService('commandStack');
+ const bpmnFactory = useService('bpmnFactory');
+ const translate = useService('translate');
+ const debounce = useService('debounceInput');
+
+ const getValue = () => {
+ return (getPriorityDefinition(element) || {}).priority || '50';
+ };
+
+ const setValue = (value) => {
+ const commands = [];
+
+ const businessObject = getBusinessObject(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 PriorityDefinition
+ let priorityDefinition = getPriorityDefinition(element);
+ const isNullValue = value === null || value === '' || value === undefined;
+
+ if (priorityDefinition && isNullValue) {
+
+ // (3a) remove priority definition if it exists and priority is set to null
+ commands.push({
+ cmd: 'element.updateModdleProperties',
+ context: {
+ element,
+ moddleElement: extensionElements,
+ properties: {
+ values: extensionElements.get('values').filter(v => v !== priorityDefinition)
+ }
+ }
+ });
+
+ } else if (priorityDefinition && !isNullValue) {
+
+ // (3b) update priority definition if it already exists
+ commands.push({
+ cmd: 'element.updateModdleProperties',
+ context: {
+ element,
+ moddleElement: priorityDefinition,
+ properties: { priority: value }
+ }
+ });
+
+ } else if (!priorityDefinition && !isNullValue) {
+
+ // (3c) create priority definition if it does not exist
+ priorityDefinition = createElement(
+ 'zeebe:PriorityDefinition',
+ { priority: value },
+ extensionElements,
+ bpmnFactory
+ );
+
+ commands.push({
+ cmd: 'element.updateModdleProperties',
+ context: {
+ element,
+ moddleElement: extensionElements,
+ properties: {
+ values: [ ...extensionElements.get('values'), priorityDefinition ]
+ }
+ }
+ });
+ }
+
+ // (4) commit all updates
+ commandStack.execute('properties-panel.multi-command-executor', commands);
+ };
+
+ const tooltip = `
+
An integer value that defines the priority of a User Task. It can be set as an expression.
+
The priority can range from 0 to 100, with a default value of 50.
+
A higher value indicates a higher priority.
+
Read more about it here.
+
`;
+
+ return FeelEntryWithVariableContext({
+ element,
+ id: 'priorityDefinitionPriority',
+ label: translate('Priority'),
+ feel: 'optional',
+ tooltip,
+ getValue,
+ setValue,
+ debounce
+ });
+}
+
+
+// helper ///////////////////////
+
+export function getPriorityDefinition(element) {
+ const businessObject = getBusinessObject(element);
+
+ return getExtensionElementsList(businessObject, 'zeebe:PriorityDefinition')[0];
+}
diff --git a/src/provider/zeebe/properties/index.js b/src/provider/zeebe/properties/index.js
index 62521335..8282f3c4 100644
--- a/src/provider/zeebe/properties/index.js
+++ b/src/provider/zeebe/properties/index.js
@@ -13,6 +13,7 @@ export { MessageProps } from './MessageProps';
export { MultiInstanceProps } from './MultiInstanceProps';
export { OutputPropagationProps } from './OutputPropagationProps';
export { OutputProps } from './OutputProps';
+export { PriorityDefinitionProps } from './PriorityDefinitionProps';
export { ScriptImplementationProps } from './ScriptImplementationProps';
export { ScriptProps } from './ScriptProps';
export { SignalProps } from './SignalProps';
diff --git a/test/spec/provider/zeebe/PriorityDefinitionProps.bpmn b/test/spec/provider/zeebe/PriorityDefinitionProps.bpmn
new file mode 100644
index 00000000..dd08f4b8
--- /dev/null
+++ b/test/spec/provider/zeebe/PriorityDefinitionProps.bpmn
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/spec/provider/zeebe/PriorityDefinitionProps.spec.js b/test/spec/provider/zeebe/PriorityDefinitionProps.spec.js
new file mode 100644
index 00000000..a34824e8
--- /dev/null
+++ b/test/spec/provider/zeebe/PriorityDefinitionProps.spec.js
@@ -0,0 +1,249 @@
+import TestContainer from 'mocha-test-container-support';
+
+import {
+ act
+} from '@testing-library/preact';
+
+import {
+ bootstrapPropertiesPanel,
+ changeInput,
+ inject
+} from 'test/TestHelper';
+
+import {
+ query as domQuery
+} from 'min-dom';
+
+import {
+ getBusinessObject
+} from 'bpmn-js/lib/util/ModelUtil';
+
+import { getPriorityDefinition } from 'src/provider/zeebe/properties/PriorityDefinitionProps';
+
+import CoreModule from 'bpmn-js/lib/core';
+import SelectionModule from 'diagram-js/lib/features/selection';
+import ModelingModule from 'bpmn-js/lib/features/modeling';
+
+import BpmnPropertiesPanel from 'src/render';
+
+import ZeebePropertiesProvider from 'src/provider/zeebe';
+
+import BehaviorsModule from 'camunda-bpmn-js-behaviors/lib/camunda-cloud';
+
+import zeebeModdleExtensions from 'zeebe-bpmn-moddle/resources/zeebe';
+
+import diagramXML from './PriorityDefinitionProps.bpmn';
+import { setEditorValue } from '../../../TestHelper';
+
+
+describe('provider/zeebe - PriorityDefinitionProps', function() {
+
+ const testModules = [
+ CoreModule,
+ SelectionModule,
+ ModelingModule,
+ BpmnPropertiesPanel,
+ ZeebePropertiesProvider,
+ BehaviorsModule
+ ];
+
+ const moddleExtensions = {
+ zeebe: zeebeModdleExtensions
+ };
+
+ let container;
+
+ beforeEach(function() {
+ container = TestContainer.get(this);
+ });
+
+ beforeEach(bootstrapPropertiesPanel(diagramXML, {
+ modules: testModules,
+ moddleExtensions,
+ debounceInput: false
+ }));
+
+
+ describe('zeebe:priorityDefinition', function() {
+
+ it('should NOT display for service task', inject(async function(elementRegistry, selection) {
+
+ // given
+ const serviceTask = elementRegistry.get('ServiceTask_1');
+
+ await act(() => {
+ selection.select(serviceTask);
+ });
+
+ // when
+ const priorityInput = domQuery('input[name=priorityDefinitionPriority]', container);
+
+ // then
+ expect(priorityInput).to.not.exist;
+ }));
+
+
+ it('should display for user task', inject(async function(elementRegistry, selection) {
+
+ // given
+ const userTask = elementRegistry.get('UserTask_1');
+
+ await act(() => {
+ selection.select(userTask);
+ });
+
+ // when
+ const entry = domQuery('[data-entry-id="priorityDefinitionPriority"]', container);
+
+ // then
+ expect(entry).to.exist;
+
+ // is FEEL input
+ const input = domQuery('[role="textbox"]', entry);
+ expect(input).to.exist;
+
+ const priorityDefinition = getPriorityDefinition(userTask);
+ const feelExpression = priorityDefinition.get('priority').substring(1);
+
+ expect(input.textContent).to.equal(feelExpression);
+ }));
+
+
+ it('should display value 50 if undefined', inject(async function(elementRegistry, selection) {
+
+ // given
+ const userTask = elementRegistry.get('UserTask_2');
+
+ await act(() => {
+ selection.select(userTask);
+ });
+
+ // when
+ const priorityInput = domQuery('input[name=priorityDefinitionPriority]', container);
+
+ // then
+ expect(priorityInput.value).to.equal('50');
+ }));
+
+
+ it('should update', inject(async function(elementRegistry, selection) {
+
+ // given
+ const userTask = elementRegistry.get('UserTask_1');
+
+ await act(() => {
+ selection.select(userTask);
+ });
+
+ // when
+ const priorityInput = domQuery('[data-entry-id="priorityDefinitionPriority"] [role="textbox"]', container);
+
+ await setEditorValue(priorityInput, 'newValue');
+
+ // then
+ // keep FEEL configuration
+ expect(getPriorityDefinition(userTask).get('priority')).to.eql('=newValue');
+ }));
+
+
+ it('should update on external change',
+ inject(async function(elementRegistry, selection, commandStack) {
+
+ // given
+ const userTask = elementRegistry.get('UserTask_1');
+ const originalValue = getPriorityDefinition(userTask).get('priority');
+
+ await act(() => {
+ selection.select(userTask);
+ });
+ const priorityInput = domQuery('[data-entry-id="priorityDefinitionPriority"] [role="textbox"]', container);
+ await setEditorValue(priorityInput, 'newValue');
+
+ // when
+ await act(() => {
+ commandStack.undo();
+ });
+
+ // then
+ expect('=' + priorityInput.textContent).to.eql(originalValue);
+ })
+ );
+
+
+ it('should create non-existing extension elements and priority definition',
+ inject(async function(elementRegistry, selection) {
+
+ // given
+ const userTask = elementRegistry.get('UserTask_2');
+
+ // assume
+ expect(getBusinessObject(userTask).get('extensionElements')).to.not.exist;
+
+ await act(() => {
+ selection.select(userTask);
+ });
+
+ // when
+ const priorityInput = domQuery('input[name=priorityDefinitionPriority]', container);
+ changeInput(priorityInput, 'newValue');
+
+ // then
+ expect(getBusinessObject(userTask).get('extensionElements')).to.exist;
+ })
+ );
+
+
+ it('should re-use existing extension elements, creating new priority definition',
+ inject(async function(elementRegistry, selection) {
+
+ // given
+ const userTask = elementRegistry.get('UserTask_3');
+
+ // assume
+ expect(getBusinessObject(userTask).get('extensionElements')).to.exist;
+ expect(getPriorityDefinition(userTask)).not.to.exist;
+
+ await act(() => {
+ selection.select(userTask);
+ });
+
+ // when
+ const priorityInput = domQuery('input[name=priorityDefinitionPriority]', container);
+ changeInput(priorityInput, 'newValue');
+
+ // then
+ const extensionElements = getBusinessObject(userTask).get('extensionElements');
+ expect(getPriorityDefinition(userTask).get('priority')).to.eql('newValue');
+ expect(extensionElements.values).to.have.length(2);
+ })
+ );
+
+ });
+
+ describe('integration', function() {
+
+ describe('removing priority definition when empty', function() {
+
+ it('removing zeebe:priority', inject(async function(elementRegistry, selection) {
+
+ // given
+ const userTask = elementRegistry.get('UserTask_4');
+
+ await act(() => {
+ selection.select(userTask);
+ });
+
+ // when
+ const priorityInput = domQuery('[data-entry-id="priorityDefinitionPriority"] [role="textbox"]', container);
+
+ await setEditorValue(priorityInput, '');
+
+ // then
+ expect(getPriorityDefinition(userTask)).not.to.exist;
+ }));
+
+ });
+
+ });
+
+});