feat(replace): update bpmn when reconnecting flows

When reconnecting the source and end waypoints of a conditional
or default flow, the bpmn tree should be updated to reflect
these changes.

Closes #373
This commit is contained in:
Ricardo Matias 2015-10-06 10:50:47 +02:00 committed by pedesen
parent 188487ebdc
commit 534061d821
3 changed files with 324 additions and 8 deletions

View File

@ -127,7 +127,7 @@ function BpmnUpdater(eventBus, bpmnFactory, connectionDocking) {
// attach / detach connection // attach / detach connection
function updateConnection(e) { function updateConnection(e) {
self.updateConnection(e.context.connection); self.updateConnection(e.context);
} }
this.executed([ this.executed([
@ -168,6 +168,71 @@ function BpmnUpdater(eventBus, bpmnFactory, connectionDocking) {
'connection.reconnectStart' 'connection.reconnectStart'
], ifBpmn(updateConnectionWaypoints)); ], ifBpmn(updateConnectionWaypoints));
// update Default & Conditional flows
this.executed([
'connection.reconnectEnd',
'connection.reconnectStart'
], ifBpmn(function(e) {
var context = e.context,
connection = context.connection,
businessObject = getBusinessObject(connection),
oldSource = getBusinessObject(context.oldSource),
oldTarget = getBusinessObject(context.oldTarget),
newSource = getBusinessObject(connection.source),
newTarget = getBusinessObject(connection.target);
if (oldSource === newSource || oldTarget === newTarget) {
return;
}
// on reconnectStart -> default flow
if (oldSource && oldSource.default) {
context.default = oldSource.default;
oldSource.default = undefined;
}
// on reconnectEnd -> default flow
if ((businessObject.sourceRef && businessObject.sourceRef.default) && !is(newTarget, 'bpmn:Activity')) {
context.default = businessObject.sourceRef.default;
businessObject.sourceRef.default = undefined;
}
// on reconnectStart -> condtional flow
if ((businessObject.conditionExpression) && is(oldSource, 'bpmn:Activity')) {
context.conditionExpression = businessObject.conditionExpression;
businessObject.conditionExpression = undefined;
}
// on reconnectEnd -> condtional flow
if ((businessObject.conditionExpression) && !is(newTarget, 'bpmn:Activity')) {
context.conditionExpression = businessObject.conditionExpression;
businessObject.conditionExpression = undefined;
}
}));
this.reverted([
'connection.reconnectEnd',
'connection.reconnectStart'
], ifBpmn(function(e) {
var context = e.context,
connection = context.connection,
businessObject = getBusinessObject(connection),
newSource = getBusinessObject(connection.source);
// default flow
if (context.default) {
if (is(newSource, 'bpmn:ExclusiveGateway') || is(newSource, 'bpmn:InclusiveGateway')) {
newSource.default = context.default;
}
}
// conditional flow
if (context.conditionExpression && is(newSource, 'bpmn:Activity')) {
businessObject.conditionExpression = context.conditionExpression;
}
}));
// update attachments // update attachments
function updateAttachment(e) { function updateAttachment(e) {
self.updateAttachment(e.context); self.updateAttachment(e.context);
@ -428,9 +493,10 @@ BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) {
}; };
BpmnUpdater.prototype.updateConnection = function(connection) { BpmnUpdater.prototype.updateConnection = function(context) {
var businessObject = getBusinessObject(connection), var connection = context.connection,
businessObject = getBusinessObject(connection),
newSource = getBusinessObject(connection.source), newSource = getBusinessObject(connection.source),
newTarget = getBusinessObject(connection.target); newTarget = getBusinessObject(connection.target);

View File

@ -102,6 +102,13 @@ function BpmnReplace(bpmnFactory, moddle, popupMenu, replace, selection, modelin
businessObject.loopCharacteristics = oldBusinessObject.loopCharacteristics; businessObject.loopCharacteristics = oldBusinessObject.loopCharacteristics;
} }
// retain default flow's reference between inclusive and exclusive gateways
if ((is(oldBusinessObject, 'bpmn:ExclusiveGateway') || is(oldBusinessObject, 'bpmn:InclusiveGateway')) &&
(is(businessObject, 'bpmn:ExclusiveGateway') || is(businessObject, 'bpmn:InclusiveGateway')))
{
businessObject.default = oldBusinessObject.default;
}
newElement = replace.replaceElement(element, newElement); newElement = replace.replaceElement(element, newElement);
if (hints.select !== false) { if (hints.select !== false) {

View File

@ -9,8 +9,6 @@ var modelingModule = require('../../../../lib/features/modeling'),
moveModule = require('diagram-js/lib/features/move'), moveModule = require('diagram-js/lib/features/move'),
coreModule = require('../../../../lib/core'); coreModule = require('../../../../lib/core');
var canvasEvent = require('../../../util/MockEvents').createCanvasEvent;
var is = require('../../../../lib/util/ModelUtil').is, var is = require('../../../../lib/util/ModelUtil').is,
isExpanded = require('../../../../lib/util/DiUtil').isExpanded, isExpanded = require('../../../../lib/util/DiUtil').isExpanded,
isInterrupting = require('../../../../lib/util/DiUtil').isInterrupting, isInterrupting = require('../../../../lib/util/DiUtil').isInterrupting,
@ -975,7 +973,8 @@ describe('features/replace', function() {
})); }));
it('should replace SequenceFlow with DefaultFlow -> undo', inject(function(elementRegistry, bpmnReplace, commandStack) { it('should replace SequenceFlow with DefaultFlow -> undo',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given // given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'); var sequenceFlow = elementRegistry.get('SequenceFlow_3');
@ -1013,6 +1012,152 @@ describe('features/replace', function() {
expect(gateway.businessObject.default).to.equal(sequenceFlow.businessObject); expect(gateway.businessObject.default).to.equal(sequenceFlow.businessObject);
})); }));
it('should replace DefaultFlow with SequenceFlow when changing source',
inject(function(elementRegistry, bpmnReplace, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
task = elementRegistry.get('Task_2');
var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow);
// trigger DefaultFlow replacement
sequenceFlowOpts[0].action();
// when
modeling.reconnectStart(sequenceFlow, task, [
{ x: 686, y: 267, original: { x: 686, y: 307 } },
{ x: 686, y: 207, original: { x: 686, y: 187 } }
]);
var gateway = elementRegistry.get('ExclusiveGateway_1');
// then
expect(gateway.businessObject.default).to.not.exist;
}));
it('should replace DefaultFlow with SequenceFlow when changing source -> undo',
inject(function(elementRegistry, bpmnReplace, modeling, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
task = elementRegistry.get('Task_2');
var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow);
// trigger DefaultFlow replacement
sequenceFlowOpts[0].action();
// when
modeling.reconnectStart(sequenceFlow, task, [
{ x: 686, y: 267, original: { x: 686, y: 307 } },
{ x: 686, y: 207, original: { x: 686, y: 187 } }
]);
commandStack.undo();
var gateway = elementRegistry.get('ExclusiveGateway_1');
// then
expect(gateway.businessObject.default).equal(sequenceFlow.businessObject);
}));
it('should replace DefaultFlow with SequenceFlow when changing target',
inject(function(elementRegistry, elementFactory, canvas, bpmnReplace, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
root = canvas.getRootElement();
var intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent'});
modeling.createShape(intermediateEvent, { x: 686, y: 50 }, root);
var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow);
// trigger DefaultFlow replacement
sequenceFlowOpts[0].action();
// when
modeling.reconnectEnd(sequenceFlow, intermediateEvent, [
{ x: 686, y: 267, original: { x: 686, y: 307 } },
{ x: 686, y: 50, original: { x: 686, y: 75 } }
]);
var gateway = elementRegistry.get('ExclusiveGateway_1');
// then
expect(gateway.businessObject.default).to.not.exist;
}));
it('should replace DefaultFlow with SequenceFlow when changing target -> undo',
inject(function(elementRegistry, elementFactory, canvas, bpmnReplace, modeling, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1'),
root = canvas.getRootElement();
var intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent'});
modeling.createShape(intermediateEvent, { x: 686, y: 50 }, root);
var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow);
// trigger DefaultFlow replacement
sequenceFlowOpts[0].action();
// when
modeling.reconnectEnd(sequenceFlow, intermediateEvent, [
{ x: 686, y: 267, original: { x: 686, y: 307 } },
{ x: 686, y: 50, original: { x: 686, y: 75 } }
]);
commandStack.undo();
var gateway = elementRegistry.get('ExclusiveGateway_1');
// then
expect(gateway.businessObject.default).equal(sequenceFlow.businessObject);
}));
it('should keep DefaultFlow when morphing Gateway', inject(function(elementRegistry, bpmnReplace) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
exclusiveGateway = elementRegistry.get('ExclusiveGateway_1');
// when
var opts = bpmnReplace.getReplaceOptions(sequenceFlow);
// trigger DefaultFlow replacement
opts[0].action();
var inclusiveGateway = bpmnReplace.replaceElement(exclusiveGateway, { type: 'bpmn:InclusiveGateway'});
// then
expect(inclusiveGateway.businessObject.default).to.equal(sequenceFlow.businessObject);
}));
it('should keep DefaultFlow when morphing Gateway -> undo',
inject(function(elementRegistry, bpmnReplace, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
exclusiveGateway = elementRegistry.get('ExclusiveGateway_1');
// when
var opts = bpmnReplace.getReplaceOptions(sequenceFlow);
// trigger DefaultFlow replacement
opts[0].action();
bpmnReplace.replaceElement(exclusiveGateway, { type: 'bpmn:InclusiveGateway'});
commandStack.undo();
// then
expect(exclusiveGateway.businessObject.default).to.equal(sequenceFlow.businessObject);
}));
}); });
@ -1098,6 +1243,104 @@ describe('features/replace', function() {
expect(sequenceFlow.businessObject.conditionExpression).to.not.exist; expect(sequenceFlow.businessObject.conditionExpression).to.not.exist;
})); }));
it('should replace ConditionalFlow with SequenceFlow when changing source',
inject(function(elementRegistry, bpmnReplace, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
startEvent = elementRegistry.get('StartEvent_1');
var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow);
// trigger ConditionalFlow replacement
sequenceFlowOpts[0].action();
// when
modeling.reconnectStart(sequenceFlow, startEvent, [
{ x: 196, y: 197, original: { x: 178, y: 197 } },
{ x: 497, y: 278, original: { x: 547, y: 278 } }
]);
// then
expect(sequenceFlow.businessObject.conditionExpression).to.not.exist;
}));
it('should replace ConditionalFlow with SequenceFlow when changing source -> undo',
inject(function(elementRegistry, bpmnReplace, modeling, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
startEvent = elementRegistry.get('StartEvent_1');
var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow);
// trigger ConditionalFlow replacement
sequenceFlowOpts[0].action();
// when
modeling.reconnectStart(sequenceFlow, startEvent, [
{ x: 196, y: 197, original: { x: 178, y: 197 } },
{ x: 497, y: 278, original: { x: 547, y: 278 } }
]);
commandStack.undo();
// then
expect(sequenceFlow.businessObject.conditionExpression.$type).to.equal('bpmn:FormalExpression');
}));
it('should replace ConditionalFlow with SequenceFlow when changing target',
inject(function(elementRegistry, elementFactory, canvas, bpmnReplace, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
root = canvas.getRootElement(),
intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent'});
modeling.createShape(intermediateEvent, { x: 497, y: 197 }, root);
var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow);
// trigger ConditionalFlow replacement
sequenceFlowOpts[0].action();
// when
modeling.reconnectEnd(sequenceFlow, intermediateEvent, [
{ x: 389, y: 197, original: { x: 389, y: 197 } },
{ x: 497, y: 197, original: { x: 497, y: 197 } }
]);
// then
expect(sequenceFlow.businessObject.conditionExpression).to.not.exist;
}));
it('should replace ConditionalFlow with SequenceFlow when changing target -> undo',
inject(function(elementRegistry, elementFactory, canvas, bpmnReplace, modeling, commandStack) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_3'),
root = canvas.getRootElement(),
intermediateEvent = elementFactory.createShape({ type: 'bpmn:IntermediateThrowEvent'});
modeling.createShape(intermediateEvent, { x: 497, y: 197 }, root);
var sequenceFlowOpts = bpmnReplace.getReplaceOptions(sequenceFlow);
// trigger ConditionalFlow replacement
sequenceFlowOpts[0].action();
// when
modeling.reconnectEnd(sequenceFlow, intermediateEvent, [
{ x: 389, y: 197, original: { x: 389, y: 197 } },
{ x: 497, y: 197, original: { x: 497, y: 197 } }
]);
commandStack.undo();
// then
expect(sequenceFlow.businessObject.conditionExpression.$type).to.equal('bpmn:FormalExpression');
}));
}); });
}); });