diff --git a/lib/import/Importer.js b/lib/import/Importer.js index 7ea17d66..3af536df 100644 --- a/lib/import/Importer.js +++ b/lib/import/Importer.js @@ -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 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; + } +} \ No newline at end of file diff --git a/test/spec/import/ImporterSpec.js b/test/spec/import/ImporterSpec.js index 2413365a..2f0a3c0e 100644 --- a/test/spec/import/ImporterSpec.js +++ b/test/spec/import/ImporterSpec.js @@ -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); }); - }); - }); diff --git a/test/spec/import/multiple-nestes-processes.bpmn b/test/spec/import/multiple-nestes-processes.bpmn new file mode 100644 index 00000000..37855cdc --- /dev/null +++ b/test/spec/import/multiple-nestes-processes.bpmn @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +