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:
parent
188487ebdc
commit
534061d821
|
@ -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);
|
||||||
|
|
||||||
|
@ -492,4 +558,4 @@ function ifBpmn(fn) {
|
||||||
fn(event);
|
fn(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -375,7 +382,7 @@ function BpmnReplace(bpmnFactory, moddle, popupMenu, replace, selection, modelin
|
||||||
// Add entries to replace menu
|
// Add entries to replace menu
|
||||||
forEach(filteredEntries, function(definition) {
|
forEach(filteredEntries, function(definition) {
|
||||||
var entry = addMenuEntry(definition);
|
var entry = addMenuEntry(definition);
|
||||||
|
|
||||||
menuEntries.push(entry);
|
menuEntries.push(entry);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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');
|
||||||
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue