diff --git a/components/bpmn-q/modeler-component/editor/ModelerHandler.js b/components/bpmn-q/modeler-component/editor/ModelerHandler.js index ebcb98fc..57c55399 100644 --- a/components/bpmn-q/modeler-component/editor/ModelerHandler.js +++ b/components/bpmn-q/modeler-component/editor/ModelerHandler.js @@ -85,27 +85,38 @@ export function createTempModeler() { } /** - * Create a Modeler with only Camunda native extensions and no additional modules + * Creates a modeler with all additional modules and extension moddles from all active plugins which is not + * saved in as the current modeler instance and load the given xml into it. + * + * @param xml the xml representing the BPMN diagram to load * - * @returns the created bpmn-js modeler + * @returns the created modeler */ -export function createLightweightModeler() { - return new BpmnModeler({ - moddleExtensions: getExtensions(), - }); +export async function createTempModelerFromXml(xml) { + // create new modeler with the custom QuantME extensions + const bpmnModeler = createTempModeler(); + + // import the xml containing the definitions + try { + await bpmnModeler.importXML(xml); + return bpmnModeler; + } catch (err) { + console.error(err); + } + return undefined; } /** - * Creates a modeler with all additional modules and extension moddles from all active plugins which is not - * saved in as the current modeler instance and load the given xml into it. + * Creates a modeler with all additional modules and extension moddles from all active plugins which is + * saved as the current modeler instance and load the given xml into it. * * @param xml the xml representing the BPMN diagram to load * * @returns the created modeler */ -export async function createTempModelerFromXml(xml) { +export async function createModelerFromXml(xml) { // create new modeler with the custom QuantME extensions - const bpmnModeler = createTempModeler(); + const bpmnModeler = createModeler(); // import the xml containing the definitions try { diff --git a/components/bpmn-q/modeler-component/editor/util/TransformationUtilities.js b/components/bpmn-q/modeler-component/editor/util/TransformationUtilities.js index f283945c..000a5b8e 100644 --- a/components/bpmn-q/modeler-component/editor/util/TransformationUtilities.js +++ b/components/bpmn-q/modeler-component/editor/util/TransformationUtilities.js @@ -1,5 +1,5 @@ -import {isFlowLikeElement} from './ModellingUtilities'; -import {getDi, is} from 'bpmn-js/lib/util/ModelUtil'; +import { isFlowLikeElement } from './ModellingUtilities'; +import { getDi, is } from 'bpmn-js/lib/util/ModelUtil'; /** * Insert the given element and all child elements into the diagram @@ -30,18 +30,18 @@ export function insertShape(definitions, parent, newElement, idMap, replace, mod if (replace) { // replace old element to retain attached sequence flow, associations, data objects, ... - element = bpmnReplace.replaceElement(elementRegistry.get(oldElement.id), {type: newElement.$type}); + element = bpmnReplace.replaceElement(elementRegistry.get(oldElement.id), { type: newElement.$type }); } else { // create new shape for this element - element = modeling.createShape({type: newElement.$type}, {x: 50, y: 50}, parent, {}); + element = modeling.createShape({ type: newElement.$type }, { x: 50, y: 50 }, parent, {}); } } else { // create connection between two previously created elements let sourceElement = elementRegistry.get(idMap[newElement.sourceRef.id]); let targetElement = elementRegistry.get(idMap[newElement.targetRef.id]); - element = modeling.connect(sourceElement, targetElement, {type: newElement.$type}); + element = modeling.connect(sourceElement, targetElement, { type: newElement.$type }); } // store id to create sequence flows @@ -52,13 +52,11 @@ export function insertShape(definitions, parent, newElement, idMap, replace, mod // get the shape element related to the subprocess let shape = getDi(element); - shape.isExpanded = true; - // TODO: fix the following if, as the access to the DI of the new element is not possible with the current BPMN-JS version - /*if (shape && shape.isExpanded) { - // expand the new element - elementRegistry.get(element.id).businessObject.di.isExpanded = true; - }*/ + // expand the replacement subprocess if the detector subprocess was expanded + if (shape && (newElement.isExpanded === 'true')) { + shape.isExpanded = true; + } // preserve messages defined in ReceiveTasks } else if (newElement.$type === 'bpmn:ReceiveTask' && newElement.messageRef) { @@ -71,21 +69,21 @@ export function insertShape(definitions, parent, newElement, idMap, replace, mod let message = bpmnFactory.create('bpmn:Message'); message.name = oldMessage.name; definitions.rootElements.push(message); - modeling.updateProperties(element, {'messageRef': message}); + modeling.updateProperties(element, { 'messageRef': message }); // store id if other receive tasks reference the same message idMap[oldMessage.id] = message.id; } else { // reuse already created message and add it to receive task - modeling.updateProperties(element, {'messageRef': idMap[oldMessage.id]}); + modeling.updateProperties(element, { 'messageRef': idMap[oldMessage.id] }); } } // add element to which a boundary event is attached if (newElement.$type === 'bpmn:BoundaryEvent') { let hostElement = elementRegistry.get(idMap[newElement.attachedToRef.id]); - modeling.updateProperties(element, {'attachedToRef': hostElement.businessObject}); + modeling.updateProperties(element, { 'attachedToRef': hostElement.businessObject }); element.host = hostElement; } @@ -109,7 +107,7 @@ export function insertShape(definitions, parent, newElement, idMap, replace, mod } // return success flag and idMap with id mappings of this element and all children - return {success: success, idMap: idMap, element: element}; + return { success: success, idMap: idMap, element: element }; } /** @@ -162,7 +160,7 @@ export function insertChildElements(definitions, parent, newElement, idMap, mode } } - return {success: success, idMap: idMap, element: parent}; + return { success: success, idMap: idMap, element: parent }; } /** @@ -229,7 +227,7 @@ export function getAllElementsInProcess(processBo, elementRegistry, elementType) for (let i = 0; i < flowElementBos.length; i++) { let flowElementBo = flowElementBos[i]; if (flowElementBo.$type && flowElementBo.$type === elementType) { - elements.push({element: flowElementBo, parent: processElement}); + elements.push({ element: flowElementBo, parent: processElement }); } // recursively retrieve service tasks if subprocess is found @@ -258,7 +256,7 @@ export function getAllElementsForProcess(processBo, elementRegistry, elementType for (let i = 0; i < flowElements.length; i++) { let flowElement = flowElements[i]; if (is(flowElement, elementType)) { - elements.push({element: flowElement, parent: processElement}); + elements.push({ element: flowElement, parent: processElement }); } } return elements; diff --git a/components/bpmn-q/modeler-component/extensions/quantme/framework-config/config.js b/components/bpmn-q/modeler-component/extensions/quantme/framework-config/config.js index 6bd1d91c..c732a523 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/framework-config/config.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/framework-config/config.js @@ -15,6 +15,7 @@ const defaultConfig = { quantmeDataConfigurationsEndpoint: process.env.DATA_CONFIG, opentoscaEndpoint: process.env.OPENTOSCA_ENDPOINT, wineryEndpoint: process.env.WINERY_ENDPOINT, + camundaEndpoint: process.env.CAMUNDA_ENDPOINT, nisqAnalyzerEndpoint: process.env.NISQ_ANALYZER_ENDPOINT, githubToken: process.env.GITHUB_TOKEN, transformationFrameworkEndpoint: process.env.TRANSFORMATION_FRAMEWORK_ENDPOINT, diff --git a/components/bpmn-q/modeler-component/extensions/quantme/replacement/QuantMETransformator.js b/components/bpmn-q/modeler-component/extensions/quantme/replacement/QuantMETransformator.js index 751b477e..db464ffa 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/replacement/QuantMETransformator.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/replacement/QuantMETransformator.js @@ -9,22 +9,24 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {layout} from './layouter/Layouter'; -import {matchesQRM} from './QuantMEMatcher'; -import {addQuantMEInputParameters} from './InputOutputHandler'; +import { layout } from './layouter/Layouter'; +import { matchesQRM } from './QuantMEMatcher'; +import { addQuantMEInputParameters } from './InputOutputHandler'; import * as Constants from '../Constants'; -import {replaceHardwareSelectionSubprocess} from './hardware-selection/QuantMEHardwareSelectionHandler'; -import {replaceCuttingSubprocess} from './circuit-cutting/QuantMECuttingHandler'; -import {insertShape} from '../../../editor/util/TransformationUtilities'; -import {createTempModelerFromXml} from '../../../editor/ModelerHandler'; +import { replaceHardwareSelectionSubprocess } from './hardware-selection/QuantMEHardwareSelectionHandler'; +import { replaceCuttingSubprocess } from './circuit-cutting/QuantMECuttingHandler'; +import { insertShape } from '../../../editor/util/TransformationUtilities'; +import { createModelerFromXml } from '../../../editor/ModelerHandler'; import { getCamundaInputOutput, getDefinitionsFromXml, getRootProcess, getSingleFlowElement } from '../../../editor/util/ModellingUtilities'; -import {getXml} from '../../../editor/util/IoUtilities'; +import { getXml } from '../../../editor/util/IoUtilities'; +const { DOMParser } = require('xmldom'); +const xmlParser = require('xml-js'); /** * Initiate the replacement process for the QuantME tasks that are contained in the current process model * @@ -33,24 +35,24 @@ import {getXml} from '../../../editor/util/IoUtilities'; * @param endpointConfig endpoints of the services required for the dynamic hardware selection */ export async function startQuantmeReplacementProcess(xml, currentQRMs, endpointConfig) { - let modeler = await createTempModelerFromXml(xml); + let modeler = await createModelerFromXml(xml); let modeling = modeler.get('modeling'); let elementRegistry = modeler.get('elementRegistry'); // get root element of the current diagram - const definitions = modeler.getDefinitions(); - const rootElement = getRootProcess(definitions); + let definitions = modeler.getDefinitions(); + let rootElement = getRootProcess(definitions); console.log(rootElement); if (typeof rootElement === 'undefined') { console.log('Unable to retrieve root process element from definitions!'); - return {status: 'failed', cause: 'Unable to retrieve root process element from definitions!'}; + return { status: 'failed', cause: 'Unable to retrieve root process element from definitions!' }; } // get all QuantME modeling constructs from the process let replacementConstructs = getQuantMETasks(rootElement, elementRegistry); console.log('Process contains ' + replacementConstructs.length + ' QuantME modeling constructs to replace...'); if (!replacementConstructs || !replacementConstructs.length) { - return {status: 'transformed', xml: xml}; + return { status: 'transformed', xml: xml }; } // check for available replacement models for all QuantME modeling constructs @@ -111,10 +113,96 @@ export async function startQuantmeReplacementProcess(xml, currentQRMs, endpointC } // layout diagram after successful transformation - layout(modeling, elementRegistry, rootElement); + //layout(modeling, elementRegistry, rootElement); let updated_xml = await getXml(modeler); - console.log(updated_xml); - return {status: 'transformed', xml: updated_xml}; + + // Parse the XML string into a JavaScript object + let xmlDoc = xmlParser.xml2js(updated_xml, { compact: true }); + const bpmnNamespace = 'http://www.omg.org/spec/BPMN/20100524/MODEL'; + const diagramNamespace = 'http://www.omg.org/spec/BPMN/20100524/DI'; + const quantmeNamespace = 'https://github.com/UST-QuAntiL/QuantME-Quantum4BPMN'; + + // retrieve the namespace prefixes from the rootElement + let prefixes = Object.entries(rootElement.$parent.$attrs); + const foundBpmnPair = prefixes.find(pair => pair[1] === bpmnNamespace); + const foundDiagramPair = prefixes.find(pair => pair[1] === diagramNamespace); + const foundQuantMEPair = prefixes.find(pair => pair[1] === quantmeNamespace); + let modifiedXmlString = updated_xml; + if (foundBpmnPair && foundDiagramPair && foundQuantMEPair) { + + // Remove xmlns: prefix from the key + const bpmnPrefix = foundBpmnPair[0].replace(/^xmlns:/, ''); + const diagramPrefix = foundDiagramPair[0].replace(/^xmlns:/, ''); + const quantmePrefix = foundQuantMEPair[0].replace(/^xmlns:/, ''); + + // Get all BPMNDiagram elements + const definitionsElement = xmlDoc[bpmnPrefix + ':definitions']; + let process = definitionsElement[bpmnPrefix + ':process']; + + let subprocesses = process[bpmnPrefix + ':subProcess']; + let quantmeCuttingSubprocess = process[quantmePrefix + 'circuitCuttingSubprocess']; + let quantmeHardwareSelectionSubprocess = process[quantmePrefix + 'quantumHardwareSelectionSubprocess']; + let bpmnDiagrams = definitionsElement[diagramPrefix + ':BPMNDiagram']; + + let subprocessBpmnElement = []; + + // remove additional planes & extract bpmnElement + for (let i = 0; i < bpmnDiagrams.length; i++) { + let bpmnPlane = JSON.parse(JSON.stringify(bpmnDiagrams[i])); + subprocessBpmnElement.push(bpmnPlane['bpmndi:BPMNPlane']['_attributes']['bpmnElement']); + } + + let shapes = JSON.parse(JSON.stringify(bpmnDiagrams[0]['bpmndi:BPMNPlane']))['bpmndi:BPMNShape']; + for (let i = 0; i < shapes.length; i++) { + let shape = shapes[i]; + let bpmnShape = shape._attributes.bpmnElement; + let height = bpmnDiagrams[0]['bpmndi:BPMNPlane']['bpmndi:BPMNShape'][i]['dc:Bounds']['_attributes'].height; + if (height === 10 || height === '10') { + bpmnDiagrams[0]['bpmndi:BPMNPlane']['bpmndi:BPMNShape'][i]['dc:Bounds']['_attributes'].height = 80; + bpmnDiagrams[0]['bpmndi:BPMNPlane']['bpmndi:BPMNShape'][i]['dc:Bounds']['_attributes'].width = 100; + } + } + + // Remove all bpmndi:BPMNDiagram elements which do not contain bpmn shapes + if (Array.isArray(bpmnDiagrams)) { + if (bpmnDiagrams.length > 1) { + + // extract the diagrams with shapes + let diagram = []; + for (let i = 0; i < bpmnDiagrams.length; i++) { + if (bpmnDiagrams[i]['bpmndi:BPMNPlane']['bpmndi:BPMNShape'] !== undefined) { + diagram.push(bpmnDiagrams[i]); + } + } + xmlDoc[bpmnPrefix + ':definitions'][diagramPrefix + ':BPMNDiagram'] = diagram; + } + } + + if (subprocesses !== undefined) { + process[bpmnPrefix + ':subProcess'] = removeIsExpandedAttribute(subprocesses, bpmnPrefix, quantmePrefix); + } + if (quantmeHardwareSelectionSubprocess !== undefined) { + process[quantmePrefix + ':quantumHardwareSelectionSubprocess'] = removeIsExpandedAttribute(quantmeHardwareSelectionSubprocess, bpmnPrefix, quantmePrefix); + } + if (quantmeCuttingSubprocess) { + process[quantmePrefix + ':circuitCuttingSubprocess'] = removeIsExpandedAttribute(quantmeCuttingSubprocess, bpmnPrefix, quantmePrefix); + } + + // Serialize the modified JavaScript object back to XML string + modifiedXmlString = xmlParser.js2xml(xmlDoc, { compact: true }); + } + modeler = await createModelerFromXml(modifiedXmlString); + modeling = modeler.get('modeling'); + elementRegistry = modeler.get('elementRegistry'); + + // get root element of the current diagram + definitions = modeler.getDefinitions(); + rootElement = getRootProcess(definitions); + layout(modeling, elementRegistry, rootElement); + let updated_xml2 = await getXml(modeler); + + + return { status: 'transformed', xml: updated_xml2 }; } /** @@ -130,7 +218,7 @@ export function getQuantMETasks(process, elementRegistry) { for (let i = 0; i < flowElements.length; i++) { let flowElement = flowElements[i]; if (flowElement.$type && flowElement.$type.startsWith('quantme:')) { - quantmeTasks.push({task: flowElement, parent: processBo}); + quantmeTasks.push({ task: flowElement, parent: processBo }); } // recursively retrieve QuantME tasks if subprocess is found @@ -160,7 +248,6 @@ async function getMatchingQRM(task, currentQRMs) { */ async function replaceByFragment(definitions, task, parent, replacement, modeler) { let bpmnFactory = modeler.get('bpmnFactory'); - if (!replacement) { console.log('Replacement fragment is undefined. Aborting replacement!'); return false; @@ -175,6 +262,37 @@ async function replaceByFragment(definitions, task, parent, replacement, modeler } console.log('Replacement element: ', replacementElement); + + if (['bpmn:SubProcess', 'quantme:QuantumHardwareSelectionSubprocess', 'quantme:CircuitCuttingSubprocess'].includes(replacementElement.$type)) { + + // Create a DOM parser + const parser = new DOMParser(); + + // Parse the XML string + const xmlDoc = parser.parseFromString(replacement, 'text/xml'); + + const bpmndiNamespace = 'http://www.omg.org/spec/BPMN/20100524/DI'; + const bpmndiShapes = xmlDoc.getElementsByTagNameNS(bpmndiNamespace, 'BPMNShape'); + + let isExpanded = null; + for (let i = 0; i < bpmndiShapes.length; i++) { + const bpmnElement = bpmndiShapes[i].getAttribute('bpmnElement'); + if (bpmnElement === replacementElement.id) { + isExpanded = bpmndiShapes[i].getAttribute('isExpanded'); + replacementElement.isExpanded = isExpanded; + + // check the children of each replacementElement + for (let j = 0; j < replacementElement.flowElements.length; j++) { + if (['bpmn:SubProcess', 'quantme:QuantumHardwareSelectionSubprocess', 'quantme:CircuitCuttingSubprocess'].includes(replacementElement.flowElements[j].$type)) { + isChildExpanded(replacementElement.flowElements[j], bpmndiShapes); + } + } + break; + } + } + } + + let result = insertShape(definitions, parent, replacementElement, {}, true, modeler, task); // add all attributes of the replaced QuantME task to the input parameters of the replacement fragment @@ -183,3 +301,83 @@ async function replaceByFragment(definitions, task, parent, replacement, modeler return result['success']; } + +/** + * Recursively checks the children of an element and updates the isExpanded attribute. + * @param {*} element + * @param {*} bpmndiShapes the diagram elements + */ +function isChildExpanded(element, bpmndiShapes) { + for (let i = 0; i < bpmndiShapes.length; i++) { + const bpmnElement = bpmndiShapes[i].getAttribute('bpmnElement'); + + if (bpmnElement === element.id && ['bpmn:SubProcess', 'quantme:QuantumHardwareSelectionSubprocess', 'quantme:CircuitCuttingSubprocess'].includes(element.$type)) { + let isExpanded = bpmndiShapes[i].getAttribute('isExpanded'); + if (isExpanded) { + element.isExpanded = isExpanded; + } + } + } + + if (element.flowElements !== undefined) { + for (let i = 0; i < element.flowElements.length; i++) { + const child = element.flowElements[i]; + if (['bpmn:SubProcess', 'quantme:QuantumHardwareSelectionSubprocess', 'quantme:CircuitCuttingSubprocess'].includes(child.$type)) { + if (isChildExpanded(child, bpmndiShapes)) { + return true; + } + } + } + } + + return false; +} + +/** + * Recursively removes the isExpanded attribute from the bpmn shapes. + * + * @param subprocessElements + * @param bpmnPrefix + * @param quantmePrefix + * @returns the modified subprocess elements + */ +function removeIsExpandedAttribute(subprocessElements, bpmnPrefix, quantmePrefix) { + + // remove top-level isExpanded attribute from shape + if (subprocessElements['_attributes'] !== undefined) { + let parentAttributes = subprocessElements['_attributes']; + delete parentAttributes.isExpanded; + } + + if (Array.isArray(subprocessElements)) { + for (let i = 0; i < subprocessElements.length; i++) { + let subprocess = subprocessElements[i]; + deleteAttribute(subprocess, bpmnPrefix, quantmePrefix); + let subprocesses = subprocess[bpmnPrefix + ':subProcess']; + deleteAttribute(subprocesses, bpmnPrefix, quantmePrefix); + let quantmeCuttingSubprocess = subprocess[quantmePrefix + ':circuitCuttingSubprocess']; + deleteAttribute(quantmeCuttingSubprocess, bpmnPrefix, quantmePrefix); + let quantmeHardwareSelectionSubprocess = subprocess[quantmePrefix + ':quantumHardwareSelectionSubprocess']; + deleteAttribute(quantmeHardwareSelectionSubprocess, bpmnPrefix, quantmePrefix); + } + return subprocessElements; + } else { + let subprocesses = subprocessElements[bpmnPrefix + ':subProcess']; + deleteAttribute(subprocesses, bpmnPrefix, quantmePrefix); + let quantmeCuttingSubprocess = subprocessElements[quantmePrefix + ':circuitCuttingSubprocess']; + deleteAttribute(quantmeCuttingSubprocess, bpmnPrefix, quantmePrefix); + let quantmeHardwareSelectionSubprocess = subprocessElements[quantmePrefix + ':quantumHardwareSelectionSubprocess']; + deleteAttribute(quantmeHardwareSelectionSubprocess, bpmnPrefix, quantmePrefix); + return subprocessElements; + } +} + +function deleteAttribute(element, bpmnPrefix, quantmePrefix) { + if (element !== undefined) { + let attributes = element['_attributes']; + if (attributes !== undefined) { + delete attributes.isExpanded; + } + removeIsExpandedAttribute(element, bpmnPrefix, quantmePrefix); + } +} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/replacement/layouter/Layouter.js b/components/bpmn-q/modeler-component/extensions/quantme/replacement/layouter/Layouter.js index ef1035d5..27f70df1 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/replacement/layouter/Layouter.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/replacement/layouter/Layouter.js @@ -9,8 +9,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {getDi, is} from 'bpmn-js/lib/util/ModelUtil'; -import {isFlowLikeElement} from '../../../../editor/util/ModellingUtilities'; +import { getDi, is } from 'bpmn-js/lib/util/ModelUtil'; +import { isFlowLikeElement } from '../../../../editor/util/ModellingUtilities'; // space between multiple boundary events of a task/subprocess let BOUNDARY_EVENT_MARGIN = '10'; @@ -56,15 +56,6 @@ function layoutProcess(modeling, elementRegistry, process) { // layout elements in subprocess if (['bpmn:SubProcess', 'quantme:QuantumHardwareSelectionSubprocess', 'quantme:CircuitCuttingSubprocess'].includes(flowElements[i].$type)) { console.log('Flow element is subprocess. Layouting contained elements...'); - const flowElement = elementRegistry.get(flowElements[i].id); - let oldBounds = getDi(flowElement).bounds; - modeling.resizeShape(elementRegistry.get(flowElements[i].id), { - x: oldBounds.x, - y: oldBounds.y, - height: 10, - width: 10 - }); - layoutProcess(modeling, elementRegistry, elementRegistry.get(flowElements[i].id).businessObject); } @@ -97,7 +88,7 @@ function layoutProcess(modeling, elementRegistry, process) { nodes.push(elementRegistry.get(artifact.id)); if (artifact.$type === 'bpmn:Association') { - edges.push({id: artifact.id, sourceId: artifact.sourceRef.id, targetId: artifact.targetRef.id}); + edges.push({ id: artifact.id, sourceId: artifact.sourceRef.id, targetId: artifact.targetRef.id }); } } } @@ -140,7 +131,7 @@ function layoutBoundaryEvents(modeling, elementRegistry) { let offset = (attachedToElementBoundaries.length + 1) * (parseInt(boundaryEventBounds.width) + parseInt(BOUNDARY_EVENT_MARGIN)); let to_move_x = bottomOfAttached - offset; let to_move_y = attachedToBounds.y - boundaryEventBounds.y + attachedToBounds.height - boundaryEventBounds.height / 2; - modeling.moveShape(boundaryEventShape, {x: to_move_x, y: to_move_y}); + modeling.moveShape(boundaryEventShape, { x: to_move_x, y: to_move_y }); // update list for the next boundary event attachedToElementBoundaries.push(boundaryEventShape.id); @@ -155,7 +146,7 @@ function layoutBoundaryEvents(modeling, elementRegistry) { let sourceX = boundaryEventBounds.x + boundaryEventBounds.width / 2; let sourceY = boundaryEventBounds.y + boundaryEventBounds.height; waypoints.shift(); - waypoints.unshift({x: sourceX, y: sourceY}); + waypoints.unshift({ x: sourceX, y: sourceY }); // update diagram modeling.updateWaypoints(connectionShape, waypoints); @@ -234,7 +225,7 @@ function adaptLabels(modeling, connection) { // place the first label of the given connection let firstLabel = connection.labels[0]; let middle = getMiddleOfLocation(connection, firstLabel); - modeling.moveElements([firstLabel], {x: middle.x - firstLabel.x, y: middle.y - firstLabel.y}); + modeling.moveElements([firstLabel], { x: middle.x - firstLabel.x, y: middle.y - firstLabel.y }); } // TODO: handle cases with multiple labels defined for the connection @@ -255,11 +246,11 @@ function getMiddleOfLocation(connection, label) { let middlePoint2 = waypoints[middleWaypointIndex]; if (middlePoint1.x === middlePoint2.x) { - return {x: middlePoint1.x - LABEL_MARGIN - parseInt(label.width), y: (middlePoint1.y + middlePoint2.y) / 2}; + return { x: middlePoint1.x - LABEL_MARGIN - parseInt(label.width), y: (middlePoint1.y + middlePoint2.y) / 2 }; } if (middlePoint1.y === middlePoint2.y) { - return {x: (middlePoint1.x + middlePoint2.x) / 2, y: middlePoint1.y - LABEL_MARGIN - parseInt(label.height)}; + return { x: (middlePoint1.x + middlePoint2.x) / 2, y: middlePoint1.y - LABEL_MARGIN - parseInt(label.height) }; } return { @@ -356,7 +347,7 @@ function getEdgeFromFlowElement(elementRegistry, flowElement) { sourceElement = sourceElement.attachedToRef; } - return {id: flowElement.id, sourceId: sourceElement.id, targetId: flowElement.targetRef.id}; + return { id: flowElement.id, sourceId: sourceElement.id, targetId: flowElement.targetRef.id }; } /** @@ -372,14 +363,14 @@ function layoutWithDagre(modeling, elementRegistry, dagre, tasks, flows, options console.log('Adding %i tasks to the graph for layouting: ', tasks.length); for (let i = 0; i < tasks.length; i++) { let task = tasks[i]; - g.setNode(task.id, {label: task.id, width: task.width, height: task.height}); + g.setNode(task.id, { label: task.id, width: task.width, height: task.height }); } // add flows as edges to the graph console.log('Adding %i flows to the graph for layouting: ', flows.length); for (let i = 0; i < flows.length; i++) { let flow = flows[i]; - g.setEdge(flow['sourceId'], flow['targetId'], {label: flow['id']}); + g.setEdge(flow['sourceId'], flow['targetId'], { label: flow['id'] }); } // layout the graph @@ -393,7 +384,7 @@ function layoutWithDagre(modeling, elementRegistry, dagre, tasks, flows, options // determine new position of task and move it there let to_move_x = node.x - element.x - element.width / 2; let to_move_y = node.y - element.y - element.height / 2; - let delta_string = {x: to_move_x, y: to_move_y}; + let delta_string = { x: to_move_x, y: to_move_y }; modeling.moveElements([element], delta_string); }); @@ -402,18 +393,21 @@ function layoutWithDagre(modeling, elementRegistry, dagre, tasks, flows, options let edge = g.edge(e); let points = edge.points; let element = elementRegistry.get(edge.label); - let waypoints = element.waypoints; + if (element !== undefined) { + let waypoints = element.waypoints; - while (waypoints.length > 0) { - waypoints.pop(); - } + while (waypoints.length > 0) { + waypoints.pop(); + } - for (let pointsIndex = 0; pointsIndex < points.length; pointsIndex++) { - let point; - point = {x: points[pointsIndex].x, y: points[pointsIndex].y}; - waypoints.push(point); - } + for (let pointsIndex = 0; pointsIndex < points.length; pointsIndex++) { + let point; + point = { x: points[pointsIndex].x, y: points[pointsIndex].y }; + waypoints.push(point); + } - element.waypoints = waypoints; + element.waypoints = waypoints; + } }); + } diff --git a/components/bpmn-q/package-lock.json b/components/bpmn-q/package-lock.json index 4a435456..cae864af 100644 --- a/components/bpmn-q/package-lock.json +++ b/components/bpmn-q/package-lock.json @@ -39,7 +39,9 @@ "timers": "^0.1.1", "tiny-svg": "^3.0.0", "webpack": "^5.75.0", - "whatwg-fetch": "^3.6.2" + "whatwg-fetch": "^3.6.2", + "xml-js": "^1.6.11", + "xpath": "^0.0.32" }, "devDependencies": { "@babel/core": "^7.20.12", @@ -4113,9 +4115,9 @@ "link": true }, "node_modules/bpmnlint-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bpmnlint-utils/-/bpmnlint-utils-1.0.2.tgz", - "integrity": "sha512-+ti0VICOpgYpQgzpF0mwXqDX4NhrQCc2YKy28VjPu7v9sXpdpWEylP+iBw6WlheJFRcXoZh2q/QRLb5DMB3naQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bpmnlint-utils/-/bpmnlint-utils-1.1.1.tgz", + "integrity": "sha512-Afdb77FmwNB3INyUfbzXW40yY+mc0qYU3SgDFeI4zTtduiVomOlfqoXiEaUIGI8Hyh7aVYpmf3O97P2w7x0DYQ==", "dev": true }, "node_modules/bpmnlint/node_modules/bpmn-moddle": { @@ -13495,8 +13497,7 @@ "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "node_modules/saxen": { "version": "8.1.2", @@ -16119,6 +16120,17 @@ "node": ">=0.10.0" } }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -16138,6 +16150,14 @@ "node": ">=0.1" } }, + "node_modules/xpath": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", + "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -19384,9 +19404,9 @@ } }, "bpmnlint-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bpmnlint-utils/-/bpmnlint-utils-1.0.2.tgz", - "integrity": "sha512-+ti0VICOpgYpQgzpF0mwXqDX4NhrQCc2YKy28VjPu7v9sXpdpWEylP+iBw6WlheJFRcXoZh2q/QRLb5DMB3naQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bpmnlint-utils/-/bpmnlint-utils-1.1.1.tgz", + "integrity": "sha512-Afdb77FmwNB3INyUfbzXW40yY+mc0qYU3SgDFeI4zTtduiVomOlfqoXiEaUIGI8Hyh7aVYpmf3O97P2w7x0DYQ==", "dev": true }, "brace-expansion": { @@ -26684,8 +26704,7 @@ "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "saxen": { "version": "8.1.2", @@ -28721,6 +28740,14 @@ "integrity": "sha512-dTaaRwm4ccF8UF15/PLT3pNNlZP04qko/FUcr0QBppYLk8+J7xA9gg2vI2X4Kr1PcJAVxwI9NdADex29FX2QVQ==", "dev": true }, + "xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "requires": { + "sax": "^1.2.4" + } + }, "xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -28733,6 +28760,11 @@ "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==", "dev": true }, + "xpath": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", + "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/components/bpmn-q/package.json b/components/bpmn-q/package.json index 4f3b14f9..a934712b 100644 --- a/components/bpmn-q/package.json +++ b/components/bpmn-q/package.json @@ -53,7 +53,9 @@ "timers": "^0.1.1", "tiny-svg": "^3.0.0", "webpack": "^5.75.0", - "whatwg-fetch": "^3.6.2" + "whatwg-fetch": "^3.6.2", + "xml-js": "^1.6.11", + "xpath": "^0.0.32" }, "devDependencies": { "@babel/core": "^7.20.12", diff --git a/components/bpmn-q/test/tests/helpers/DiagramHelper.js b/components/bpmn-q/test/tests/helpers/DiagramHelper.js index 0cb1845e..4a6b5755 100644 --- a/components/bpmn-q/test/tests/helpers/DiagramHelper.js +++ b/components/bpmn-q/test/tests/helpers/DiagramHelper.js @@ -286,4 +286,5 @@ export const validQuantMEDiagram = ' ]*>/g; + const matches = xmlString.match(regexPattern); + + let expanded = []; + + // Regular expression pattern to extract bpmndi:BPMNShape elements + const shapeRegexPattern = /]*>/g; + + // Regular expression pattern to extract isExpanded attribute value + const isExpandedRegexPattern = /isExpanded="([^"]+)"/; + + // Extract the bpmndi:BPMNShape elements using the regular expression + const shapeMatches = xmlString.match(shapeRegexPattern); + + // Loop through the shape matches and extract the isExpanded attribute + for (let i = 0; i < shapeMatches.length; i++) { + const shapeMatch = shapeMatches[i]; + + // Extract the bpmnElement attribute value + const bpmnElementMatch = shapeMatch.match(/bpmnElement="([^"]+)"/); + if (bpmnElementMatch && bpmnElementMatch.length > 1) { + const bpmnElement = bpmnElementMatch[1]; + + // Extract the isExpanded attribute value + const isExpandedMatch = shapeMatch.match(isExpandedRegexPattern); + let positionCircuitCutting = xmlString.search('circuitCuttingSubprocess id="' + bpmnElement + '"'); + let positionSubProcess = xmlString.search('subProcess id="' + bpmnElement + '"'); + + if (positionCircuitCutting > -1 || positionSubProcess > -1) { + if (isExpandedMatch && isExpandedMatch.length > 1) { + const isExpanded = isExpandedMatch[1]; + + if (isExpanded !== undefined) { + expanded.push(isExpanded); + } + } else { + expanded.push('false'); + } + } + } + } + return expanded; +} \ No newline at end of file diff --git a/components/bpmn-q/webpack.config.js b/components/bpmn-q/webpack.config.js index e951dba6..aa9bd988 100644 --- a/components/bpmn-q/webpack.config.js +++ b/components/bpmn-q/webpack.config.js @@ -64,7 +64,7 @@ module.exports = { new webpack.EnvironmentPlugin({ AUTOSAVE_INTERVAL: 300000, AWS_RUNTIME_HANDLER_ENDPOINT: 'http://localhost:8890', - CAMUNDA_ENDPOINT: 'http://localhost:8080/engine-rest', + CAMUNDA_ENDPOINT: 'http://localhost:8090/engine-rest', DATA_CONFIG: 'https://raw.githubusercontent.com/PlanQK/workflow-modeler/master/components/bpmn-q/modeler-component/extensions/quantme/configurations/quantmeDataObjects.json', DOWNLOAD_FILE_NAME: 'quantum-workflow-model', ENABLE_DATA_FLOW_PLUGIN: true,