feat(importer): import all associated processes

closes #1546
This commit is contained in:
Martin Stamm 2022-01-17 11:07:10 +01:00 committed by Philipp Fromme
parent 30d2cf9755
commit 99bfdd2fb8
3 changed files with 278 additions and 14 deletions

View File

@ -1,3 +1,5 @@
import { find, forEach, map } from 'min-dash';
import { is } from '../util/ModelUtil';
import BpmnTreeWalker from './BpmnTreeWalker';
@ -65,19 +67,22 @@ export function importBpmnDiagram(diagram, definitions, bpmnDiagram) {
var walker = new BpmnTreeWalker(visitor, translate);
// traverse BPMN 2.0 document model,
// starting at definitions
if (!bpmnDiagram && definitions.diagrams) {
for (var i = 0; i < definitions.diagrams.length; i++) {
walker.handleDefinitions(definitions, definitions.diagrams[i]);
}
} else {
walker.handleDefinitions(definitions, bpmnDiagram);
bpmnDiagram = bpmnDiagram || (definitions.diagrams && definitions.diagrams[0]);
var diagramsToLoad = getDiagramsToLoad(definitions, bpmnDiagram);
if (!diagramsToLoad) {
throw new Error(translate('no diagram to display'));
}
var mainDiagram = bpmnDiagram || definitions.diagrams[0];
// traverse BPMN 2.0 document model,
// starting at definitions
forEach(diagramsToLoad, function(diagram) {
walker.handleDefinitions(definitions, diagram);
});
var rootId = mainDiagram.plane.bpmnElement.id;
var rootId = bpmnDiagram.plane.bpmnElement.id;
// we do need to account for different ways we create root elements
// each nested imported <root> do have the `_plane` suffix, while
@ -111,3 +116,99 @@ export function importBpmnDiagram(diagram, definitions, bpmnDiagram) {
}
});
}
/**
* Returns all diagrams in the same hirarchy as the requested Diagram.
* It includes all parent- and subProcess diagrams.
*
* @param {Array} definitions
* @param {Object} bpmnDiagram
*/
function getDiagramsToLoad(definitions, bpmnDiagram) {
if (!bpmnDiagram) {
return;
}
var bpmnElement = bpmnDiagram.plane.bpmnElement,
rootElement = bpmnElement;
if (!is(bpmnElement, 'bpmn:Process') && !is(bpmnElement, 'bpmn:Collaboration')) {
rootElement = findRootProcess(bpmnElement);
}
// in case the process is part of a collaboration, the plane references the
// collaboration, not the process
var collaboration;
if (is(rootElement, 'bpmn:Collaboration')) {
collaboration = rootElement;
}
else {
collaboration = find(definitions.rootElements, function(element) {
if (!is(element, 'bpmn:Collaboration')) {
return;
}
return find(element.participants, function(participant) {
return participant.processRef === rootElement;
});
});
}
var roots = [rootElement];
// all collaboration processes can contain sub-diagrams
if (collaboration) {
roots = map(collaboration.participants, function(participant) {
return participant.processRef;
});
roots.push(collaboration);
}
var allChildren = selfAndAllFlowElements(roots);
// if we have multiple diagrams referencing the same element, we
// use the first in the file
var diagramsToLoad = [bpmnDiagram];
var handledElements = [bpmnElement];
forEach(definitions.diagrams, function(diagram) {
var bo = diagram.plane.bpmnElement;
if (
allChildren.indexOf(bo) !== -1 &&
handledElements.indexOf(bo) === -1
) {
diagramsToLoad.push(diagram);
handledElements.push(bo);
}
});
return diagramsToLoad;
}
function selfAndAllFlowElements(elements) {
var result = [];
forEach(elements, function(element) {
if (!element) {
return;
}
result.push(element);
result = result.concat(selfAndAllFlowElements(element.flowElements));
});
return result;
}
function findRootProcess(element) {
var parent = element;
while (parent) {
if (is(parent, 'bpmn:Process')) {
return parent;
}
parent = parent.$parent;
}
}

View File

@ -708,7 +708,7 @@ describe('import - Importer', function() {
describe('multiple-diagrams', function() {
it('should import multiple diagrams', function() {
it('should import first diagrams if none is defined', function() {
// given
var xml = require('../../fixtures/bpmn/multiple-diagrams.bpmn');
@ -724,7 +724,7 @@ describe('import - Importer', function() {
diagram.invoke(function(elementRegistry, canvas) {
expect(elementRegistry.get('Task_A')).to.exist;
expect(elementRegistry.get('Task_B')).to.exist;
expect(elementRegistry.get('Task_B')).to.not.exist;
expect(canvas.getRootElement()).to.equal(elementRegistry.get('Process_1'));
});
@ -732,6 +732,118 @@ describe('import - Importer', function() {
});
it('should import complete diagram tree', function() {
// given
var xml = require('./multiple-nestes-processes.bpmn');
var selectedDiagram = 'BpmnDiagram_1';
// when
return runImport(diagram, xml, selectedDiagram).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
diagram.invoke(function(elementRegistry, canvas) {
expect(elementRegistry.get('SubProcess_1')).to.exist;
expect(elementRegistry.get('Task_1A')).to.exist;
expect(elementRegistry.get('Task_1B')).to.exist;
expect(elementRegistry.get('SubProcess_2')).to.not.exist;
expect(canvas.getRootElement()).to.equal(elementRegistry.get('Process_1'));
});
});
});
it('should switch to correct plane', function() {
// given
var xml = require('./multiple-nestes-processes.bpmn');
var selectedDiagram = 'SubProcessDiagram_1';
// when
return runImport(diagram, xml, selectedDiagram).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
diagram.invoke(function(elementRegistry, canvas) {
expect(elementRegistry.get('SubProcess_1')).to.exist;
expect(elementRegistry.get('Task_1A')).to.exist;
expect(elementRegistry.get('Task_1B')).to.exist;
expect(elementRegistry.get('SubProcess_2')).to.not.exist;
expect(canvas.getRootElement()).to.equal(elementRegistry.get('SubProcess_1_plane'));
});
});
});
it('should use first diagram for multiple candidates', function() {
// given
var xml = require('./multiple-nestes-processes.bpmn');
var selectedDiagram = 'BpmnDiagram_2';
// when
return runImport(diagram, xml, selectedDiagram).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
diagram.invoke(function(elementRegistry, canvas) {
expect(elementRegistry.get('SubProcess_2')).to.exist;
expect(elementRegistry.get('Task_2A')).to.exist;
expect(elementRegistry.get('Task_2B')).to.not.exist;
expect(elementRegistry.get('SubProcess_1')).to.not.exist;
expect(canvas.getRootElement()).to.equal(elementRegistry.get('Process_2'));
});
});
});
it('should use use selected diagram for multiple candidates', function() {
// given
var xml = require('./multiple-nestes-processes.bpmn');
var selectedDiagram = 'SubProcess_2_diagram_B';
// when
return runImport(diagram, xml, selectedDiagram).then(function(result) {
var warnings = result.warnings;
// then
expect(warnings).to.have.length(0);
diagram.invoke(function(elementRegistry, canvas) {
expect(elementRegistry.get('SubProcess_2')).to.exist;
expect(elementRegistry.get('Task_2A')).to.not.exist;
expect(elementRegistry.get('Task_2B')).to.exist;
expect(elementRegistry.get('SubProcess_1')).to.not.exist;
expect(canvas.getRootElement()).to.equal(elementRegistry.get('SubProcess_2_plane'));
});
});
});
it('should allow subProcess to have attached plane', function() {
// given
@ -760,9 +872,7 @@ describe('import - Importer', function() {
expect(subProcessElement.parent).to.equal(processRoot);
expect(taskInSubProcessElement.parent).to.equal(subProcessRoot);
});
});
});

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="sid-38422fae-e03e-43a3-bef4-bd33b32041b2" targetNamespace="http://bpmn.io/bpmn" exporter="bpmn-js (https://demo.bpmn.io)" exporterVersion="3.3.1">
<process id="Process_1" isExecutable="false">
<bpmn:subProcess id="SubProcess_1">
<task id="Task_1A" name="A" />
<task id="Task_1B" name="B" />
</bpmn:subProcess>
</process>
<process id="Process_2" isExecutable="false">
<bpmn:subProcess id="SubProcess_2">
<task id="Task_2A" name="A" />
<task id="Task_2B" name="B" />
</bpmn:subProcess>
</process>
<bpmndi:BPMNDiagram id="BpmnDiagram_1">
<bpmndi:BPMNPlane id="BpmnPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="SubProcess_1_di" bpmnElement="SubProcess_1">
<dc:Bounds x="220" y="50" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
<bpmndi:BPMNDiagram id="SubProcessDiagram_1">
<bpmndi:BPMNPlane id="SubProcessPlane_1" bpmnElement="SubProcess_1">
<bpmndi:BPMNShape id="Task_1A_di" bpmnElement="Task_1A">
<dc:Bounds x="220" y="50" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_1B_di" bpmnElement="Task_1B">
<dc:Bounds x="350" y="50" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
<bpmndi:BPMNDiagram id="BpmnDiagram_2">
<bpmndi:BPMNPlane id="BpmnPlane_2" bpmnElement="Process_2">
<bpmndi:BPMNShape id="SubProcess_2_di" bpmnElement="SubProcess_2">
<omgdc:Bounds x="256" y="81" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
<bpmndi:BPMNDiagram id="SubProcess_2_diagram_A">
<bpmndi:BPMNPlane id="SubProcessPlane_2_A" bpmnElement="SubProcess_2">
<bpmndi:BPMNShape id="Task_2A_di" bpmnElement="Task_2A">
<omgdc:Bounds x="256" y="81" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
<bpmndi:BPMNDiagram id="SubProcess_2_diagram_B">
<bpmndi:BPMNPlane id="SubProcessPlane_2_B" bpmnElement="SubProcess_2">
<bpmndi:BPMNShape id="Task_2B_di" bpmnElement="Task_2B">
<omgdc:Bounds x="256" y="81" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>