Skip to content

Commit

Permalink
feat: make Zeebe user task the default implementation of user task
Browse files Browse the repository at this point in the history
Related to camunda/camunda-modeler#4648

---------

Co-authored-by: Maciej Barelkowski <[email protected]>
  • Loading branch information
Skaiir and barmac committed Dec 10, 2024
1 parent 9ededd5 commit dceaf78
Show file tree
Hide file tree
Showing 6 changed files with 352 additions and 34 deletions.
81 changes: 81 additions & 0 deletions lib/camunda-cloud/CreateZeebeUserTaskBehavior.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { getBusinessObject, is } from 'bpmn-js/lib/util/ModelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';

import { createElement } from '../util/ElementUtil';
import { getExtensionElementsList } from '../util/ExtensionElementsUtil';

const HIGH_PRIORITY = 5000;

/**
* Zeebe BPMN specific behavior for creating user tasks.
*/
export default class CreateZeebeUserTaskBehavior extends CommandInterceptor {
constructor(bpmnFactory, eventBus, modeling) {
super(eventBus);

/**
* Add zeebe:userTask extension element when creating bpmn:UserTask.
*/
this.postExecuted(
[ 'shape.create', 'shape.replace' ],
HIGH_PRIORITY,
function(context) {
const shape = context.shape || context.newShape;
const explicitlyDisabled = context.hints && context.hints.createElementsBehavior === false;

if (!is(shape, 'bpmn:UserTask') || explicitlyDisabled) {
return;
}

let userTaskElement = getZeebeUserTask(shape);
if (userTaskElement) {
return;
}

const businessObject = getBusinessObject(shape);
let extensionElements = businessObject.get('extensionElements');

if (!extensionElements) {
extensionElements = createElement(
'bpmn:ExtensionElements',
{
values: [],
},
businessObject,
bpmnFactory
);

modeling.updateProperties(shape, { extensionElements });
}

userTaskElement = createElement(
'zeebe:UserTask',
{},
extensionElements,
bpmnFactory
);

modeling.updateModdleProperties(shape, extensionElements, {
values: [ ...(extensionElements.values || []), userTaskElement ],
});
},
true
);
}
}

CreateZeebeUserTaskBehavior.$inject = [ 'bpmnFactory', 'eventBus', 'modeling' ];

/**
* Get zeebe:userTask extension.
*
* @param {djs.model.Base|ModdleElement} element
*
* @returns {ModdleElement|null}
*/
function getZeebeUserTask(element) {
const businessObject = getBusinessObject(element);
const userTaskElements = getExtensionElementsList(businessObject, 'zeebe:UserTask');

return userTaskElements[0] || null;
}
3 changes: 3 additions & 0 deletions lib/camunda-cloud/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import CleanUpSubscriptionBehavior from './CleanUpSubscriptionBehavior';
import CleanUpTimerExpressionBehavior from './CleanUpTimerExpressionBehavior';
import CopyPasteBehavior from './CopyPasteBehavior';
import CreateZeebeCallActivityBehavior from './CreateZeebeCallActivityBehavior';
import CreateZeebeUserTaskBehavior from './CreateZeebeUserTaskBehavior';
import DeleteParticipantBehaviour from '../shared/DeleteParticipantBehaviour';
import FormsBehavior from './FormsBehavior';
import RemoveAssignmentDefinitionBehavior from './RemoveAssignmentDefinitionBehavior';
Expand All @@ -22,6 +23,7 @@ export default {
'cleanUpTimerExpressionBehavior',
'copyPasteBehavior',
'createZeebeCallActivityBehavior',
'createZeebeUserTaskBehavior',
'deleteParticipantBehaviour',
'formsBehavior',
'removeAssignmentDefinitionBehavior',
Expand All @@ -36,6 +38,7 @@ export default {
cleanUpTimerExpressionBehavior: [ 'type', CleanUpTimerExpressionBehavior ],
copyPasteBehavior: [ 'type', CopyPasteBehavior ],
createZeebeCallActivityBehavior: [ 'type', CreateZeebeCallActivityBehavior ],
createZeebeUserTaskBehavior: [ 'type', CreateZeebeUserTaskBehavior ],
deleteParticipantBehaviour: [ 'type', DeleteParticipantBehaviour ],
formsBehavior: [ 'type', FormsBehavior ],
removeAssignmentDefinitionBehavior: [ 'type', RemoveAssignmentDefinitionBehavior ],
Expand Down
18 changes: 0 additions & 18 deletions test/camunda-cloud/CleanUpTaskListenersBehaviorSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,24 +155,6 @@ describe('camunda-cloud/features/modeling - CleanUpTaskListenersBehavior', funct
}));


it('should remove zeebe:TaskListeners for non zeebe user task', inject(function(bpmnReplace, elementRegistry) {

// given
let el = elementRegistry.get('NonZeebeUserTask');

// when
bpmnReplace.replaceElement(el, {
type: 'bpmn:UserTask'
});

// then
el = elementRegistry.get('NonZeebeUserTask');
const extensionElements = getExtensionElements(el);

expect(extensionElements.get('values')).to.have.lengthOf(0);
}));


it('should remove zeebe:TaskListeners container when empty', inject(function(elementRegistry, modeling) {

// given
Expand Down
239 changes: 239 additions & 0 deletions test/camunda-cloud/CreateZeebeUserTaskBehaviorSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import { bootstrapCamundaCloudModeler, inject } from 'test/TestHelper';

import { getBusinessObject, is } from 'bpmn-js/lib/util/ModelUtil';
import { find } from 'min-dash';

import { getExtensionElementsList } from 'lib/util/ExtensionElementsUtil';


import emptyProcessDiagramXML from './process-empty.bpmn';
import userTasksXML from './process-user-tasks.bpmn';

describe('camunda-cloud/features/modeling - CreateZeebeUserTaskBehavior', function() {

describe('when a shape is created', function() {

beforeEach(bootstrapCamundaCloudModeler(emptyProcessDiagramXML));


it('should execute when creating bpmn:UserTask', inject(function(
canvas,
modeling
) {

// given
const rootElement = canvas.getRootElement();

// when
const newShape = modeling.createShape(
{ type: 'bpmn:UserTask' },
{ x: 100, y: 100 },
rootElement
);

// then
const businessObject = getBusinessObject(newShape),
zeebeUserTaskExtensions = getExtensionElementsList(businessObject, 'zeebe:UserTask');

expect(zeebeUserTaskExtensions).to.exist;
expect(zeebeUserTaskExtensions).to.have.lengthOf(1);
}));


it('should NOT execute when zeebe:UserTask already present', inject(function(
canvas,
bpmnFactory,
modeling
) {

// given
const rootElement = canvas.getRootElement(),
bo = bpmnFactory.create('bpmn:UserTask', {
extensionElements: bpmnFactory.create('bpmn:ExtensionElements', {
values: [ bpmnFactory.create('zeebe:UserTask') ],
}),
});

// when
const newShape = modeling.createShape(
{ type: 'bpmn:UserTask', businessObject: bo },
{ x: 100, y: 100 },
rootElement
);

// then
const businessObject = getBusinessObject(newShape),
zeebeUserTaskExtensions = getExtensionElementsList(businessObject, 'zeebe:UserTask');

expect(zeebeUserTaskExtensions).to.exist;
expect(zeebeUserTaskExtensions).to.have.lengthOf(1);
}));


it('should NOT execute when creating bpmn:Task', inject(function(
canvas,
modeling
) {

// given
const rootElement = canvas.getRootElement();

// when
const newShape = modeling.createShape(
{ type: 'bpmn:Task' },
{ x: 100, y: 100 },
rootElement
);

// then
const zeebeUserTaskExtension = getZeebeUserTask(newShape);

expect(zeebeUserTaskExtension).not.to.exist;
}));
});


describe('when a shape is pasted', function() {

beforeEach(bootstrapCamundaCloudModeler(userTasksXML));


it('should NOT add zeebe:UserTask', inject(function(
canvas,
copyPaste,
elementRegistry
) {

// given
const rootElement = canvas.getRootElement();
const userTask = elementRegistry.get('UserTask_1');

// when
copyPaste.copy(userTask);

const elements = copyPaste.paste({
element: rootElement,
point: {
x: 1000,
y: 1000,
},
});

// then
const pastedUserTask = find(elements, (element) =>
is(element, 'bpmn:UserTask')
);

const zeebeUserTask = getZeebeUserTask(pastedUserTask);

expect(zeebeUserTask).not.to.exist;
}));


it('should keep existing zeebe:UserTask', inject(function(
canvas,
copyPaste,
elementRegistry
) {

// given
const rootElement = canvas.getRootElement();
const userTask = elementRegistry.get('withZeebeUserTask');

// when
copyPaste.copy(userTask);

const elements = copyPaste.paste({
element: rootElement,
point: {
x: 1000,
y: 1000,
},
});

// then
const pastedUserTask = find(elements, (element) =>
is(element, 'bpmn:UserTask')
);
const zeebeUserTasks = getExtensionElementsList(pastedUserTask, 'zeebe:UserTask');

expect(zeebeUserTasks).to.exist;
expect(zeebeUserTasks).to.have.lengthOf(1);
}));
});


describe('when a shape is replaced', function() {

beforeEach(bootstrapCamundaCloudModeler(userTasksXML));


it('should add zeebe:UserTask when target is bpmn:UserTask', inject(function(
elementRegistry,
bpmnReplace,
canvas,
modeling
) {

// given
const rootElement = canvas.getRootElement();

// when
const task = modeling.createShape(
{ type: 'bpmn:Task', id: 'simpleTask' },
{ x: 100, y: 100 },
rootElement
);
bpmnReplace.replaceElement(task, { type: 'bpmn:UserTask' });

// then
const updatedTask = elementRegistry.get(task.id),
zeebeUserTaskExtension = getZeebeUserTask(updatedTask);

expect(zeebeUserTaskExtension).to.exist;
}));


it('should NOT add zeebe:UserTask when target is bpmn:ServiceTask', inject(function(
elementRegistry,
bpmnReplace,
canvas,
modeling
) {

// given
const rootElement = canvas.getRootElement();

// when
const task = modeling.createShape(
{ type: 'bpmn:Task', id: 'simpleTask' },
{ x: 100, y: 100 },
rootElement
);
bpmnReplace.replaceElement(task, { type: 'bpmn:ServiceTask' });

// then
const updatedTask = elementRegistry.get(task.id),
zeebeUserTask = getZeebeUserTask(updatedTask);

expect(zeebeUserTask).not.to.exist;
}));
});
});


// helpers //////////

/**
* Get the first zeebe:userTask element of an element.
*
* @param {djs.model.Base|ModdleElement} element
*
* @returns {ModdleElement|null}
*/
function getZeebeUserTask(element) {
const businessObject = getBusinessObject(element);
const userTaskElements = getExtensionElementsList(businessObject, 'zeebe:UserTask');

return userTaskElements[0] || null;
}
Loading

0 comments on commit dceaf78

Please sign in to comment.