diff --git a/lib/features/replace/BpmnReplace.js b/lib/features/replace/BpmnReplace.js index 77ba0566..91a41f34 100644 --- a/lib/features/replace/BpmnReplace.js +++ b/lib/features/replace/BpmnReplace.js @@ -1,7 +1,9 @@ 'use strict'; -var forEach = require('lodash/collection/forEach'), - filter = require('lodash/collection/filter'); +var forEach = require('lodash/collection/forEach'), + filter = require('lodash/collection/filter'), + pick = require('lodash/object/pick'), + assign = require('lodash/object/assign'); var REPLACE_OPTIONS = require ('./ReplaceOptions'); @@ -11,12 +13,20 @@ var startEventReplace = REPLACE_OPTIONS.START_EVENT, gatewayReplace = REPLACE_OPTIONS.GATEWAY, taskReplace = REPLACE_OPTIONS.TASK, subProcessExpandedReplace = REPLACE_OPTIONS.SUBPROCESS_EXPANDED, - transactionReplace = REPLACE_OPTIONS.TRANSACTION; + transactionReplace = REPLACE_OPTIONS.TRANSACTION, + boundaryEventReplace = REPLACE_OPTIONS.BOUNDARY_EVENT; var is = require('../../util/ModelUtil').is, getBusinessObject = require('../../util/ModelUtil').getBusinessObject, isExpanded = require('../../util/DiUtil').isExpanded; +var CUSTOM_PROPERTIES = [ + 'cancelActivity', + 'instantiate', + 'eventGatewayType' +]; + + /** * A replace menu provider that gives users the controls to choose * and replace BPMN elements with each other. @@ -60,13 +70,10 @@ function BpmnReplace(bpmnFactory, moddle, popupMenu, replace, selection, modelin eventDefinitions.push(eventDefinition); } - if (target.instantiate !== undefined) { - businessObject.instantiate = target.instantiate; - } + // initialize special properties defined in target definition + + assign(businessObject, pick(target, CUSTOM_PROPERTIES)); - if (target.eventGatewayType !== undefined) { - businessObject.eventGatewayType = target.eventGatewayType; - } // copy size (for activities only) if (is(oldBusinessObject, 'bpmn:Activity')) { @@ -77,6 +84,7 @@ function BpmnReplace(bpmnFactory, moddle, popupMenu, replace, selection, modelin newElement.height = element.height; } + if (is(oldBusinessObject, 'bpmn:SubProcess')) { newElement.isExpanded = isExpanded(oldBusinessObject); } @@ -236,6 +244,10 @@ function BpmnReplace(bpmnFactory, moddle, popupMenu, replace, selection, modelin }); } else + if (is(businessObject, 'bpmn:BoundaryEvent')) { + addEntries(boundaryEventReplace, filterEvents); + } else + if (is(businessObject, 'bpmn:FlowNode')) { addEntries(taskReplace, function(entry) { return entry.target.type !== businessObject.$type; @@ -247,13 +259,16 @@ function BpmnReplace(bpmnFactory, moddle, popupMenu, replace, selection, modelin var target = entry.target; - var eventDefinition = businessObject.eventDefinitions && businessObject.eventDefinitions[0].$type; - var isEventDefinitionEqual = target.eventDefinition == eventDefinition; - var isEventTypeEqual = businessObject.$type == target.type; + var eventDefinition = businessObject.eventDefinitions && businessObject.eventDefinitions[0].$type, + cancelActivity = target.cancelActivity !== false; + + var isEventDefinitionEqual = target.eventDefinition == eventDefinition, + isEventTypeEqual = businessObject.$type == target.type, + isInterruptingEqual = businessObject.cancelActivity == cancelActivity; return ((!isEventDefinitionEqual && isEventTypeEqual) || !isEventTypeEqual) || - !(isEventDefinitionEqual && isEventTypeEqual); + !(isEventDefinitionEqual && isEventTypeEqual && isInterruptingEqual); } diff --git a/lib/features/replace/ReplaceOptions.js b/lib/features/replace/ReplaceOptions.js index 91ef92cf..d03f4bac 100644 --- a/lib/features/replace/ReplaceOptions.js +++ b/lib/features/replace/ReplaceOptions.js @@ -90,7 +90,7 @@ module.exports.INTERMEDIATE_EVENT = [ }, { label: 'Message Intermediate Catch Event', - actionName: 'replace-with-intermediate-catch', + actionName: 'replace-with-message-intermediate-catch', className: 'icon-intermediate-event-catch-message', target: { type: 'bpmn:IntermediateCatchEvent', @@ -99,7 +99,7 @@ module.exports.INTERMEDIATE_EVENT = [ }, { label: 'Message Intermediate Throw Event', - actionName: 'replace-with-intermediate-throw', + actionName: 'replace-with-message-intermediate-throw', className: 'icon-intermediate-event-throw-message', target: { type: 'bpmn:IntermediateThrowEvent', @@ -117,7 +117,7 @@ module.exports.INTERMEDIATE_EVENT = [ }, { label: 'Escalation Intermediate Catch Event', - actionName: 'replace-with-escalation-catch', + actionName: 'replace-with-escalation-intermediate-catch', className: 'icon-intermediate-event-catch-escalation', target: { type: 'bpmn:IntermediateCatchEvent', @@ -161,8 +161,8 @@ module.exports.INTERMEDIATE_EVENT = [ } }, { - label: 'Signal Throw Catch Event', - actionName: 'replace-with-throw-intermediate-catch', + label: 'Signal Intermediate Catch Event', + actionName: 'replace-with-signal-intermediate-catch', className: 'icon-intermediate-event-catch-signal', target: { type: 'bpmn:IntermediateCatchEvent', @@ -444,3 +444,110 @@ module.exports.TASK = [ } } ]; + +module.exports.BOUNDARY_EVENT = [ + { + label: 'Message Boundary Event', + actionName: 'replace-with-message-boundary', + className: 'icon-intermediate-event-catch-message', + target: { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:MessageEventDefinition' + } + }, + { + label: 'Timer Boundary Event', + actionName: 'replace-with-timer-boundary', + className: 'icon-intermediate-event-catch-timer', + target: { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:TimerEventDefinition' + } + }, + { + label: 'Escalation Boundary Event', + actionName: 'replace-with-escalation-boundary', + className: 'icon-intermediate-event-catch-escalation', + target: { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:EscalationEventDefinition' + } + }, + { + label: 'Conditional Boundary Event', + actionName: 'replace-with-conditional-boundary', + className: 'icon-intermediate-event-catch-condition', + target: { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:ConditionalEventDefinition' + } + }, + { + label: 'Error Boundary Event', + actionName: 'replace-with-error-boundary', + className: 'icon-intermediate-event-catch-error', + target: { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:ErrorEventDefinition' + } + }, + { + label: 'Signal Boundary Event', + actionName: 'replace-with-signal-boundary', + className: 'icon-intermediate-event-catch-signal', + target: { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:SignalEventDefinition' + } + }, + { + label: 'Message Boundary Event (non-interrupting)', + actionName: 'replace-with-non-interrupting-message-boundary', + className: 'icon-intermediate-event-catch-non-interrupting-message', + target: { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:MessageEventDefinition', + cancelActivity: false + } + }, + { + label: 'Timer Boundary Event (non-interrupting)', + actionName: 'replace-with-non-interrupting-timer-boundary', + className: 'icon-intermediate-event-catch-non-interrupting-timer', + target: { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:TimerEventDefinition', + cancelActivity: false + } + }, + { + label: 'Escalation Boundary Event (non-interrupting)', + actionName: 'replace-with-non-interrupting-escalation-boundary', + className: 'icon-intermediate-event-catch-non-interrupting-escalation', + target: { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:EscalationEventDefinition', + cancelActivity: false + } + }, + { + label: 'Conditional Boundary Event (non-interrupting)', + actionName: 'replace-with-non-interrupting-conditional-boundary', + className: 'icon-intermediate-event-catch-non-interrupting-condition', + target: { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:ConditionalEventDefinition', + cancelActivity: false + } + }, + { + label: 'Signal Boundary Event (non-interrupting)', + actionName: 'replace-with-non-interrupting-signal-boundary', + className: 'icon-intermediate-event-catch-non-interrupting-signal', + target: { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:SignalEventDefinition', + cancelActivity: false + } + }, +]; diff --git a/test/fixtures/bpmn/draw/activity-markers-simple.bpmn b/test/fixtures/bpmn/draw/activity-markers-simple.bpmn index 444714c1..8fb5d1e7 100644 --- a/test/fixtures/bpmn/draw/activity-markers-simple.bpmn +++ b/test/fixtures/bpmn/draw/activity-markers-simple.bpmn @@ -14,6 +14,12 @@ + + + + + + @@ -38,6 +44,18 @@ + + + + + + + + + + + + diff --git a/test/fixtures/bpmn/features/replace/01_replace.bpmn b/test/fixtures/bpmn/features/replace/01_replace.bpmn index 711df8ec..9643d6dd 100644 --- a/test/fixtures/bpmn/features/replace/01_replace.bpmn +++ b/test/fixtures/bpmn/features/replace/01_replace.bpmn @@ -31,6 +31,12 @@ + + + + + + @@ -107,6 +113,18 @@ + + + + + + + + + + + + diff --git a/test/spec/features/popup-menu/PopupMenuSpec.js b/test/spec/features/popup-menu/PopupMenuSpec.js index 65de7eeb..2c449635 100644 --- a/test/spec/features/popup-menu/PopupMenuSpec.js +++ b/test/spec/features/popup-menu/PopupMenuSpec.js @@ -514,7 +514,7 @@ describe('features/popup-menu', function() { var entry = queryEntry(popupMenu, 'replace-with-subprocess'); // when - // replacing the expanded sub process with a transaction + // replacing the transaction with an expanded sub process var subProcess = popupMenu.trigger(Events.create(entry, { x: 0, y: 0 })); // then @@ -523,4 +523,41 @@ describe('features/popup-menu', function() { }); + describe('replace menu', function() { + + it('should contain all boundary events for an interrupting boundary event', + inject(function(popupMenu, bpmnReplace, elementRegistry) { + + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'); + + // when + openPopup(boundaryEvent, 40); + + var entriesContainer = queryPopup(popupMenu, '.djs-popup-body'); + + // then + expect(entriesContainer.childNodes.length).to.equal(10); + expect(queryEntry(popupMenu, 'replace-with-message-intermediate-catch')).to.be.null; + })); + + + it('should contain all boundary events for a non interrupting boundary event', + inject(function(popupMenu, bpmnReplace, elementRegistry) { + + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_2'); + + // when + openPopup(boundaryEvent, 40); + + var entriesContainer = queryPopup(popupMenu, '.djs-popup-body'); + + // then + expect(entriesContainer.childNodes.length).to.equal(10); + expect(queryEntry(popupMenu, 'replace-with-non-interrupting-message-intermediate-catch')).to.be.null; + })); + + }); + }); diff --git a/test/spec/features/replace/BpmnReplaceSpec.js b/test/spec/features/replace/BpmnReplaceSpec.js index 30f06d1c..5d319d74 100644 --- a/test/spec/features/replace/BpmnReplaceSpec.js +++ b/test/spec/features/replace/BpmnReplaceSpec.js @@ -82,19 +82,85 @@ describe('features/replace', function() { it('transaction', inject(function(elementRegistry, modeling, bpmnReplace, canvas) { + // given var transaction = elementRegistry.get('Transaction_1'), newElementData = { type: 'bpmn:SubProcess', isExpanded: true }; + // when var newElement = bpmnReplace.replaceElement(transaction, newElementData); + // then expect(newElement).to.be.defined; expect(is(newElement.businessObject, 'bpmn:SubProcess')).to.be.true; })); + + it('non interrupting boundary event by interrupting boundary event', + inject(function(elementRegistry, modeling, bpmnReplace, canvas) { + + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + newElementData = { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:EscalationEventDefinition' + }; + + // when + var newElement = bpmnReplace.replaceElement(boundaryEvent, newElementData); + + // then + expect(newElement).to.be.defined; + expect(is(newElement.businessObject, 'bpmn:BoundaryEvent')).to.be.true; + expect(newElement.businessObject.eventDefinitions[0].$type).to.equal('bpmn:EscalationEventDefinition'); + expect(newElement.businessObject.cancelActivity).to.be.true; + })); + + + it('interrupting boundary event by non interrupting boundary event', + inject(function(elementRegistry, modeling, bpmnReplace, canvas) { + + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_2'), + newElementData = { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:SignalEventDefinition', + cancelActivity: false + }; + + // when + var newElement = bpmnReplace.replaceElement(boundaryEvent, newElementData); + + // then + expect(newElement).to.be.defined; + expect(is(newElement.businessObject, 'bpmn:BoundaryEvent')).to.be.true; + expect(newElement.businessObject.eventDefinitions[0].$type).to.equal('bpmn:SignalEventDefinition'); + expect(newElement.businessObject.cancelActivity).to.be.false; + })); + + + it('boundary event and update host', + inject(function(elementRegistry, modeling, bpmnReplace, canvas) { + + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent_1'), + host = elementRegistry.get('Task_1'), + newElementData = { + type: 'bpmn:BoundaryEvent', + eventDefinition: 'bpmn:ErrorEventDefinition', + }; + + // when + var newElement = bpmnReplace.replaceElement(boundaryEvent, newElementData); + + // then + expect(newElement.host).to.be.defined; + expect(newElement.host).to.eql(host); + })); + });