fix(copy-paste): ignore data associations during cloning

* use bpmnFactory for cloning to ensure all relevant
  elements have actual IDs
* don't copy dataAssociations, as they are visual elements
  that will be created during element re-connection

NOTE: This fixes data input association not properly being
wired during target replace, too.

Closes #694, #693
This commit is contained in:
Nico Rehwaldt 2017-12-07 23:12:00 +01:00 committed by Philipp Fromme
parent 25d5fb2967
commit cd24b27768
8 changed files with 147 additions and 38 deletions

View File

@ -34,7 +34,7 @@ function BpmnCopyPaste(
bpmnFactory, eventBus, copyPaste,
clipboard, canvas, bpmnRules) {
var helper = new ModelCloneHelper(eventBus);
var helper = new ModelCloneHelper(eventBus, bpmnFactory);
copyPaste.registerDescriptor(function(element, descriptor) {
var businessObject = descriptor.oldBusinessObject = getBusinessObject(element);

View File

@ -56,7 +56,7 @@ function toggeling(element, target) {
*/
function BpmnReplace(bpmnFactory, replace, selection, modeling, eventBus) {
var helper = new ModelCloneHelper(eventBus);
var helper = new ModelCloneHelper(eventBus, bpmnFactory);
/**
* Prepares a new business object for the replacement element

View File

@ -29,8 +29,9 @@ function isType(element, types) {
* A bpmn properties cloning interface
*
*/
function ModelCloneHelper(eventBus) {
function ModelCloneHelper(eventBus, bpmnFactory) {
this._eventBus = eventBus;
this._bpmnFactory = bpmnFactory;
}
module.exports = ModelCloneHelper;
@ -60,8 +61,11 @@ ModelCloneHelper.prototype.clone = function(refElement, newElement, properties)
// - same values from simple types
// - cloning id's
// - cloning reference elements
if (newElementProp === refElementProp ||
(propDescriptor && (propDescriptor.isId || propDescriptor.isReference))) {
if (newElementProp === refElementProp) {
return;
}
if (propDescriptor && (propDescriptor.isId || propDescriptor.isReference)) {
return;
}
@ -110,8 +114,9 @@ ModelCloneHelper.prototype.clone = function(refElement, newElement, properties)
ModelCloneHelper.prototype._deepClone = function _deepClone(propertyElement, context) {
var eventBus = this._eventBus;
var bpmnFactory = this._bpmnFactory;
var newProp = propertyElement.$model.create(propertyElement.$type);
var newProp = bpmnFactory.create(propertyElement.$type);
var properties = filter(Object.keys(propertyElement), function(prop) {
var descriptor = newProp.$model.getPropertyDescriptor(newProp, prop);

View File

@ -13,7 +13,9 @@ module.exports.IGNORED_PROPERTIES = [
'outgoing',
'artifacts',
'default',
'flowElements'
'flowElements',
'dataInputAssociations',
'dataOutputAssociations'
];

View File

@ -523,9 +523,15 @@ describe('features/copy-paste', function() {
describe('integration', function() {
it('multiple participants', inject(integrationTest([ 'Participant_0pgdgt4', 'Participant_1id96b4' ])));
it('multiple participants', inject(integrationTest([
'Participant_0pgdgt4',
'Participant_1id96b4'
])));
it('multiple participants', inject(integrationTest([ 'Participant_0pgdgt4', 'Participant_1id96b4' ])));
it('multiple participants', inject(integrationTest([
'Participant_0pgdgt4',
'Participant_1id96b4'
])));
});
@ -537,42 +543,44 @@ describe('features/copy-paste', function() {
beforeEach(bootstrapModeler(collaborationAssociations, { modules: testModules }));
it('copying participant should copy process as well', inject(function(elementRegistry, copyPaste, canvas) {
it('copying participant should copy process as well', inject(
function(elementRegistry, copyPaste, canvas) {
// given
var participants = map([ 'Participant_Input', 'Participant_Output' ], function(e) {
return elementRegistry.get(e);
});
var rootElement = canvas.getRootElement();
// given
var participants = map([ 'Participant_Input', 'Participant_Output' ], function(e) {
return elementRegistry.get(e);
});
var rootElement = canvas.getRootElement();
// when
copyPaste.copy(participants);
// when
copyPaste.copy(participants);
copyPaste.paste({
element: rootElement,
point: {
x: 4000,
y: 4500
}
});
copyPaste.paste({
element: rootElement,
point: {
x: 4000,
y: 4500
}
});
// then
var elements = elementRegistry.filter(function(element) {
return element.type === 'bpmn:Participant';
});
// then
var elements = elementRegistry.filter(function(element) {
return element.type === 'bpmn:Participant';
});
var processIds = map(elements, function(e) {
return e.businessObject.processRef.id;
});
var processIds = map(elements, function(e) {
return e.businessObject.processRef.id;
});
expect(uniq(processIds)).to.have.length(4);
}));
expect(uniq(processIds)).to.have.length(4);
}
));
it('participant with OutputDataAssociation', inject(integrationTest([ 'Participant_Output' ])));
it('participant with DataOutputAssociation', inject(integrationTest([ 'Participant_Output' ])));
it('participant with InputDataAssociation', inject(integrationTest([ 'Participant_Input' ])));
it('participant with DataInputAssociation', inject(integrationTest([ 'Participant_Input' ])));
});

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:task id="Task">
<bpmn:property id="TaskPlaceholderProperty" name="__targetRef_placeholder" />
<bpmn:dataInputAssociation id="DataInputAssociation">
<bpmn:sourceRef>DataObjectReference_IN</bpmn:sourceRef>
<bpmn:targetRef>TaskPlaceholderProperty</bpmn:targetRef>
</bpmn:dataInputAssociation>
<bpmn:dataOutputAssociation id="DataOutputAssociation_1by9zp9">
<bpmn:targetRef>DataObjectReference_OUT</bpmn:targetRef>
</bpmn:dataOutputAssociation>
</bpmn:task>
<bpmn:dataObjectReference id="DataObjectReference_IN" dataObjectRef="DataObject_1l9lukc" />
<bpmn:dataObject id="DataObject_1l9lukc" />
<bpmn:dataObjectReference id="DataObjectReference_OUT" dataObjectRef="DataObject_03twvyv" />
<bpmn:dataObject id="DataObject_03twvyv" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="Task_di" bpmnElement="Task">
<dc:Bounds x="132" y="150" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_IN_di" bpmnElement="DataObjectReference_IN">
<dc:Bounds x="62" y="33" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="80" y="87" width="0" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="DataInputAssociation_di" bpmnElement="DataInputAssociation">
<di:waypoint xsi:type="dc:Point" x="98" y="81" />
<di:waypoint xsi:type="dc:Point" x="152" y="150" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="DataObjectReference_OUT_di" bpmnElement="DataObjectReference_OUT">
<dc:Bounds x="232" y="33" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="250" y="87" width="0" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="DataOutputAssociation_1by9zp9_di" bpmnElement="DataOutputAssociation_1by9zp9">
<di:waypoint xsi:type="dc:Point" x="207" y="150" />
<di:waypoint xsi:type="dc:Point" x="248" y="83" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -236,6 +236,48 @@ describe('features/replace - bpmn replace', function() {
});
describe('should replace with data objects', function() {
var diagramXML = require('./BpmnReplace.dataObjects.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
it('restoring dataAssociations', inject(function(elementRegistry, bpmnReplace) {
// given
var task = elementRegistry.get('Task');
// when
var serviceTask = bpmnReplace.replaceElement(task, { type: 'bpmn:ServiceTask' });
var bo = serviceTask.businessObject;
// then
// expect one incoming connection
expect(serviceTask.incoming).to.have.length(1);
var inputAssociations = bo.dataInputAssociations;
expect(inputAssociations).to.have.length(1);
var inputAssociation = inputAssociations[0];
// expect input association references __target_ref_placeholder property
expect(inputAssociation.targetRef).to.equal(bo.properties[0]);
// ...and
// expect one outgoing connection
expect(serviceTask.outgoing).to.have.length(1);
var outputAssociations = bo.dataOutputAssociations;
expect(outputAssociations).to.have.length(1);
}));
});
describe('position and size', function() {
var diagramXML = require('../../../fixtures/bpmn/features/replace/01_replace.bpmn');

View File

@ -5,6 +5,7 @@ require('../../TestHelper');
/* global bootstrapModeler, inject */
var coreModule = require('../../../lib/core');
var modelingModule = require('../../../lib/features/modeling');
var ModelCloneHelper = require('../../../lib/util/model/ModelCloneHelper');
@ -18,7 +19,11 @@ function getProp(element, property) {
describe('util/ModelCloneHelper', function() {
var testModules = [ camundaModdleModule, coreModule ];
var testModules = [
camundaModdleModule,
coreModule,
modelingModule
];
var basicXML = require('../../fixtures/bpmn/basic.bpmn');
@ -31,8 +36,8 @@ describe('util/ModelCloneHelper', function() {
var helper;
beforeEach(inject(function(eventBus) {
helper = new ModelCloneHelper(eventBus);
beforeEach(inject(function(eventBus, bpmnFactory) {
helper = new ModelCloneHelper(eventBus, bpmnFactory);
}));
describe('simple', function() {
@ -64,6 +69,7 @@ describe('util/ModelCloneHelper', function() {
});
describe('nested', function() {
it('should pass nested property - documentation', inject(function(moddle) {