diff --git a/lib/features/modeling/behavior/EventBasedGatewayBehavior.js b/lib/features/modeling/behavior/EventBasedGatewayBehavior.js new file mode 100644 index 00000000..53b291fc --- /dev/null +++ b/lib/features/modeling/behavior/EventBasedGatewayBehavior.js @@ -0,0 +1,84 @@ +import inherits from 'inherits'; + +import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; + +import { is } from '../../../util/ModelUtil'; + +export default function EventBasedGatewayBehavior(eventBus, modeling) { + + CommandInterceptor.call(this, eventBus); + + /** + * Remove existing sequence flows of event-based target before connecting + * from event-based gateway. + */ + this.preExecuted('connection.create', function(event) { + + var source = event.context.source, + target = event.context.target, + existingIncomingConnections = target.incoming.slice(); + + if ( + is(source, 'bpmn:EventBasedGateway') && + target.incoming.length + ) { + + existingIncomingConnections + .filter(isSequenceFlow) + .forEach(function(sequenceFlow) { + modeling.removeConnection(sequenceFlow); + }); + } + }); + + /** + * After replacing shape with event-based gateway, remove incoming sequence + * flows of event-based targets which do not belong to event-based gateway + * source. + */ + this.preExecuted('shape.replace', function(event) { + + var newShape = event.context.newShape, + newShapeTargets, + newShapeTargetsIncomingSequenceFlows = []; + + if (!is(newShape, 'bpmn:EventBasedGateway')) { + return; + } + + newShapeTargets = newShape.outgoing + .filter(isSequenceFlow) + .map(function(sequenceFlow) { + return sequenceFlow.target; + }); + + newShapeTargets + .forEach(function(target) { + target.incoming + .filter(isSequenceFlow) + .forEach(function(sequenceFlow) { + newShapeTargetsIncomingSequenceFlows.push(sequenceFlow); + }); + }); + + newShapeTargetsIncomingSequenceFlows + .forEach(function(sequenceFlow) { + if (sequenceFlow.source !== newShape) { + modeling.removeConnection(sequenceFlow); + } + }); + }); +} + +EventBasedGatewayBehavior.$inject = [ + 'eventBus', + 'modeling' +]; + +inherits(EventBasedGatewayBehavior, CommandInterceptor); + +// helpers ////////////////////// + +function isSequenceFlow(connection) { + return is(connection, 'bpmn:SequenceFlow'); +} diff --git a/lib/features/modeling/behavior/index.js b/lib/features/modeling/behavior/index.js index 2784cc8d..8c68ef47 100644 --- a/lib/features/modeling/behavior/index.js +++ b/lib/features/modeling/behavior/index.js @@ -9,6 +9,7 @@ import DataInputAssociationBehavior from './DataInputAssociationBehavior'; import DataStoreBehavior from './DataStoreBehavior'; import DeleteLaneBehavior from './DeleteLaneBehavior'; import DropOnFlowBehavior from './DropOnFlowBehavior'; +import EventBasedGatewayBehavior from './EventBasedGatewayBehavior'; import ImportDockingFix from './ImportDockingFix'; import IsHorizontalFix from './IsHorizontalFix'; import LabelBehavior from './LabelBehavior'; @@ -36,6 +37,7 @@ export default { 'dataInputAssociationBehavior', 'deleteLaneBehavior', 'dropOnFlowBehavior', + 'eventBasedGatewayBehavior', 'importDockingFix', 'isHorizontalFix', 'labelBehavior', @@ -61,6 +63,7 @@ export default { dataStoreBehavior: [ 'type', DataStoreBehavior ], deleteLaneBehavior: [ 'type', DeleteLaneBehavior ], dropOnFlowBehavior: [ 'type', DropOnFlowBehavior ], + eventBasedGatewayBehavior: [ 'type', EventBasedGatewayBehavior ], importDockingFix: [ 'type', ImportDockingFix ], isHorizontalFix: [ 'type', IsHorizontalFix ], labelBehavior: [ 'type', LabelBehavior ], diff --git a/test/spec/features/modeling/behavior/EventBasedGatewayBehavior.bpmn b/test/spec/features/modeling/behavior/EventBasedGatewayBehavior.bpmn new file mode 100644 index 00000000..0f001bcb --- /dev/null +++ b/test/spec/features/modeling/behavior/EventBasedGatewayBehavior.bpmn @@ -0,0 +1,265 @@ + + + + + + + + + + SequenceFlow_05gdptn + SequenceFlow_1u422yl + + + SequenceFlow_05gdptn + SequenceFlow_18qhwmj + + + + SequenceFlow_1u422yl + SequenceFlow_00erwwx + + + + SequenceFlow_18qhwmj + + + SequenceFlow_00erwwx + + + SequenceFlow_1cfekdr + SequenceFlow_Existing + + + SequenceFlow_Existing + SequenceFlow_1c5mx3r + + + + + + SequenceFlow_1c5mx3r + + + SequenceFlow_1cfekdr + SequenceFlow_16zmjey + + + + SequenceFlow_16zmjey + + + + SequenceFlow_ExistingA + + + SequenceFlow_ExistingA + SequenceFlow_ExistingB + + + SequenceFlow_ExistingB + + + SequenceFlow_A + SequenceFlow_1cnfp7p + SequenceFlow_1pcgnez + + + SequenceFlow_B + SequenceFlow_0e8x81p + SequenceFlow_0zasly0 + + + SequenceFlow_1pcgnez + + + SequenceFlow_1cnfp7p + + + SequenceFlow_0zasly0 + + + SequenceFlow_0e8x81p + + + SequenceFlow_B + SequenceFlow_A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/behavior/EventBasedGatewayBehaviorSpec.js b/test/spec/features/modeling/behavior/EventBasedGatewayBehaviorSpec.js new file mode 100644 index 00000000..7e8107c8 --- /dev/null +++ b/test/spec/features/modeling/behavior/EventBasedGatewayBehaviorSpec.js @@ -0,0 +1,109 @@ +import { + bootstrapModeler, + inject +} from 'test/TestHelper'; + +import coreModule from 'lib/core'; +import modelingModule from 'lib/features/modeling'; +import replaceModule from 'lib/features/replace'; + +describe('features/modeling/behavior - event-based gateway', function() { + + var diagramXML = require('./EventBasedGatewayBehavior.bpmn'); + + describe('when connecting from event-based gateway', function() { + + beforeEach(bootstrapModeler( + diagramXML, { + modules: [ + coreModule, + modelingModule + ] + } + )); + + + it('should remove single existing sequence flow', inject( + function(elementRegistry, modeling) { + + // given + var eventBasedGateway = elementRegistry.get('EventBasedGateway_A'), + intermediateEvent = elementRegistry.get('IntermediateCatchEvent'), + existingConnection = elementRegistry.get('SequenceFlow_Existing'), + newConnection; + + // when + newConnection = modeling.connect(eventBasedGateway, intermediateEvent, { + type: 'bpmn:SequenceFlow' + }); + + // then + expect(elementRegistry.get(existingConnection.id)).not.to.exist; + expect(elementRegistry.get(newConnection.id)).to.exist; + } + )); + + + it('should remove multiple existing sequence flows', inject( + function(elementRegistry, modeling) { + + // given + var eventBasedGateway = elementRegistry.get('EventBasedGateway_C'), + receiveTask = elementRegistry.get('ReceiveTask_A'), + existingSequenceFlowA = elementRegistry.get('SequenceFlow_ExistingA'), + existingSequenceFlowB = elementRegistry.get('SequenceFlow_ExistingB'), + existingMessageFlow = elementRegistry.get('MessageFlow_Existing'), + newSequenceFlow; + + // when + newSequenceFlow = modeling.connect(eventBasedGateway, receiveTask, { + type: 'bpmn:SequenceFlow' + }); + + // then + expect(elementRegistry.get(existingSequenceFlowA.id)).to.not.exist; + expect(elementRegistry.get(existingSequenceFlowB.id)).to.not.exist; + expect(elementRegistry.get(existingMessageFlow.id)).to.exist; + expect(elementRegistry.get(newSequenceFlow.id)).to.exist; + } + )); + + }); + + + describe('when replacing with event-based gateway', function() { + + beforeEach(bootstrapModeler( + diagramXML, { + modules: [ + coreModule, + modelingModule, + replaceModule + ] + } + )); + + + it('should remove non-event-based incoming sequence flows of event-based targets', + inject(function(elementRegistry, bpmnReplace) { + + // given + var gatewayA = elementRegistry.get('ExclusiveGateway_B'), + receiveTaskA = elementRegistry.get('ReceiveTask_B'), + receiveTaskB = elementRegistry.get('ReceiveTask_C'); + + // when + bpmnReplace.replaceElement(gatewayA, { type: 'bpmn:EventBasedGateway' }); + + // then + expect(elementRegistry.get('SequenceFlow_A')).to.exist; + expect(receiveTaskA.incoming.length).to.equal(1); + + expect(elementRegistry.get('SequenceFlow_B')).to.exist; + expect(receiveTaskB.incoming.length).to.equal(1); + }) + ); + + }); + +});