chore(replace): retain definitions where appropriate

* simplify implementation
* retain event definitions when switching from interrupting
  to non-interrupting and vice versa

Closes #799
This commit is contained in:
Nico Rehwaldt 2018-06-27 15:11:34 +02:00 committed by Philipp Fromme
parent 628e2d1019
commit 75c0880341
4 changed files with 192 additions and 68 deletions

View File

@ -6,6 +6,8 @@ All notable changes to [bpmn-js](https://github.com/bpmn-io/bpmn-js) are documen
___Note:__ Yet to be released changes appear here._
* `FIX`: keep event definitions when switching from interrupting to non-interrupting boundary event ([#799](https://github.com/bpmn-io/bpmn-js/issues/799))
## 2.3.0
* `CHORE`: update to `diagram-js@2.4.0`

View File

@ -1,14 +1,13 @@
import {
pick,
assign,
uniqueBy,
findIndex,
filter,
has
} from 'min-dash';
import {
is,
getBusinessObject
} from '../../util/ModelUtil';
import {
@ -110,29 +109,35 @@ export default function BpmnReplace(
var elementProps = getProperties(oldBusinessObject.$descriptor),
newElementProps = getProperties(newBusinessObject.$descriptor, true),
properties = uniqueBy(function(e) { return e; }, filter(elementProps, function(value) {
return findIndex(newElementProps, value) !== -1;
}));
copyProps = intersection(elementProps, newElementProps);
// initialize special properties defined in target definition
assign(newBusinessObject, pick(target, CUSTOM_PROPERTIES));
properties = filter(properties, function(property) {
var properties = filter(copyProps, function(property) {
var propName = property.replace(/bpmn:/, '');
// so the applied properties from 'target' don't get lost
if (newBusinessObject[property] !== undefined) {
return false;
// copying event definitions, unless we replace
if (propName === 'eventDefinitions') {
return hasEventDefinition(element, target.eventDefinitionType);
}
// retain loop characteristics if the target element is not an event sub process
// retain loop characteristics if the target element
// is not an event sub process
if (propName === 'loopCharacteristics') {
return !isEventSubProcess(newBusinessObject);
}
if ((propName === 'processRef' && target.isExpanded === false) ||
propName === 'triggeredByEvent' ||
propName === 'eventDefinitions') {
// so the applied properties from 'target' don't get lost
if (property in newBusinessObject) {
return false;
}
if (propName === 'processRef' && target.isExpanded === false) {
return false;
}
if (propName === 'triggeredByEvent') {
return false;
}
@ -143,8 +148,14 @@ export default function BpmnReplace(
// initialize custom BPMN extensions
if (target.eventDefinitionType) {
// only initialize with new eventDefinition
// if we did not set an event definition yet,
// i.e. because we cloned it
if (!hasEventDefinition(newBusinessObject, target.eventDefinitionType)) {
newElement.eventDefinitionType = target.eventDefinitionType;
}
}
if (is(oldBusinessObject, 'bpmn:Activity')) {
@ -233,3 +244,21 @@ BpmnReplace.$inject = [
function isSubProcess(bo) {
return is(bo, 'bpmn:SubProcess');
}
function hasEventDefinition(element, type) {
var bo = getBusinessObject(element);
return type && bo.get('eventDefinitions').some(function(definition) {
return is(definition, type);
});
}
/**
* Compute intersection between two arrays.
*/
function intersection(a1, a2) {
return a1.filter(function(el) {
return a2.indexOf(el) !== -1;
});
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="1.14.0">
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="1.16.1">
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:startEvent id="StartEvent_1" name="KEEP ME">
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
@ -43,11 +43,15 @@
<bpmn:adHocSubProcess id="AdHocSubProcessCollapsed" />
<bpmn:adHocSubProcess id="AdHocSubProcessExpanded" />
<bpmn:boundaryEvent id="BoundaryEvent_1" cancelActivity="false" attachedToRef="Task_1">
<bpmn:timerEventDefinition />
<bpmn:timerEventDefinition>
<bpmn:timeDuration>P1D</bpmn:timeDuration>
</bpmn:timerEventDefinition>
</bpmn:boundaryEvent>
<bpmn:boundaryEvent id="BoundaryEvent_2" attachedToRef="Task_1">
<bpmn:outgoing>SequenceFlow_7</bpmn:outgoing>
<bpmn:conditionalEventDefinition />
<bpmn:conditionalEventDefinition>
<bpmn:condition xsi:type="bpmn:tFormalExpression">${a &lt; b}</bpmn:condition>
</bpmn:conditionalEventDefinition>
</bpmn:boundaryEvent>
<bpmn:sequenceFlow id="SequenceFlow_6" sourceRef="SubProcess_1" targetRef="Transaction_1" />
<bpmn:sequenceFlow id="SequenceFlow_7" sourceRef="BoundaryEvent_2" targetRef="SubProcess_1" />

View File

@ -133,33 +133,72 @@ describe('features/replace - bpmn replace', function() {
}));
it('non interrupting boundary event by interrupting boundary event',
describe('boundary event', function() {
it('<non-interrupting> with <interrupting>',
inject(function(elementRegistry, modeling, bpmnReplace, canvas) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
boundaryBo = boundaryEvent.businessObject,
newElementData = {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:EscalationEventDefinition'
eventDefinitionType: 'bpmn:TimerEventDefinition'
};
var eventDefinitions = boundaryBo.eventDefinitions.slice();
// when
var newElement = bpmnReplace.replaceElement(boundaryEvent, newElementData);
var newBo = newElement.businessObject;
// then
expect(newElement).to.exist;
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;
expect(is(newBo, 'bpmn:BoundaryEvent')).to.be.true;
expect(newBo.eventDefinitions).to.jsonEqual(eventDefinitions, skipId);
expect(newBo.cancelActivity).to.be.true;
})
);
it('interrupting boundary event by non interrupting boundary event',
it('<interrupting> with <non-interrupting>',
inject(function(elementRegistry, modeling, bpmnReplace, canvas) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_2'),
boundaryBo = boundaryEvent.businessObject,
newElementData = {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:ConditionalEventDefinition',
cancelActivity: false
};
var eventDefinitions = boundaryBo.eventDefinitions.slice();
// when
var newElement = bpmnReplace.replaceElement(boundaryEvent, newElementData);
var newBo = newElement.businessObject;
// then
expect(newElement).to.exist;
expect(is(newBo, 'bpmn:BoundaryEvent')).to.be.true;
expect(newBo.eventDefinitions).to.jsonEqual(eventDefinitions, skipId);
expect(newBo.cancelActivity).to.be.false;
})
);
it('<timer> with <signal>',
inject(function(elementRegistry, modeling, bpmnReplace, canvas) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_1'),
newElementData = {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:SignalEventDefinition',
@ -169,16 +208,52 @@ describe('features/replace - bpmn replace', function() {
// when
var newElement = bpmnReplace.replaceElement(boundaryEvent, newElementData);
var newBo = newElement.businessObject;
var newEventDefinitions = newBo.eventDefinitions;
var newEventDefinition = newEventDefinitions[0];
// then
expect(newElement).to.exist;
expect(is(newElement, 'bpmn:BoundaryEvent')).to.be.true;
expect(newElement.businessObject.eventDefinitions[0].$type).to.equal('bpmn:SignalEventDefinition');
expect(newElement.businessObject.cancelActivity).to.be.false;
expect(newEventDefinitions).to.have.length(1);
expect(is(newBo, 'bpmn:BoundaryEvent')).to.be.true;
expect(is(newEventDefinition, 'bpmn:SignalEventDefinition')).to.be.true;
expect(newBo.cancelActivity).to.be.false;
})
);
it('boundary event and update host',
it('<conditional> with <timer>',
inject(function(elementRegistry, modeling, bpmnReplace, canvas) {
// given
var boundaryEvent = elementRegistry.get('BoundaryEvent_2'),
newElementData = {
type: 'bpmn:BoundaryEvent',
eventDefinitionType: 'bpmn:TimerEventDefinition'
};
// when
var newElement = bpmnReplace.replaceElement(boundaryEvent, newElementData);
var newBo = newElement.businessObject;
var newEventDefinitions = newBo.eventDefinitions;
var newEventDefinition = newEventDefinitions[0];
// then
expect(newElement).to.exist;
expect(newEventDefinitions).to.have.length(1);
expect(is(newBo, 'bpmn:BoundaryEvent')).to.be.true;
expect(is(newEventDefinition, 'bpmn:TimerEventDefinition')).to.be.true;
expect(newBo.cancelActivity).to.be.true;
})
);
it('updating host',
inject(function(elementRegistry, modeling, bpmnReplace, canvas) {
// given
@ -200,6 +275,8 @@ describe('features/replace - bpmn replace', function() {
});
});
describe('should replace in collaboration', function() {
@ -1385,3 +1462,15 @@ describe('features/replace - bpmn replace', function() {
});
});
// helpers ////////////////////////
function skipId(key, value) {
if (key === 'id') {
return;
}
return value;
}