From 770403b621c44d67935b7cbd801845f2484197dc Mon Sep 17 00:00:00 2001 From: LaviniaStiliadou Date: Sat, 12 Aug 2023 19:54:40 +0200 Subject: [PATCH 1/7] fix replacement of hardware selection subprocess --- .../hardware-selection/QuantMEHardwareSelectionHandler.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/bpmn-q/modeler-component/extensions/quantme/replacement/hardware-selection/QuantMEHardwareSelectionHandler.js b/components/bpmn-q/modeler-component/extensions/quantme/replacement/hardware-selection/QuantMEHardwareSelectionHandler.js index 3590d67b..6365bd8f 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/replacement/hardware-selection/QuantMEHardwareSelectionHandler.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/replacement/hardware-selection/QuantMEHardwareSelectionHandler.js @@ -48,7 +48,6 @@ export async function replaceHardwareSelectionSubprocess(subprocess, parent, mod // retrieve business object of the new element let bo = elementRegistry.get(element.id).businessObject; - bo.di.isExpanded = true; // extract workflow fragment within the QuantumHardwareSelectionSubprocess let hardwareSelectionFragment = await getHardwareSelectionFragment(bo); @@ -187,7 +186,7 @@ export async function replaceHardwareSelectionSubprocess(subprocess, parent, mod modeling.connect(joiningGateway, invokeTransformedFragment, {type: 'bpmn:SequenceFlow'}); // pass all variables between the caller and callee workflow - addExtensionElements(invokeTransformedFragmentBo, invokeTransformedFragmentBo, bpmnFactory.create('camunda:In'), bpmnFactory, commandStack); + addExtensionElements(invokeTransformedFragment, invokeTransformedFragmentBo, bpmnFactory.create('camunda:In'), bpmnFactory, commandStack); let extensionElements = getExtensionElements(invokeTransformedFragmentBo, moddle); let invokeTransformedFragmentIn = extensionElements.values[0]; let invokeTransformedFragmentOut = bpmnFactory.create('camunda:Out'); From 1ad8309c3c8670c3f52b4dbc3b8bf55796bf502e Mon Sep 17 00:00:00 2001 From: LaviniaStiliadou Date: Sun, 13 Aug 2023 21:59:47 +0200 Subject: [PATCH 2/7] allow quantme tasks inside hardware selection subprocess --- .../QuantMEHardwareSelectionHandler.js | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/components/bpmn-q/modeler-component/extensions/quantme/replacement/hardware-selection/QuantMEHardwareSelectionHandler.js b/components/bpmn-q/modeler-component/extensions/quantme/replacement/hardware-selection/QuantMEHardwareSelectionHandler.js index 6365bd8f..4cde893a 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/replacement/hardware-selection/QuantMEHardwareSelectionHandler.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/replacement/hardware-selection/QuantMEHardwareSelectionHandler.js @@ -9,7 +9,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {getQuantMETasks} from '../QuantMETransformator'; +import { getQuantMETasks } from '../QuantMETransformator'; import { INVOKE_NISQ_ANALYZER_SCRIPT, INVOKE_TRANSFORMATION_SCRIPT, POLL_FOR_TRANSFORMATION_SCRIPT, @@ -18,11 +18,11 @@ import { SELECT_ON_QUEUE_SIZE_SCRIPT } from './HardwareSelectionScripts'; import * as consts from '../../Constants'; -import {addExtensionElements} from '../../../../editor/util/camunda-utils/ExtensionElementsUtil'; -import {createTempModelerFromXml, createPlainModeler} from '../../../../editor/ModelerHandler'; -import {getPropertiesToCopy, insertShape} from '../../../../editor/util/TransformationUtilities'; -import {getCamundaInputOutput, getExtensionElements, getRootProcess} from '../../../../editor/util/ModellingUtilities'; -import {getXml} from '../../../../editor/util/IoUtilities'; +import { addExtensionElements } from '../../../../editor/util/camunda-utils/ExtensionElementsUtil'; +import { createTempModelerFromXml, createModeler } from '../../../../editor/ModelerHandler'; +import { getPropertiesToCopy, insertShape } from '../../../../editor/util/TransformationUtilities'; +import { getCamundaInputOutput, getExtensionElements, getRootProcess } from '../../../../editor/util/ModellingUtilities'; +import { getXml } from '../../../../editor/util/IoUtilities'; /** * Replace the given QuantumHardwareSelectionSubprocess by a native subprocess orchestrating the hardware selection @@ -36,7 +36,7 @@ export async function replaceHardwareSelectionSubprocess(subprocess, parent, mod let moddle = modeler.get('moddle'); // replace QuantumHardwareSelectionSubprocess with traditional subprocess - let element = bpmnReplace.replaceElement(elementRegistry.get(subprocess.id), {type: 'bpmn:SubProcess'}); + let element = bpmnReplace.replaceElement(elementRegistry.get(subprocess.id), { type: 'bpmn:SubProcess' }); // update the properties of the new element modeling.updateProperties(element, getPropertiesToCopy(subprocess)); @@ -56,20 +56,20 @@ export async function replaceHardwareSelectionSubprocess(subprocess, parent, mod bo.flowElements = []; // add start event for the new subprocess - let startEvent = modeling.createShape({type: 'bpmn:StartEvent'}, {x: 50, y: 50}, element, {}); + let startEvent = modeling.createShape({ type: 'bpmn:StartEvent' }, { x: 50, y: 50 }, element, {}); let startEventBo = elementRegistry.get(startEvent.id).businessObject; startEventBo.name = 'Start Hardware Selection Subprocess'; // add gateway to avoid multiple hardware selections for the same circuit - let splittingGateway = modeling.createShape({type: 'bpmn:ExclusiveGateway'}, {x: 50, y: 50}, element, {}); + let splittingGateway = modeling.createShape({ type: 'bpmn:ExclusiveGateway' }, { x: 50, y: 50 }, element, {}); let splittingGatewayBo = elementRegistry.get(splittingGateway.id).businessObject; splittingGatewayBo.name = 'Hardware already selected?'; // connect start event and gateway - modeling.connect(startEvent, splittingGateway, {type: 'bpmn:SequenceFlow'}); + modeling.connect(startEvent, splittingGateway, { type: 'bpmn:SequenceFlow' }); // add task to invoke the NISQ Analyzer and connect it - let invokeHardwareSelection = modeling.createShape({type: 'bpmn:ScriptTask'}, {x: 50, y: 50}, element, {}); + let invokeHardwareSelection = modeling.createShape({ type: 'bpmn:ScriptTask' }, { x: 50, y: 50 }, element, {}); let invokeHardwareSelectionBo = elementRegistry.get(invokeHardwareSelection.id).businessObject; invokeHardwareSelectionBo.name = 'Invoke NISQ Analyzer'; invokeHardwareSelectionBo.scriptFormat = 'groovy'; @@ -105,7 +105,7 @@ export async function replaceHardwareSelectionSubprocess(subprocess, parent, mod ); // connect gateway with selection path and add condition - let selectionFlow = modeling.connect(splittingGateway, invokeHardwareSelection, {type: 'bpmn:SequenceFlow'}); + let selectionFlow = modeling.connect(splittingGateway, invokeHardwareSelection, { type: 'bpmn:SequenceFlow' }); let selectionFlowBo = elementRegistry.get(selectionFlow.id).businessObject; selectionFlowBo.name = 'no'; let selectionFlowCondition = bpmnFactory.create('bpmn:FormalExpression'); @@ -119,26 +119,26 @@ export async function replaceHardwareSelectionSubprocess(subprocess, parent, mod } let selectionTaskBo = elementRegistry.get(selectionTask.id).businessObject; selectionTaskBo.asyncBefore = true; - modeling.connect(invokeHardwareSelection, selectionTask, {type: 'bpmn:SequenceFlow'}); + modeling.connect(invokeHardwareSelection, selectionTask, { type: 'bpmn:SequenceFlow' }); // add task implementing the transformation of the QuantME modeling constructs within the QuantumHardwareSelectionSubprocess console.log('Adding extracted workflow fragment XML: ', hardwareSelectionFragment); - let retrieveFragment = modeling.createShape({type: 'bpmn:ScriptTask'}, {x: 50, y: 50}, element, {}); + let retrieveFragment = modeling.createShape({ type: 'bpmn:ScriptTask' }, { x: 50, y: 50 }, element, {}); let retrieveFragmentBo = elementRegistry.get(retrieveFragment.id).businessObject; retrieveFragmentBo.name = 'Retrieve Fragment to Transform'; retrieveFragmentBo.scriptFormat = 'groovy'; retrieveFragmentBo.script = RETRIEVE_FRAGMENT_SCRIPT_PREFIX + hardwareSelectionFragment + RETRIEVE_FRAGMENT_SCRIPT_SUFFIX; retrieveFragmentBo.asyncBefore = true; - modeling.connect(selectionTask, retrieveFragment, {type: 'bpmn:SequenceFlow'}); + modeling.connect(selectionTask, retrieveFragment, { type: 'bpmn:SequenceFlow' }); // add task implementing the transformation of the QuantME modeling constructs within the QuantumHardwareSelectionSubprocess - let invokeTransformation = modeling.createShape({type: 'bpmn:ScriptTask'}, {x: 50, y: 50}, element, {}); + let invokeTransformation = modeling.createShape({ type: 'bpmn:ScriptTask' }, { x: 50, y: 50 }, element, {}); let invokeTransformationBo = elementRegistry.get(invokeTransformation.id).businessObject; invokeTransformationBo.name = 'Invoke Transformation Framework'; invokeTransformationBo.scriptFormat = 'groovy'; invokeTransformationBo.script = INVOKE_TRANSFORMATION_SCRIPT; invokeTransformationBo.asyncBefore = true; - modeling.connect(retrieveFragment, invokeTransformation, {type: 'bpmn:SequenceFlow'}); + modeling.connect(retrieveFragment, invokeTransformation, { type: 'bpmn:SequenceFlow' }); // add Transformation Framework endpoint as input parameter let invokeTransformationInOut = getCamundaInputOutput(invokeTransformationBo, bpmnFactory); @@ -156,20 +156,20 @@ export async function replaceHardwareSelectionSubprocess(subprocess, parent, mod ); // add task to poll for the results of the transformation and deployment - let pollForTransformation = modeling.createShape({type: 'bpmn:ScriptTask'}, {x: 50, y: 50}, element, {}); + let pollForTransformation = modeling.createShape({ type: 'bpmn:ScriptTask' }, { x: 50, y: 50 }, element, {}); let pollForTransformationBo = elementRegistry.get(pollForTransformation.id).businessObject; pollForTransformationBo.name = 'Poll for Transformation and Deployment'; pollForTransformationBo.scriptFormat = 'groovy'; pollForTransformationBo.script = POLL_FOR_TRANSFORMATION_SCRIPT; pollForTransformationBo.asyncBefore = true; - modeling.connect(invokeTransformation, pollForTransformation, {type: 'bpmn:SequenceFlow'}); + modeling.connect(invokeTransformation, pollForTransformation, { type: 'bpmn:SequenceFlow' }); // join control flow - let joiningGateway = modeling.createShape({type: 'bpmn:ExclusiveGateway'}, {x: 50, y: 50}, element, {}); - modeling.connect(pollForTransformation, joiningGateway, {type: 'bpmn:SequenceFlow'}); + let joiningGateway = modeling.createShape({ type: 'bpmn:ExclusiveGateway' }, { x: 50, y: 50 }, element, {}); + modeling.connect(pollForTransformation, joiningGateway, { type: 'bpmn:SequenceFlow' }); // add connection from splitting to joining gateway and add condition - let alreadySelectedFlow = modeling.connect(splittingGateway, joiningGateway, {type: 'bpmn:SequenceFlow'}); + let alreadySelectedFlow = modeling.connect(splittingGateway, joiningGateway, { type: 'bpmn:SequenceFlow' }); let alreadySelectedFlowBo = elementRegistry.get(alreadySelectedFlow.id).businessObject; alreadySelectedFlowBo.name = 'yes'; let alreadySelectedFlowCondition = bpmnFactory.create('bpmn:FormalExpression'); @@ -177,13 +177,13 @@ export async function replaceHardwareSelectionSubprocess(subprocess, parent, mod alreadySelectedFlowBo.conditionExpression = alreadySelectedFlowCondition; // add call activity invoking the dynamically transformed and deployed workflow fragment - let invokeTransformedFragment = modeling.createShape({type: 'bpmn:CallActivity'}, {x: 50, y: 50}, element, {}); + let invokeTransformedFragment = modeling.createShape({ type: 'bpmn:CallActivity' }, { x: 50, y: 50 }, element, {}); let invokeTransformedFragmentBo = elementRegistry.get(invokeTransformedFragment.id).businessObject; invokeTransformedFragmentBo.name = 'Invoke Transformed Fragment'; invokeTransformedFragmentBo.calledElement = '${fragment_endpoint}'; invokeTransformedFragmentBo.calledElementBinding = 'latest'; invokeTransformedFragmentBo.asyncBefore = true; - modeling.connect(joiningGateway, invokeTransformedFragment, {type: 'bpmn:SequenceFlow'}); + modeling.connect(joiningGateway, invokeTransformedFragment, { type: 'bpmn:SequenceFlow' }); // pass all variables between the caller and callee workflow addExtensionElements(invokeTransformedFragment, invokeTransformedFragmentBo, bpmnFactory.create('camunda:In'), bpmnFactory, commandStack); @@ -196,10 +196,10 @@ export async function replaceHardwareSelectionSubprocess(subprocess, parent, mod invokeTransformedFragmentBo.extensionElements = extensionElements; // add end event for the new subprocess - let endEvent = modeling.createShape({type: 'bpmn:EndEvent'}, {x: 50, y: 50}, element, {}); + let endEvent = modeling.createShape({ type: 'bpmn:EndEvent' }, { x: 50, y: 50 }, element, {}); let endEventBo = elementRegistry.get(endEvent.id).businessObject; endEventBo.name = 'Terminate Hardware Selection Subprocess'; - modeling.connect(invokeTransformedFragment, endEvent, {type: 'bpmn:SequenceFlow'}); + modeling.connect(invokeTransformedFragment, endEvent, { type: 'bpmn:SequenceFlow' }); return true; } @@ -220,7 +220,7 @@ export async function configureBasedOnHardwareSelection(xml, provider, qpu, circ const rootElement = getRootProcess(modeler.getDefinitions()); 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!' }; } rootElement.isExecutable = true; @@ -243,7 +243,7 @@ export async function configureBasedOnHardwareSelection(xml, provider, qpu, circ } } - return {status: 'success', xml: await getXml(modeler)}; + return { status: 'success', xml: await getXml(modeler) }; } /** @@ -270,7 +270,7 @@ function addSelectionStrategyTask(selectionStrategy, parent, elementRegistry, mo * Add a task implementing the Shortest-Queue selection strategy */ function addShortestQueueSelectionStrategy(parent, elementRegistry, modeling) { - let task = modeling.createShape({type: 'bpmn:ScriptTask'}, {x: 50, y: 50}, parent, {}); + let task = modeling.createShape({ type: 'bpmn:ScriptTask' }, { x: 50, y: 50 }, parent, {}); let taskBo = elementRegistry.get(task.id).businessObject; taskBo.name = 'Selecting based on Queue Size'; taskBo.scriptFormat = 'groovy'; @@ -282,7 +282,7 @@ async function getHardwareSelectionFragment(subprocess) { console.log('Extracting workflow fragment from subprocess: ', subprocess); // create new modeler to extract the XML of the workflow fragment - let modeler = createPlainModeler(); + let modeler = createModeler(); let elementRegistry = modeler.get('elementRegistry'); let bpmnReplace = modeler.get('bpmnReplace'); let modeling = modeler.get('modeling'); @@ -304,13 +304,13 @@ async function getHardwareSelectionFragment(subprocess) { let rootElementBo = elementRegistry.get(rootElement.id); // add start and end event to the new process - let startEvent = bpmnReplace.replaceElement(elementRegistry.get(rootElement.flowElements[0].id), {type: 'bpmn:StartEvent'}); - let endEvent = modeling.createShape({type: 'bpmn:EndEvent'}, {x: 50, y: 50}, rootElementBo, {}); + let startEvent = bpmnReplace.replaceElement(elementRegistry.get(rootElement.flowElements[0].id), { type: 'bpmn:StartEvent' }); + let endEvent = modeling.createShape({ type: 'bpmn:EndEvent' }, { x: 50, y: 50 }, rootElementBo, {}); // insert given subprocess and connect to start and end event let insertedSubprocess = insertShape(definitions, rootElementBo, subprocess, {}, false, modeler).element; - modeling.connect(startEvent, insertedSubprocess, {type: 'bpmn:SequenceFlow'}); - modeling.connect(insertedSubprocess, endEvent, {type: 'bpmn:SequenceFlow'}); + modeling.connect(startEvent, insertedSubprocess, { type: 'bpmn:SequenceFlow' }); + modeling.connect(insertedSubprocess, endEvent, { type: 'bpmn:SequenceFlow' }); // export xml and remove line breaks let xml = await getXml(modeler); From e0519e14999bcc041c3ffcf0ebbb67d9d06fdfeb Mon Sep 17 00:00:00 2001 From: LaviniaStiliadou Date: Mon, 21 Aug 2023 14:52:47 +0200 Subject: [PATCH 3/7] feature plugin dependency & auto save (#57) * reorganize config, add option to specify plugin dependencies Co-Authored-By: SharonNaemi <49727936+SharonNaemi@users.noreply.github.com> * integrate auto save * set interval size with environment variable * restore transformation button * add missing imports * adapt test * add doc for plugin dependencies * add overview to env variables * add doc for auto save * fix test * integrate upload into github tab * Small fixes * Remove redundant declaration * Small readme fixes * Rename timeout interval environment variable * Refactor QuantMETab * Remove deployment plugin * Rename QrmTab to GitHubTab * enable config of auto save interval size * fix interval implementation * only save xml in interval if xml changed * fix test * adjust autosave filename * reference doc in config --------- Co-authored-by: SharonNaemi <49727936+SharonNaemi@users.noreply.github.com> Co-authored-by: SharonNaemi Co-authored-by: Benjamin Weder Co-authored-by: mbeisel --- .../QuantumWorkflowModeler.js | 3 +- .../editor/EditorConstants.js | 9 +- .../editor/config/EditorConfigManager.js | 56 +++- .../config/{EditorTab.js => GeneralTab.js} | 40 ++- .../editor/plugin/PluginHandler.js | 80 +++-- .../editor/util/IoUtilities.js | 57 +++- .../data-extension/DataFlowPlugin.js | 2 +- .../rules/DataFlowRulesProvider.js | 32 +- .../extensions/qhana/QHAnaPlugin.js | 2 +- .../extensions/quantme/QuantMEPlugin.js | 63 +--- .../quantme/configTabs/BPMNConfigTab.js | 107 ------- .../configTabs/{UploadTab.js => GitHubTab.js} | 103 ++++++- .../quantme/configTabs/HybridRuntimeTab.js | 114 -------- .../quantme/configTabs/NisqAnalyzerTab.js | 58 ---- .../quantme/configTabs/OpenToscaTab.js | 81 ------ .../quantme/configTabs/QrmDataTab.js | 126 -------- .../quantme/configTabs/QuantMETab.js | 273 ++++++++++++++++++ .../quantme/framework-config/config.js | 3 +- .../bpmn-q/test/tests/editor/plugin.spec.js | 2 +- components/bpmn-q/webpack.config.js | 11 +- .../extensions/plugins.md | 57 ++++ .../modeler-configuration.md | 53 ++++ 22 files changed, 729 insertions(+), 603 deletions(-) rename components/bpmn-q/modeler-component/editor/config/{EditorTab.js => GeneralTab.js} (71%) delete mode 100644 components/bpmn-q/modeler-component/extensions/quantme/configTabs/BPMNConfigTab.js rename components/bpmn-q/modeler-component/extensions/quantme/configTabs/{UploadTab.js => GitHubTab.js} (53%) delete mode 100644 components/bpmn-q/modeler-component/extensions/quantme/configTabs/HybridRuntimeTab.js delete mode 100644 components/bpmn-q/modeler-component/extensions/quantme/configTabs/NisqAnalyzerTab.js delete mode 100644 components/bpmn-q/modeler-component/extensions/quantme/configTabs/OpenToscaTab.js delete mode 100644 components/bpmn-q/modeler-component/extensions/quantme/configTabs/QrmDataTab.js create mode 100644 components/bpmn-q/modeler-component/extensions/quantme/configTabs/QuantMETab.js create mode 100644 doc/quantum-workflow-modeler/extensions/plugins.md create mode 100644 doc/quantum-workflow-modeler/modeler-configuration.md diff --git a/components/bpmn-q/modeler-component/QuantumWorkflowModeler.js b/components/bpmn-q/modeler-component/QuantumWorkflowModeler.js index ce9da905..26908445 100644 --- a/components/bpmn-q/modeler-component/QuantumWorkflowModeler.js +++ b/components/bpmn-q/modeler-component/QuantumWorkflowModeler.js @@ -12,7 +12,7 @@ import './modeler.css'; import React from 'react'; import { createRoot } from 'react-dom/client'; import ButtonToolbar from "./editor/ui/ButtonToolbar"; -import { createNewDiagram, loadDiagram } from "./editor/util/IoUtilities"; +import { createNewDiagram, loadDiagram, setAutoSaveInterval } from "./editor/util/IoUtilities"; import NotificationHandler from "./editor/ui/notifications/NotificationHandler"; import { createModeler, getModeler } from "./editor/ModelerHandler"; import { getPluginButtons, getTransformationButtons } from "./editor/plugin/PluginHandler"; @@ -335,6 +335,7 @@ export class QuantumWorkflowModeler extends HTMLElement { // restart modeler to apply plugin config when shadow dom is rendered requestAnimationFrame(() => { this.startModeler(); + setAutoSaveInterval(); }); } } diff --git a/components/bpmn-q/modeler-component/editor/EditorConstants.js b/components/bpmn-q/modeler-component/editor/EditorConstants.js index 3ede37e5..288375b2 100644 --- a/components/bpmn-q/modeler-component/editor/EditorConstants.js +++ b/components/bpmn-q/modeler-component/editor/EditorConstants.js @@ -10,13 +10,18 @@ export const workflowEventTypes = { LOADED: 'quantum-workflow-loaded', // New Workflow loaded in modeler SAVED: 'quantum-workflow-saved', // Workflow saved TRANSFORMED: 'quantum-workflow-transformed', // Workflow transformed - DEPLOYED: 'quantum-workflow-deployed' // Workflow deployed to workflow engine + DEPLOYED: 'quantum-workflow-deployed', // Workflow deployed to workflow engine }; +export const autoSaveFile = { + INTERVAL: 'Interval', + ON_ACTION: 'On Action' +} + // supported save file options export const saveFileFormats = { ALL: 'all', BPMN: '.bpmn', PNG: '.png', SVG: '.svg' -}; \ No newline at end of file +}; diff --git a/components/bpmn-q/modeler-component/editor/config/EditorConfigManager.js b/components/bpmn-q/modeler-component/editor/config/EditorConfigManager.js index 1339fedd..43b7baf2 100644 --- a/components/bpmn-q/modeler-component/editor/config/EditorConfigManager.js +++ b/components/bpmn-q/modeler-component/editor/config/EditorConfigManager.js @@ -1,12 +1,14 @@ import { getPluginConfig } from '../plugin/PluginConfigHandler'; -import { saveFileFormats, transformedWorkflowHandlers } from '../EditorConstants'; +import { saveFileFormats, transformedWorkflowHandlers, autoSaveFile } from '../EditorConstants'; // default configurations of the editor const defaultConfig = { camundaEndpoint: process.env.CAMUNDA_ENDPOINT, fileName: process.env.DOWNLOAD_FILE_NAME, transformedWorkflowHandler: transformedWorkflowHandlers.NEW_TAB, - fileFormat: saveFileFormats.BPMN + autoSaveFileOption: autoSaveFile.INTERVAL, + fileFormat: saveFileFormats.BPMN, + autoSaveIntervalSize: process.env.AUTOSAVE_INTERVAL }; let config = {}; @@ -90,6 +92,33 @@ export function setTransformedWorkflowHandler(transformedWorkflowHandler) { } /** + * Get the id of the handler to handle auto save of files. + * + * @return {string} the currently specified handler id + */ +export function getAutoSaveFileOption() { + if (config.autoSaveFileOption === undefined) { + const autoSaveFileOption = autoSaveFile[getPluginConfig('editor').autoSaveFileOption]; + setAutoSaveFileOption(autoSaveFileOption || defaultConfig.autoSaveFileOption); + } + return config.autoSaveFileOption; +} + +/** + * Set the id of the handler to handle auto save of files + * + * @param autoSaveFileOption the id of the transformed workflow handler + */ +export function setAutoSaveFileOption(autoSaveFileOption) { + if (autoSaveFileOption !== null && autoSaveFileOption !== undefined + // check that the new value is a valid handler id + && Object.values(autoSaveFile).includes(autoSaveFileOption)) { + + config.autoSaveFileOption = autoSaveFileOption; + } +} + +/** * Get the file format * * @return {string} the currently specified handler id @@ -116,6 +145,29 @@ export function setFileFormat(fileFormat) { } } +/** + * Get the autosave interval size + * + * @return {string} the current interval size + */ +export function getAutoSaveIntervalSize() { + if (config.autoSaveIntervalSize === undefined) { + setAutoSaveIntervalSize(getPluginConfig('editor').autoSaveIntervalSize || defaultConfig.autoSaveIntervalSize); + } + return config.autoSaveIntervalSize; +} + +/** + * Set the interval size of the autosave function + * + * @param intervalSize the interval size + */ +export function setAutoSaveIntervalSize(intervalSize) { + if (intervalSize !== null && intervalSize !== undefined) { + config.autoSaveIntervalSize = intervalSize; + } +} + /** * Resets the current editor configs */ diff --git a/components/bpmn-q/modeler-component/editor/config/EditorTab.js b/components/bpmn-q/modeler-component/editor/config/GeneralTab.js similarity index 71% rename from components/bpmn-q/modeler-component/editor/config/EditorTab.js rename to components/bpmn-q/modeler-component/editor/config/GeneralTab.js index 38ed8bc6..4f6f822a 100644 --- a/components/bpmn-q/modeler-component/editor/config/EditorTab.js +++ b/components/bpmn-q/modeler-component/editor/config/GeneralTab.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { getModeler } from "../ModelerHandler"; import * as editorConfig from "./EditorConfigManager"; -import { transformedWorkflowHandlers, saveFileFormats } from '../EditorConstants'; +import { autoSaveFile, saveFileFormats, transformedWorkflowHandlers } from '../EditorConstants'; /** * Tab for the ConfigModal. Used to allow the configurations of the editor configs, namely the camunda endpoint and the @@ -14,8 +14,11 @@ export default function EditorTab() { const [camundaEndpoint, setCamundaEndpoint] = useState(editorConfig.getCamundaEndpoint()); const [workflowHandler, setWorkflowHandler] = useState(editorConfig.getTransformedWorkflowHandler()); + const [autoSaveFileOption, setAutoSaveFileOption] = useState(editorConfig.getAutoSaveFileOption()); const [fileName, setFileName] = useState(editorConfig.getFileName()); const [fileFormat, setFileFormat] = useState(editorConfig.getFileFormat()); + const [autoSaveIntervalSize, setAutoSaveIntervalSize] = useState(editorConfig.getAutoSaveIntervalSize()); + const modeler = getModeler(); @@ -45,8 +48,11 @@ export default function EditorTab() { modeler.config.fileName = fileName; editorConfig.setCamundaEndpoint(camundaEndpoint); editorConfig.setTransformedWorkflowHandler(workflowHandler); + editorConfig.setAutoSaveFileOption(autoSaveFileOption); + modeler.get('eventBus').fire('autoSaveOptionChanged', { autoSaveFileOption }); editorConfig.setFileName(fileName); editorConfig.setFileFormat(fileFormat); + editorConfig.setAutoSaveIntervalSize(autoSaveIntervalSize); }; // return tab which contains entries to change the camunda endpoint and the workflow handler @@ -118,6 +124,38 @@ export default function EditorTab() { +

Auto save file:

+ + + + + + + {autoSaveFileOption === autoSaveFile.INTERVAL && ( + + + + + )} + +
Auto save file option: + +
Auto save interval size: + setAutoSaveIntervalSize(event.target.value)} /> +
); } diff --git a/components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js b/components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js index 2b45efb1..cc1d95fd 100644 --- a/components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js +++ b/components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js @@ -1,9 +1,10 @@ -import PlanQKPlugin from "../../extensions/planqk/PlanQKPlugin"; -import QuantMEPlugin from "../../extensions/quantme/QuantMEPlugin"; +import PlanQKPlugin from '../../extensions/planqk/PlanQKPlugin'; +import QuantMEPlugin from '../../extensions/quantme/QuantMEPlugin'; import DataFlowPlugin from '../../extensions/data-extension/DataFlowPlugin'; import QHAnaPlugin from '../../extensions/qhana/QHAnaPlugin'; -import {getAllConfigs} from "./PluginConfigHandler"; -import EditorTab from "../config/EditorTab"; +import { getAllConfigs } from './PluginConfigHandler'; +import GeneralTab from '../config/GeneralTab'; +import GitHubTab from '../../extensions/quantme/configTabs/GitHubTab'; /** * Handler for plugins of the modeler. Controls active plugins and the properties they define. Central access point to @@ -11,50 +12,65 @@ import EditorTab from "../config/EditorTab"; */ // list of plugins integrated in the modeler, register new plugins here +// dependencies can be specified by the name of the corresponding plugins const PLUGINS = [ - DataFlowPlugin, - QHAnaPlugin, - PlanQKPlugin, - QuantMEPlugin, + { + plugin: QuantMEPlugin, + dependencies: [] + }, + { + plugin: DataFlowPlugin, + dependencies: [] + }, + { + plugin: QHAnaPlugin, + dependencies: [] + }, + { + plugin: PlanQKPlugin, + dependencies: [] + } ]; // list of currently active plugins in the current running instance of the modeler, defined based on the plugin configuration let activePlugins = []; -/** - * Returns these plugins of PLUGINS which have an entry in the current plugin configuration of the modeler. - * - * @returns {*[]} Array of active plugins. - */ export function getActivePlugins() { - - // return saved active plugins array if (activePlugins.length > 0) { return activePlugins; - - // determine active plugins } else { - activePlugins = []; - - let plugin; - - // add all plugins of PLUGINS to active plugins which have a config entry for them - for (let pluginConfig of getAllConfigs()) { - - plugin = PLUGINS.find(plugin => plugin.name === pluginConfig.name && checkEnabledStatus(plugin.name)); + const loadPlugin = (plugin) => { + if (!activePlugins.includes(plugin.plugin)) { + for (const dependency of plugin.dependencies) { + const dependencyPlugin = PLUGINS.find((p) => p.plugin.name === dependency); + if (dependencyPlugin && !activePlugins.includes(dependencyPlugin.plugin)) { + activePlugins.push(dependencyPlugin.plugin); + loadPlugin(dependencyPlugin); + } + } + activePlugins.push(plugin.plugin); + } + }; + for (const pluginConfig of getAllConfigs()) { + const plugin = PLUGINS.find( + (p) => p.plugin.name === pluginConfig.name && checkEnabledStatus(p.plugin.name) + ); if (plugin) { - activePlugins.push(plugin); + loadPlugin(plugin); } } + return activePlugins; } } + + export function checkEnabledStatus(pluginName) { - switch(pluginName) { + switch (pluginName) { case 'dataflow': return process.env.ENABLE_DATA_FLOW_PLUGIN; case 'planqk': @@ -179,13 +195,17 @@ export function getConfigTabs() { // add default editor tab to configure editor configs let configTabs = [{ tabId: 'EditorTab', - tabTitle: 'Editor', - configTab: EditorTab, + tabTitle: 'General', + configTab: GeneralTab, + }, { + tabId: 'GitHubTab', + tabTitle: 'GitHub', + configTab: GitHubTab, }]; // load the config tabs of the active plugins into one array for (let plugin of getActivePlugins()) { - if (plugin.configTabs) { + if (plugin.configTabs && checkEnabledStatus(plugin.name)) { configTabs = configTabs.concat(plugin.configTabs); } } diff --git a/components/bpmn-q/modeler-component/editor/util/IoUtilities.js b/components/bpmn-q/modeler-component/editor/util/IoUtilities.js index db34386b..425f3a36 100644 --- a/components/bpmn-q/modeler-component/editor/util/IoUtilities.js +++ b/components/bpmn-q/modeler-component/editor/util/IoUtilities.js @@ -1,11 +1,11 @@ -import { file } from 'jszip'; -import { transformedWorkflowHandlers, workflowEventTypes, saveFileFormats } from '../EditorConstants'; -import { dispatchWorkflowEvent } from '../events/EditorEventHandler'; +import { autoSaveFile, saveFileFormats, transformedWorkflowHandlers, workflowEventTypes } from "../EditorConstants"; +import { getModeler } from "../ModelerHandler"; +import { dispatchWorkflowEvent } from "../events/EditorEventHandler"; +import fetch from "node-fetch"; const editorConfig = require('../config/EditorConfigManager'); let FormData = require('form-data'); -import fetch from 'node-fetch'; // workflow with a start event to use as template for new workflows const NEW_DIAGRAM_XML = '\n' + @@ -47,18 +47,19 @@ export async function saveXmlAsLocalFile(xml, fileName = editorConfig.getFileNam * @param fileName The name of the file. * @returns {Promise} */ -export async function saveModelerAsLocalFile(modeler, fileName = editorConfig.getFileName(), fileFormat = editorConfig.getFileFormat()) { +export async function saveModelerAsLocalFile(modeler, fileName = editorConfig.getFileName(), fileFormat = editorConfig.getFileFormat(), openWindow = true) { const xml = await getXml(modeler); - if (fileFormat === saveFileFormats.BPMN || fileFormat === saveFileFormats.ALL) { - await openFileDialog(xml, fileName, saveFileFormats.BPMN); + if (openWindow) { + await openFileDialog(xml, fileName, saveFileFormats.BPMN); + } else { + await saveXmlAsLocalFile(xml, fileName); + } } if (fileFormat === saveFileFormats.ALL || fileFormat === saveFileFormats.SVG || fileFormat === saveFileFormats.PNG) { await saveWorkflowAsSVG(modeler, fileName, fileFormat); } - - return; } /** @@ -231,6 +232,36 @@ export function openInNewTab(workflowXml, fileName) { }; } +export function setAutoSaveInterval(autoSaveFileOption = editorConfig.getAutoSaveFileOption()) { + if (autoSaveFileOption === autoSaveFile.INTERVAL) { + getModeler().autosaveIntervalId = setInterval(() => { saveFile(); }, editorConfig.getAutoSaveIntervalSize()); + } else { + saveFile(); + } +} + +export function saveFile() { + // extract the xml and save it to a file + getModeler().saveXML({ format: true }, function (err, xml) { + if (!err) { + let oldXml = getModeler().oldXml; + if (oldXml !== xml && oldXml !== undefined) { + // Save the XML + console.log('Autosaved:', xml); + getModeler().oldXml = xml; + const timestamp = getTimestamp(); + const filename = `${editorConfig.getFileName().replace('.bpmn','')}_autosave_${timestamp}`; + saveXmlAsLocalFile(xml, filename); + } + } + }); +} + +function getTimestamp() { + const date = new Date(); + return date.toISOString().replace(/:/g, '-'); +} + export async function saveWorkflowAsSVG(modeler, fileName, fileFormat) { modeler.saveSVG({ format: true }, function (error, svg) { if (error) { @@ -269,8 +300,8 @@ function downloadPng(pngDataUrl, fileName, fileFormat) { async function openFileDialog(content, fileName, fileFormat) { let suggestedName = fileName; if (suggestedName.includes('.bpmn')) { - suggestedName = fileName.split('.bpmn')[0]; - } + suggestedName = fileName.split('.bpmn')[0]; + } let fileHandle = await window.showSaveFilePicker({ startIn: 'downloads', suggestedName: suggestedName + fileFormat, types: [ { @@ -289,8 +320,8 @@ async function openFileDialog(content, fileName, fileFormat) { async function openFileUrlDialog(content, fileName, fileFormat) { let suggestedName = fileName; if (suggestedName.includes('.bpmn')) { - suggestedName = fileName.split('.bpmn')[0]; - } + suggestedName = fileName.split('.bpmn')[0]; + } let fileHandle = await window.showSaveFilePicker({ startIn: 'downloads', suggestedName: suggestedName + fileFormat, types: [ { diff --git a/components/bpmn-q/modeler-component/extensions/data-extension/DataFlowPlugin.js b/components/bpmn-q/modeler-component/extensions/data-extension/DataFlowPlugin.js index 0f458134..d4a31260 100644 --- a/components/bpmn-q/modeler-component/extensions/data-extension/DataFlowPlugin.js +++ b/components/bpmn-q/modeler-component/extensions/data-extension/DataFlowPlugin.js @@ -25,7 +25,7 @@ export default { configTabs: [ { tabId: 'DataEndpointsTab', - tabTitle: 'Data Endpoints', + tabTitle: 'Data Flow Plugin', configTab: TransformationTaskConfigurationsTab, }, ], diff --git a/components/bpmn-q/modeler-component/extensions/data-extension/rules/DataFlowRulesProvider.js b/components/bpmn-q/modeler-component/extensions/data-extension/rules/DataFlowRulesProvider.js index 63cb439a..55037fc3 100644 --- a/components/bpmn-q/modeler-component/extensions/data-extension/rules/DataFlowRulesProvider.js +++ b/components/bpmn-q/modeler-component/extensions/data-extension/rules/DataFlowRulesProvider.js @@ -5,8 +5,11 @@ import { } from 'bpmn-js/lib/features/modeling/util/ModelingUtil'; import * as consts from '../Constants'; import { isConnectedWith } from '../../../editor/util/ModellingUtilities'; +import { saveFile, setAutoSaveInterval } from '../../../editor/util/IoUtilities'; import { getModeler } from '../../../editor/ModelerHandler'; import ace from 'ace-builds'; +import * as editorConfig from "../../../editor/config/EditorConfigManager"; +import { autoSaveFile } from '../../../editor/EditorConstants'; /** * Custom rules provider for the DataFlow elements. Extends the BpmnRules. @@ -20,8 +23,7 @@ export default class CustomRulesProvider extends BpmnRules { const canConnect = this.canConnect.bind(this); const canCreate = this.canCreate.bind(this); - // persist into local storage whenever - // copy took place + // persist into local storage whenever copy took place eventBus.on('copyPaste.elementsCopied', event => { const { tree } = event; @@ -68,21 +70,41 @@ export default class CustomRulesProvider extends BpmnRules { ); }); + // save every change when the autosave option is on action + eventBus.on("commandStack.changed", function () { + if (editorConfig.getAutoSaveFileOption() === autoSaveFile.ON_ACTION) { + saveFile(); + } + }); + + // remove interval when autosave option is on action + eventBus.on("autoSaveOptionChanged", function (context) { + if (context.autoSaveFileOption === autoSaveFile.ON_ACTION) { + clearInterval(getModeler().autosaveIntervalId); + } else { + setAutoSaveInterval(); + } + }); + // update xml viewer on diagram change eventBus.on("commandStack.changed", function () { let editor = document.getElementById('editor'); let aceEditor = ace.edit(editor); let modeler = getModeler(); if (modeler) { + if (modeler.xml !== undefined) { + modeler.oldXml = getModeler().xml; + if (getModeler().xml.xml !== undefined) + modeler.oldXml = getModeler().xml.xml; + } modeler.saveXML({ format: true }).then(function (result) { - if (result.xml != undefined) { + if (result.xml !== undefined) { result = result.xml; } aceEditor.setValue(result); - }) + }); } }); - } /** diff --git a/components/bpmn-q/modeler-component/extensions/qhana/QHAnaPlugin.js b/components/bpmn-q/modeler-component/extensions/qhana/QHAnaPlugin.js index 4219772f..63a86d72 100644 --- a/components/bpmn-q/modeler-component/extensions/qhana/QHAnaPlugin.js +++ b/components/bpmn-q/modeler-component/extensions/qhana/QHAnaPlugin.js @@ -23,7 +23,7 @@ export default { configTabs: [ { tabId: 'QHAnaEndpointsTab', - tabTitle: 'QHAna Endpoints', + tabTitle: 'QHAna Plugin', configTab: QHAnaConfigurationsTab, }, ], diff --git a/components/bpmn-q/modeler-component/extensions/quantme/QuantMEPlugin.js b/components/bpmn-q/modeler-component/extensions/quantme/QuantMEPlugin.js index 7a99ad35..f1574810 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/QuantMEPlugin.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/QuantMEPlugin.js @@ -1,21 +1,14 @@ -import React from "react"; - -import QuantMEExtensionModule from "./modeling"; -import BPMNConfigTab from "./configTabs/BPMNConfigTab"; -import OpenToscaTab from "./configTabs/OpenToscaTab"; -import NisqAnalyzerTab from "./configTabs/NisqAnalyzerTab"; -import QrmDataTab from "./configTabs/QrmDataTab"; -import HybridRuntimeTab from "./configTabs/HybridRuntimeTab"; -import UploadTab from "./configTabs/UploadTab"; -import {getQRMs} from "./qrm-manager"; -import {startQuantmeReplacementProcess} from "./replacement/QuantMETransformator"; -import * as camundaConfig from "../../editor/config/EditorConfigManager"; -import * as config from "./framework-config/config-manager"; -import TransformationButton from "../../editor/ui/TransformationButton"; -import DataObjectConfigurationsTab from './configurations/DataObjectConfigurationsTab'; +import React from 'react'; +import QuantMEExtensionModule from './modeling'; +import QuantMETab from './configTabs/QuantMETab'; +import {getQRMs} from './qrm-manager'; +import {startQuantmeReplacementProcess} from './replacement/QuantMETransformator'; +import * as camundaConfig from '../../editor/config/EditorConfigManager'; +import * as config from './framework-config/config-manager'; +import TransformationButton from '../../editor/ui/TransformationButton'; import quantMEStyles from './styling/quantme.css'; -import QuantMEPluginButton from "./ui/QuantMEPluginButton"; +import QuantMEPluginButton from './ui/QuantMEPluginButton'; let quantMEModdleExtension = require('./resources/quantum4bpmn.json'); @@ -23,42 +16,12 @@ let quantMEModdleExtension = require('./resources/quantum4bpmn.json'); * Plugin Object of the QuantME extension. Used to register the plugin in the plugin handler of the modeler. */ export default { - buttons: [], + buttons: [], configTabs: [ - { - tabId: 'DataConfigurationEndpointTab', - tabTitle: 'QuantME Data', - configTab: DataObjectConfigurationsTab, - }, - { - tabId: 'OpenTOSCAEndpointTab', - tabTitle: 'OpenTOSCA', - configTab: OpenToscaTab, - }, { tabId: 'BPMNTab', - tabTitle: 'Workflow', - configTab: BPMNConfigTab, - }, - { - tabId: 'NISQAnalyzerEndpointTab', - tabTitle: 'NISQ Analyzer', - configTab: NisqAnalyzerTab, - }, - { - tabId: 'QRMDataTab', - tabTitle: 'QRM Data', - configTab: QrmDataTab, - }, - { - tabId: 'HybridRuntimesTab', - tabTitle: 'Hybrid Runtimes', - configTab: HybridRuntimeTab, - }, - { - tabId: 'UploadTab', - tabTitle: 'Upload data', - configTab: UploadTab, + tabTitle: 'QuantME Plugin', + configTab: QuantMETab, } ], name: 'quantme', @@ -77,5 +40,5 @@ export default { } ); } - }/>, + }/> }; \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/BPMNConfigTab.js b/components/bpmn-q/modeler-component/extensions/quantme/configTabs/BPMNConfigTab.js deleted file mode 100644 index ac63d075..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/BPMNConfigTab.js +++ /dev/null @@ -1,107 +0,0 @@ -import React, {useState} from 'react'; -import {getModeler} from "../../../editor/ModelerHandler"; -import * as config from "../framework-config/config-manager"; - -/** - * React component specifying a tab for the configuration dialog of the modeler. The tab allows the user to change workflow - * related configuration entries of the QuantME configs. - * - * @return {JSX.Element} The tab as a React component - * @constructor - */ -export default function BPMNConfigTab() { - - const [transformationFrameworkEndpoint, setTransformationFrameworkEndpoint] = useState(config.getTransformationFrameworkEndpoint()); - const [scriptSplitterEndpoint, setScriptSplitterEndpoint] = useState(config.getScriptSplitterEndpoint()); - const [scriptSplitterThreshold, setScriptSplitterThreshold] = useState(config.getScriptSplitterThreshold()); - - const modeler = getModeler(); - - const editorActions = modeler.get('editorActions'); - const eventBus = modeler.get('eventBus'); - - // register editor action listener for changes in config entries - if (!editorActions._actions.hasOwnProperty('transformationFrameworkEndpointChanged')) { - editorActions.register({ - transformationFrameworkEndpointChanged: function (transformationFrameworkEndpoint) { - modeler.config.transformationFrameworkEndpoint = transformationFrameworkEndpoint; - } - }); - } - if (!editorActions._actions.hasOwnProperty('scriptSplitterEndpointChanged')) { - editorActions.register({ - scriptSplitterEndpointChanged: function (scriptSplitterEndpoint) { - modeler.config.scriptSplitterEndpoint = scriptSplitterEndpoint; - eventBus.fire('config.updated', self.modeler.config); - } - }); - } - if (!editorActions._actions.hasOwnProperty('scriptSplitterThresholdChanged')) { - editorActions.register({ - scriptSplitterThresholdChanged: function (scriptSplitterEndpoint) { - modeler.config.scriptSplitterThreshold = scriptSplitterEndpoint; - } - }); - } - - // save changed config entries on close - BPMNConfigTab.prototype.onClose = () => { - modeler.config.transformationFrameworkEndpoint = transformationFrameworkEndpoint; - modeler.config.scriptSplitterEndpoint = scriptSplitterEndpoint; - modeler.config.scriptSplitterThreshold = scriptSplitterThreshold; - config.setTransformationFrameworkEndpoint(transformationFrameworkEndpoint); - config.setScriptSplitterEndpoint(scriptSplitterEndpoint); - config.setScriptSplitterThreshold(scriptSplitterThreshold); - }; - - return <> -

BPMN related configurations:

- - - - - - - -
QuantME Framework Endpoint - setTransformationFrameworkEndpoint(event.target.value)}/> -
-

Workflow generation:

- - - - - - - - - - - -
Script Splitter Endpoint - setScriptSplitterEndpoint(event.target.value)}/> -
Script Splitter Threshold - setScriptSplitterThreshold(event.target.value)}/> -
- ; -} - -BPMNConfigTab.prototype.config = () => { - const modeler = getModeler(); - - modeler.config.transformationFrameworkEndpoint = config.getTransformationFrameworkEndpoint(); - modeler.config.scriptSplitterEndpoint = config.getScriptSplitterEndpoint(); - modeler.config.scriptSplitterThreshold = config.getScriptSplitterThreshold(); -}; \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/UploadTab.js b/components/bpmn-q/modeler-component/extensions/quantme/configTabs/GitHubTab.js similarity index 53% rename from components/bpmn-q/modeler-component/extensions/quantme/configTabs/UploadTab.js rename to components/bpmn-q/modeler-component/extensions/quantme/configTabs/GitHubTab.js index 66624d03..e99c95f8 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/UploadTab.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/configTabs/GitHubTab.js @@ -9,8 +9,12 @@ import * as config from "../framework-config/config-manager"; * @return {JSX.Element} The tab as a React component * @constructor */ -export default function UploadTab() { +export default function GitHubTab() { + const [githubRepositoryName, setGithubRepositoryName] = useState(config.getQRMRepositoryName()); + const [githubUsername, setGithubUsername] = useState(config.getQRMRepositoryUserName()); + const [githubRepositoryPath, setGithubRepositoryPath] = useState(config.getQRMRepositoryPath()); + const [githubToken, setGitHubToken] = useState(config.getGitHubToken()); const [uploadGithubRepositoryName, setUploadGithubRepositoryName] = useState(config.getUploadGithubRepositoryName()); const [uploadGithubOwner, setUploadGithubOwner] = useState(config.getUploadGithubRepositoryOwner()); const [uploadFileName, setUploadFileName] = useState(config.getUploadFileName()); @@ -20,6 +24,35 @@ export default function UploadTab() { const editorActions = modeler.get('editorActions'); // register editor action listener for changes in config entries + if (!editorActions._actions.hasOwnProperty('qrmRepoNameChanged')) { + editorActions.register({ + qrmRepoNameChanged: function (qrmRepoName) { + self.modeler.config.githubRepositoryName = qrmRepoName; + } + }); + } + if (!editorActions._actions.hasOwnProperty('qrmUserNameChanged')) { + editorActions.register({ + qrmUserNameChanged: function (qrmUserName) { + self.modeler.config.githubUsername = qrmUserName; + } + }); + } + if (!editorActions._actions.hasOwnProperty('qrmRepoPathChanged')) { + editorActions.register({ + qrmRepoPathChanged: function (qrmRepoPath) { + self.modeler.config.githubRepositoryPath = qrmRepoPath; + } + }); + } + if (!editorActions._actions.hasOwnProperty('githubTokenChanged')) { + editorActions.register({ + githubTokenChanged: function (githubToken) { + self.modeler.config.githubToken = githubToken; + } + }); + } + if (!editorActions._actions.hasOwnProperty('uploadGithubRepositoryNameChanged')) { editorActions.register({ uploadGithubRepositoryNameChanged: function (uploadGithubRepositoryName) { @@ -51,7 +84,11 @@ export default function UploadTab() { } // save changed config entries on close - UploadTab.prototype.onClose = () => { + GitHubTab.prototype.onClose = () => { + modeler.config.githubRepositoryName = githubRepositoryName; + modeler.config.githubUsername = githubUsername; + modeler.config.githubRepositoryPath = githubRepositoryPath; + modeler.config.githubToken = githubToken; modeler.config.uploadGithubRepositoryName = uploadGithubRepositoryName; modeler.config.uploadGithubRepositoryOwner = uploadGithubOwner; modeler.config.uploadFileName = uploadFileName; @@ -61,10 +98,63 @@ export default function UploadTab() { config.setUploadGithubRepositoryOwner(uploadGithubOwner); config.setUploadFileName(uploadFileName); config.setUploadBranchName(uploadBranchName); - + config.setQRMRepositoryName(githubRepositoryName); + config.setQRMUserName(githubUsername); + config.setQRMRepositoryPath(githubRepositoryPath); + config.setGitHubToken(githubToken); }; return <> +

QRM Data

+ + + + + + + + + + + + + + + +
QRM Repository User: + setGithubUsername(event.target.value)}/> +
QRM Repository Name: + setGithubRepositoryName(event.target.value)}/> +
QRM Repository Path: + setGithubRepositoryPath(event.target.value)}/> +
+

GitHub Authentication

+ + + + + + + +
GitHub Token [1]: + setGitHubToken(event.target.value)}/> +

Upload Data

@@ -113,9 +203,14 @@ export default function UploadTab() { ; } -UploadTab.prototype.config = () => { +GitHubTab.prototype.config = () => { const modeler = getModeler(); + modeler.config.githubRepositoryName = config.getQRMRepositoryName(); + modeler.config.githubUsername = config.getQRMRepositoryUserName(); + modeler.config.githubRepositoryPath = config.getQRMRepositoryPath(); + modeler.config.githubToken = config.getGitHubToken(); + modeler.config.uploadGithubRepositoryName = config.getUploadGithubRepositoryName(); modeler.config.uploadGithubRepositoryOwner = config.getUploadGithubRepositoryOwner(); modeler.config.uploadFileName = config.getUploadFileName(); diff --git a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/HybridRuntimeTab.js b/components/bpmn-q/modeler-component/extensions/quantme/configTabs/HybridRuntimeTab.js deleted file mode 100644 index 7a4b370d..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/HybridRuntimeTab.js +++ /dev/null @@ -1,114 +0,0 @@ -import React, {useState} from 'react'; -import {getModeler} from "../../../editor/ModelerHandler"; -import * as config from "../framework-config/config-manager"; - -/** - * React component specifying a tab for the configuration dialog of the modeler. The tab allows the user to change the - * hybrid runtime configs. - * - * @return {JSX.Element} The tab as a React component - * @constructor - */ -export default function HybridRuntimeTab() { - - const [qiskitRuntimeHandlerEndpoint, setQiskitRuntimeHandlerEndpoint] = useState(config.getQiskitRuntimeHandlerEndpoint()); - const [hybridRuntimeProvenance, setHybridRuntimeProvenance] = useState(config.getHybridRuntimeProvenance()); - const [awsRuntimeHandlerEndpoint, setAWSRuntimeHandlerEndpoint] = useState(config.getAWSRuntimeHandlerEndpoint()); - - let hybridRuntimeProvenanceBoolean = hybridRuntimeProvenance; - - const modeler = getModeler(); - - const editorActions = modeler.get('editorActions'); - const eventBus = modeler.get('eventBus'); - - // register editor action listener for changes in config entries - if (!editorActions._actions.hasOwnProperty('qiskitRuntimeHandlerEndpointChanged')) { - editorActions.register({ - qiskitRuntimeHandlerEndpointChanged: function (qiskitRuntimeHandlerEndpoint) { - self.modeler.config.qiskitRuntimeHandlerEndpoint = qiskitRuntimeHandlerEndpoint; - eventBus.fire('config.updated', self.modeler.config); - } - }); - } - if (!editorActions._actions.hasOwnProperty('awsRuntimeHandlerEndpointChanged')) { - editorActions.register({ - awsRuntimeHandlerEndpointChanged: function (awsRuntimeHandlerEndpoint) { - self.modeler.config.awsRuntimeHandlerEndpoint = awsRuntimeHandlerEndpoint; - eventBus.fire('config.updated', self.modeler.config); - } - }); - } - if (!editorActions._actions.hasOwnProperty('hybridRuntimeProvenanceChanged')) { - editorActions.register({ - hybridRuntimeProvenanceChanged: function (hybridRuntimeProvenance) { - self.modeler.config.hybridRuntimeProvenance = hybridRuntimeProvenance; - eventBus.fire('config.updated', self.modeler.config); - } - }); - } - - // save changed config entries on close - HybridRuntimeTab.prototype.onClose = () => { - modeler.config.qiskitRuntimeHandlerEndpoint = qiskitRuntimeHandlerEndpoint; - modeler.config.hybridRuntimeProvenance = hybridRuntimeProvenance; - modeler.config.awsRuntimeHandlerEndpoint = awsRuntimeHandlerEndpoint; - config.setQiskitRuntimeHandlerEndpoint(qiskitRuntimeHandlerEndpoint); - config.setAWSRuntimeHandlerEndpoint(awsRuntimeHandlerEndpoint); - config.setHybridRuntimeProvenance(hybridRuntimeProvenance); - }; - - return (<> -

Hybrid Runtime Handler Endpoints

-
- - - - - - - - - - -
Qiskit Runtime Handler Endpoint: - setQiskitRuntimeHandlerEndpoint(event.target.value)}/> -
AWS Runtime Handler Endpoint: - setAWSRuntimeHandlerEndpoint(event.target.value)}/> -
-

Provenance Collection for Hybrid Runtime

- - - - - - - -
Retrieve Intermediate Results: - { - hybridRuntimeProvenanceBoolean = !hybridRuntimeProvenanceBoolean; - setHybridRuntimeProvenance(hybridRuntimeProvenanceBoolean); - }}/> -
- ); -} - -HybridRuntimeTab.prototype.config = () => { - const modeler = getModeler(); - - modeler.config.qiskitRuntimeHandlerEndpoint = config.getQiskitRuntimeHandlerEndpoint(); - modeler.config.hybridRuntimeProvenance = config.getHybridRuntimeProvenance(); - modeler.config.awsRuntimeHandlerEndpoint = config.getAWSRuntimeHandlerEndpoint(); -}; \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/NisqAnalyzerTab.js b/components/bpmn-q/modeler-component/extensions/quantme/configTabs/NisqAnalyzerTab.js deleted file mode 100644 index 45bcdcd7..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/NisqAnalyzerTab.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, {useState} from 'react'; -import {getModeler} from "../../../editor/ModelerHandler"; -import * as config from "../framework-config/config-manager"; - -/** - * React component specifying a tab for the configuration dialog of the modeler. The tab allows the user to change the - * NISQ analyzer endpoint. - * - * @return {JSX.Element} The tab as a React component - * @constructor - */ -export default function NisqAnalyzerTab() { - - const [nisqAnalyzerEndpoint, setNisqAnalyzerEndpoint] = useState(config.getNisqAnalyzerEndpoint()); - - const modeler = getModeler(); - - const editorActions = modeler.get('editorActions'); - - // register editor action listener for changes in config entries - if (!editorActions._actions.hasOwnProperty('nisqAnalyzerEndpointChanged')) { - editorActions.register({ - nisqAnalyzerEndpointChanged: function (nisqAnalyzerEndpoint) { - self.modeler.config.nisqAnalyzerEndpoint = nisqAnalyzerEndpoint; - } - }); - } - - // save changed config entries on close - NisqAnalyzerTab.prototype.onClose = () => { - modeler.config.nisqAnalyzerEndpoint = nisqAnalyzerEndpoint; - config.setNisqAnalyzerEndpoint(nisqAnalyzerEndpoint); - }; - - return <> -

NISQ Analyzer

- - - - - - - -
NISQ Analyzer Endpoint: - setNisqAnalyzerEndpoint(event.target.value)}/> -
- ; -} - -NisqAnalyzerTab.prototype.config = () => { - const modeler = getModeler(); - - modeler.config.nisqAnalyzerEndpoint = config.getNisqAnalyzerEndpoint(); -}; \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/OpenToscaTab.js b/components/bpmn-q/modeler-component/extensions/quantme/configTabs/OpenToscaTab.js deleted file mode 100644 index 2cf3550b..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/OpenToscaTab.js +++ /dev/null @@ -1,81 +0,0 @@ -import React, {useState} from 'react'; -import {getModeler} from "../../../editor/ModelerHandler"; -import * as config from "../framework-config/config-manager"; - -/** - * React component specifying a tab for the configuration dialog of the modeler. The tab allows the user to change the - * OpenTOSCA and Winery endpoint. - * - * @return {JSX.Element} The tab as a React component - * @constructor - */ -export default function OpenToscaTab() { - - const [opentoscaEndpoint, setOpentoscaEndpoint] = useState(config.getOpenTOSCAEndpoint()); - const [wineryEndpoint, setWineryEndpoint] = useState(config.getWineryEndpoint()); - - const modeler = getModeler(); - - const editorActions = modeler.get('editorActions'); - const eventBus = modeler.get('eventBus'); - - // register editor action listener for changes in config entries - if (!editorActions._actions.hasOwnProperty('opentoscaEndpointChanged')) { - editorActions.register({ - opentoscaEndpointChanged: function (opentoscaEndpoint) { - self.modeler.config.opentoscaEndpoint = opentoscaEndpoint; - } - }); - } - if (!editorActions._actions.hasOwnProperty('wineryEndpointChanged')) { - editorActions.register({ - wineryEndpointChanged: function (wineryEndpoint) { - self.modeler.config.wineryEndpoint = wineryEndpoint; - eventBus.fire('config.updated', self.modeler.config); - } - }); - } - - // save changed config entries on close - OpenToscaTab.prototype.onClose = () => { - modeler.config.opentoscaEndpoint = opentoscaEndpoint; - modeler.config.wineryEndpoint = wineryEndpoint; - config.setOpenTOSCAEndpoint(opentoscaEndpoint); - config.setWineryEndpoint(wineryEndpoint); - }; - - return <> -

OpenTOSCA

- - - - - - - - - - - -
OpenTOSCA Endpoint: - setOpentoscaEndpoint(event.target.value)}/> -
Winery Endpoint: - setWineryEndpoint(event.target.value)}/> -
- ; -} - -OpenToscaTab.prototype.config = () => { - const modeler = getModeler(); - - modeler.config.opentoscaEndpoint = config.getOpenTOSCAEndpoint(); - modeler.config.wineryEndpoint = config.getWineryEndpoint(); -}; \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/QrmDataTab.js b/components/bpmn-q/modeler-component/extensions/quantme/configTabs/QrmDataTab.js deleted file mode 100644 index 6a2b7068..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/QrmDataTab.js +++ /dev/null @@ -1,126 +0,0 @@ -import React, {useState} from 'react'; -import {getModeler} from "../../../editor/ModelerHandler"; -import * as config from "../framework-config/config-manager"; - -/** - * React component specifying a tab for the configuration dialog of the modeler. The tab allows the user to change the - * QRM data. - * - * @return {JSX.Element} The tab as a React component - * @constructor - */ -export default function QrmDataTab() { - - const [githubRepositoryName, setGithubRepositoryName] = useState(config.getQRMRepositoryName()); - const [githubUsername, setGithubUsername] = useState(config.getQRMRepositoryUserName()); - const [githubRepositoryPath, setGithubRepositoryPath] = useState(config.getQRMRepositoryPath()); - const [githubToken, setGitHubToken] = useState(config.getGitHubToken()); - const modeler = getModeler(); - - const editorActions = modeler.get('editorActions'); - - // register editor action listener for changes in config entries - if (!editorActions._actions.hasOwnProperty('qrmRepoNameChanged')) { - editorActions.register({ - qrmRepoNameChanged: function (qrmRepoName) { - self.modeler.config.githubRepositoryName = qrmRepoName; - } - }); - } - if (!editorActions._actions.hasOwnProperty('qrmUserNameChanged')) { - editorActions.register({ - qrmUserNameChanged: function (qrmUserName) { - self.modeler.config.githubUsername = qrmUserName; - } - }); - } - if (!editorActions._actions.hasOwnProperty('qrmRepoPathChanged')) { - editorActions.register({ - qrmRepoPathChanged: function (qrmRepoPath) { - self.modeler.config.githubRepositoryPath = qrmRepoPath; - } - }); - } - if (!editorActions._actions.hasOwnProperty('githubTokenChanged')) { - editorActions.register({ - githubTokenChanged: function (githubToken) { - self.modeler.config.githubToken = githubToken; - } - }); - } - - // save changed config entries on close - QrmDataTab.prototype.onClose = () => { - modeler.config.githubRepositoryName = githubRepositoryName; - modeler.config.githubUsername = githubUsername; - modeler.config.githubRepositoryPath = githubRepositoryPath; - modeler.config.githubToken = githubToken; - config.setQRMRepositoryName(githubRepositoryName); - config.setQRMUserName(githubUsername); - config.setQRMRepositoryPath(githubRepositoryPath); - config.setGitHubToken(githubToken); - }; - - return <> -

QRM Data

- - - - - - - - - - - - - - - -
QRM Repository User: - setGithubUsername(event.target.value)}/> -
QRM Repository Name: - setGithubRepositoryName(event.target.value)}/> -
QRM Repository Path: - setGithubRepositoryPath(event.target.value)}/> -
-

GitHub Authentication

- - - - - - - -
GitHub Token [1]: - setGitHubToken(event.target.value)}/> -
- ; -} - -QrmDataTab.prototype.config = () => { - const modeler = getModeler(); - - modeler.config.githubRepositoryName = config.getQRMRepositoryName(); - modeler.config.githubUsername = config.getQRMRepositoryUserName(); - modeler.config.githubRepositoryPath = config.getQRMRepositoryPath(); - modeler.config.githubToken = config.getGitHubToken(); - -}; \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/QuantMETab.js b/components/bpmn-q/modeler-component/extensions/quantme/configTabs/QuantMETab.js new file mode 100644 index 00000000..dbd5cc07 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/quantme/configTabs/QuantMETab.js @@ -0,0 +1,273 @@ +import React, { useState } from 'react'; +import { getModeler } from '../../../editor/ModelerHandler'; +import * as config from '../framework-config/config-manager'; + +/** + * React component specifying a tab for the configuration dialog of the modeler. The tab allows the user to change workflow + * related configuration entries of the QuantME configs. + * + * @return {JSX.Element} The tab as a React component + * @constructor + */ +export default function BPMNConfigTab() { + const [dataConfigurationsEndpoint, setDataConfigurationsEndpoint] = useState(config.getQuantMEDataConfigurationsEndpoint()); + + const [opentoscaEndpoint, setOpentoscaEndpoint] = useState(config.getOpenTOSCAEndpoint()); + const [wineryEndpoint, setWineryEndpoint] = useState(config.getWineryEndpoint()); + const [nisqAnalyzerEndpoint, setNisqAnalyzerEndpoint] = useState(config.getNisqAnalyzerEndpoint()); + const [qiskitRuntimeHandlerEndpoint, setQiskitRuntimeHandlerEndpoint] = useState(config.getQiskitRuntimeHandlerEndpoint()); + const [hybridRuntimeProvenance, setHybridRuntimeProvenance] = useState(config.getHybridRuntimeProvenance()); + const [awsRuntimeHandlerEndpoint, setAWSRuntimeHandlerEndpoint] = useState(config.getAWSRuntimeHandlerEndpoint()); + const [transformationFrameworkEndpoint, setTransformationFrameworkEndpoint] = useState(config.getTransformationFrameworkEndpoint()); + const [scriptSplitterEndpoint, setScriptSplitterEndpoint] = useState(config.getScriptSplitterEndpoint()); + const [scriptSplitterThreshold, setScriptSplitterThreshold] = useState(config.getScriptSplitterThreshold()); + let hybridRuntimeProvenanceBoolean = hybridRuntimeProvenance; + + const modeler = getModeler(); + + const editorActions = modeler.get('editorActions'); + const eventBus = modeler.get('eventBus'); + + // register editor action listener for changes in config entries + if (!editorActions._actions.hasOwnProperty('qiskitRuntimeHandlerEndpointChanged')) { + editorActions.register({ + qiskitRuntimeHandlerEndpointChanged: function (qiskitRuntimeHandlerEndpoint) { + self.modeler.config.qiskitRuntimeHandlerEndpoint = qiskitRuntimeHandlerEndpoint; + eventBus.fire('config.updated', self.modeler.config); + } + }); + } + if (!editorActions._actions.hasOwnProperty('awsRuntimeHandlerEndpointChanged')) { + editorActions.register({ + awsRuntimeHandlerEndpointChanged: function (awsRuntimeHandlerEndpoint) { + self.modeler.config.awsRuntimeHandlerEndpoint = awsRuntimeHandlerEndpoint; + eventBus.fire('config.updated', self.modeler.config); + } + }); + } + if (!editorActions._actions.hasOwnProperty('hybridRuntimeProvenanceChanged')) { + editorActions.register({ + hybridRuntimeProvenanceChanged: function (hybridRuntimeProvenance) { + self.modeler.config.hybridRuntimeProvenance = hybridRuntimeProvenance; + eventBus.fire('config.updated', self.modeler.config); + } + }); + } + if (!editorActions._actions.hasOwnProperty('opentoscaEndpointChanged')) { + editorActions.register({ + opentoscaEndpointChanged: function (opentoscaEndpoint) { + self.modeler.config.opentoscaEndpoint = opentoscaEndpoint; + } + }); + } + if (!editorActions._actions.hasOwnProperty('wineryEndpointChanged')) { + editorActions.register({ + wineryEndpointChanged: function (wineryEndpoint) { + self.modeler.config.wineryEndpoint = wineryEndpoint; + eventBus.fire('config.updated', self.modeler.config); + } + }); + } + if (!editorActions._actions.hasOwnProperty('nisqAnalyzerEndpointChanged')) { + editorActions.register({ + nisqAnalyzerEndpointChanged: function (nisqAnalyzerEndpoint) { + self.modeler.config.nisqAnalyzerEndpoint = nisqAnalyzerEndpoint; + } + }); + } + if (!editorActions._actions.hasOwnProperty('transformationFrameworkEndpointChanged')) { + editorActions.register({ + transformationFrameworkEndpointChanged: function (transformationFrameworkEndpoint) { + modeler.config.transformationFrameworkEndpoint = transformationFrameworkEndpoint; + } + }); + } + if (!editorActions._actions.hasOwnProperty('scriptSplitterEndpointChanged')) { + editorActions.register({ + scriptSplitterEndpointChanged: function (scriptSplitterEndpoint) { + modeler.config.scriptSplitterEndpoint = scriptSplitterEndpoint; + eventBus.fire('config.updated', self.modeler.config); + } + }); + } + if (!editorActions._actions.hasOwnProperty('scriptSplitterThresholdChanged')) { + editorActions.register({ + scriptSplitterThresholdChanged: function (scriptSplitterEndpoint) { + modeler.config.scriptSplitterThreshold = scriptSplitterEndpoint; + } + }); + } + + // save changed config entries on close + BPMNConfigTab.prototype.onClose = () => { + modeler.config.dataConfigurationsEndpoint = dataConfigurationsEndpoint; + modeler.config.opentoscaEndpoint = opentoscaEndpoint; + modeler.config.wineryEndpoint = wineryEndpoint; + modeler.config.nisqAnalyzerEndpoint = nisqAnalyzerEndpoint; + modeler.config.transformationFrameworkEndpoint = transformationFrameworkEndpoint; + modeler.config.scriptSplitterEndpoint = scriptSplitterEndpoint; + modeler.config.scriptSplitterThreshold = scriptSplitterThreshold; + modeler.config.qiskitRuntimeHandlerEndpoint = qiskitRuntimeHandlerEndpoint; + modeler.config.hybridRuntimeProvenance = hybridRuntimeProvenance; + modeler.config.awsRuntimeHandlerEndpoint = awsRuntimeHandlerEndpoint; + config.setQuantMEDataConfigurationsEndpoint(dataConfigurationsEndpoint); + config.setOpenTOSCAEndpoint(opentoscaEndpoint); + config.setWineryEndpoint(wineryEndpoint); + config.setNisqAnalyzerEndpoint(nisqAnalyzerEndpoint); + config.setTransformationFrameworkEndpoint(transformationFrameworkEndpoint); + config.setScriptSplitterEndpoint(scriptSplitterEndpoint); + config.setScriptSplitterThreshold(scriptSplitterThreshold); + config.setQiskitRuntimeHandlerEndpoint(qiskitRuntimeHandlerEndpoint); + config.setAWSRuntimeHandlerEndpoint(awsRuntimeHandlerEndpoint); + config.setHybridRuntimeProvenance(hybridRuntimeProvenance); + }; + + return <> +

QuantME data configuration endpoint:

+ + + + + + + +
Data Configurations Endpoint + setDataConfigurationsEndpoint(event.target.value)} /> +
+

OpenTOSCA

+ + + + + + + + + + + +
OpenTOSCA Endpoint: + setOpentoscaEndpoint(event.target.value)} /> +
Winery Endpoint: + setWineryEndpoint(event.target.value)} /> +
+

BPMN related configurations:

+ + + + + + + +
QuantME Framework Endpoint + setTransformationFrameworkEndpoint(event.target.value)} /> +
+

NISQ Analyzer

+ + + + + + + +
NISQ Analyzer Endpoint: + setNisqAnalyzerEndpoint(event.target.value)} /> +
+

Workflow generation:

+ + + + + + + + + + + +
Script Splitter Endpoint + setScriptSplitterEndpoint(event.target.value)} /> +
Script Splitter Threshold + setScriptSplitterThreshold(event.target.value)} /> +
+

Hybrid Runtime Handler Endpoints

+ + + + + + + + + + + +
Qiskit Runtime Handler Endpoint: + setQiskitRuntimeHandlerEndpoint(event.target.value)} /> +
AWS Runtime Handler Endpoint: + setAWSRuntimeHandlerEndpoint(event.target.value)} /> +
+

Provenance Collection for Hybrid Runtime

+ + + + + + + +
Retrieve Intermediate Results: + { + hybridRuntimeProvenanceBoolean = !hybridRuntimeProvenanceBoolean; + setHybridRuntimeProvenance(hybridRuntimeProvenanceBoolean); + }} /> +
+ ; +} + +BPMNConfigTab.prototype.config = () => { + const modeler = getModeler(); + + modeler.config.transformationFrameworkEndpoint = config.getTransformationFrameworkEndpoint(); + modeler.config.scriptSplitterEndpoint = config.getScriptSplitterEndpoint(); + modeler.config.scriptSplitterThreshold = config.getScriptSplitterThreshold(); +}; \ No newline at end of file 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 10fba564..6bd1d91c 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 @@ -9,7 +9,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -// takes either the environment variables or the default values definded in webpack.config +// takes either the environment variables or the default values defined in webpack.config +// TODO: On change ALWAYS UPDATE corresponding doc: doc/quantum-workflow-modeler/modeler-configuration.md const defaultConfig = { quantmeDataConfigurationsEndpoint: process.env.DATA_CONFIG, opentoscaEndpoint: process.env.OPENTOSCA_ENDPOINT, diff --git a/components/bpmn-q/test/tests/editor/plugin.spec.js b/components/bpmn-q/test/tests/editor/plugin.spec.js index 271ea7c8..dfc50c1a 100644 --- a/components/bpmn-q/test/tests/editor/plugin.spec.js +++ b/components/bpmn-q/test/tests/editor/plugin.spec.js @@ -54,7 +54,7 @@ describe('Test plugins', function () { expect(extensions['planqk']).to.not.be.undefined; expect(transfButtons.length).to.equal(3); expect(buttons.length).to.equal(2); - expect(tabs.length).to.equal(9); + expect(tabs.length).to.equal(4); expect(styles.length).to.equal(3); }); }); diff --git a/components/bpmn-q/webpack.config.js b/components/bpmn-q/webpack.config.js index 85d9aaa8..d4037330 100644 --- a/components/bpmn-q/webpack.config.js +++ b/components/bpmn-q/webpack.config.js @@ -62,21 +62,22 @@ module.exports = { }), // use the default values if environment variable does not exist new webpack.EnvironmentPlugin({ + AUTOSAVE_INTERVAL: 300000, AWS_RUNTIME_HANDLER_ENDPOINT: 'http://localhost:8890', CAMUNDA_ENDPOINT: 'http://localhost:8080/engine-rest', DATA_CONFIG: 'http://localhost:8100/data-objects', - GITHUB_TOKEN: '', - NISQ_ANALYZER_ENDPOINT: 'http://localhost:8098/nisq-analyzer', - OPENTOSCA_ENDPOINT: 'http://localhost:1337/csars', - PROVENANCE_COLLECTION: false, DOWNLOAD_FILE_NAME: 'quantum-workflow-model', ENABLE_DATA_FLOW_PLUGIN: true, ENABLE_PLANQK_PLUGIN: true, ENABLE_QHANA_PLUGIN: true, ENABLE_QUANTME_PLUGIN: true, - QISKIT_RUNTIME_HANDLER_ENDPOINT: 'http://localhost:8889', + GITHUB_TOKEN: '', + OPENTOSCA_ENDPOINT: 'http://localhost:1337/csars', + NISQ_ANALYZER_ENDPOINT: 'http://localhost:8098/nisq-analyzer', + PROVENANCE_COLLECTION: false, QHANA_GET_PLUGIN_URL: 'http://localhost:5006/api/plugins/', QHANA_LIST_PLUGINS_URL: 'http://localhost:5006/api/plugins/?item-count=100', + QISKIT_RUNTIME_HANDLER_ENDPOINT: 'http://localhost:8889', QRM_USERNAME: '', QRM_REPONAME: '', QRM_REPOPATH: '', diff --git a/doc/quantum-workflow-modeler/extensions/plugins.md b/doc/quantum-workflow-modeler/extensions/plugins.md new file mode 100644 index 00000000..bc9462c5 --- /dev/null +++ b/doc/quantum-workflow-modeler/extensions/plugins.md @@ -0,0 +1,57 @@ +# Plugin Dependencies +Plugin dependencies are a way to establish relationships between different plugins. +A plugin dependency indicates that one plugin relies on another plugin to function properly. By defining plugin dependencies, you ensure that the required plugins are loaded and available before using a particular plugin. + +To add plugin dependencies, you typically need to follow these steps: + +1. Identify the plugins: Determine which plugins in your system have dependencies. Identify the plugins that require other plugins to be present and functional. + +2. Define & update the dependencies: For each plugin with dependencies, specify the required plugins. This can be done by associating the dependent plugin with the required plugins, i.e., by adding the plugin id to the dependency attribute. + +The following code snippet demonstrates how to define plugin dependencies using an array of plugins. +Exemplarily, we specify that the `QuantMEPlugin` depends on the `QHAanaPlugin`. + +```javascript +const PLUGINS = [ + { + plugin: DataFlowPlugin, + dependencies: [] + }, + { + plugin: QHAnaPlugin, + dependencies: [] + }, + { + plugin: PlanQKPlugin, + dependencies: [] + }, + { + plugin: QuantMEPlugin, + dependencies: ['QHAnaPlugin'] + } +]; +``` + +## Dependency resolution +The provided [code](../../../components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js#L38) handles transitive dependencies by using a recursive approach to load plugins and their dependencies. +Here is a breakdown of how the code handles transitive dependencies: + +1. Check if active plugins have already been determined: The function first checks if the `activePlugins` array has already been populated. If it contains plugins, indicating that the active plugins have already been determined, the function simply returns the `activePlugins` array. + +2. Determine active plugins and their dependencies: If the `activePlugins` array is empty, the function proceeds to determine the active plugins and their dependencies. + +3. Recursive loading of plugins and dependencies: The `loadPlugin` function is defined as a recursive function that takes a plugin as an argument. It checks if the plugin is already included in the `activePlugins` array. If it is not, it iterates over the plugin's `dependencies` array. + +4. Recursive dependency resolution: For each dependency, the function finds the corresponding plugin object from the `PLUGINS` array based on the dependency's name. If the dependency plugin is found, the `loadPlugin` function is recursively called with the dependency plugin as the argument. This allows the function to resolve dependencies at multiple levels, handling transitive dependencies. + +5. Add plugin to activePlugins: After resolving all dependencies, the plugin object is added to the `activePlugins` array. + +6. Iterate over plugin configurations: The function then iterates over the plugin configurations obtained from `getAllConfigs()`. + +7. Find enabled plugins: For each plugin configuration, the function finds the corresponding plugin object from the `PLUGINS` array based on the plugin's name. It also checks if the plugin is enabled by calling the `checkEnabledStatus` function. + +8. Load plugins and dependencies: If a plugin object is found, and it is enabled, the `loadPlugin` function is called with the plugin as the argument. This initiates the loading of the plugin and its dependencies. + +9. Return active plugins: Finally, the function returns the `activePlugins` array, which contains all the active plugins and their resolved dependencies. + +By recursively loading plugins and their dependencies, the code handles transitive dependencies, ensuring that all required plugins are loaded and added to the `activePlugins` array in the correct order. \ No newline at end of file diff --git a/doc/quantum-workflow-modeler/modeler-configuration.md b/doc/quantum-workflow-modeler/modeler-configuration.md new file mode 100644 index 00000000..958eab5c --- /dev/null +++ b/doc/quantum-workflow-modeler/modeler-configuration.md @@ -0,0 +1,53 @@ +# Environment Variables + +In the following, all environment variables that can be used to customize the workflow modeler are summarized. + +### Overview + +* ```AWS_RUNTIME_HANDLER_ENDPOINT``` (default 'http://localhost:8890'): Defines the endpoint of the [Amazon Braket Hybrid Jobs Handler](https://github.com/UST-QuAntiL/amazon-braket-hybrid-jobs-handler) which enables the automatic generation of hybrid programs from Amazon Braket programs. + +* ```CAMUNDA_ENDPOINT``` (default: 'http://localhost:8080/engine-rest'): Defines the endpoint of the Camunda engine to deploy workflows to. + +* ```DATA_CONFIG``` (default: 'http://localhost:8100/data-objects'): Defines the configuration of data objects. + +* ```ENABLE_DATA_FLOW_PLUGIN``` (default: 'true'): Defines if the Data Flow plugin is enabled. + +* ```ENABLE_PLANQK_PLUGIN``` (default: 'true'): Defines if the PlanQK plugin is enabled. + +* ```ENABLE_QHANA_PLUGIN``` (default: 'true'): Defines if the QHAna plugin is enabled. + +* ```ENABLE_QUANTME_PLUGIN``` (default: 'true'): Defines if the QuantME plugin is enabled. + +* ```GITHUB_TOKEN``` (default: ''): Defines the GitHub Token which can be used to make authorized requests. For more information take a look at [GitHub Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). + +* ```AUTOSAVE_INTERVAL``` (default ms: '300000'): Defines the interval of the auto save feature. If changes are applied to the workflow then it get saved after `5` minutes. + +* ```NISQ_ANALYZER_ENDPOINT``` (default: 'http://localhost:8098/nisq-analyzer'): Defines the endpoint of the [NISQ Analyzer](https://github.com/UST-QuAntiL/nisq-analyzer) to enable an automated hardware selection. + +* ```OPENTOSCA_ENDPOINT``` (default: 'http://localhost:1337/csars'): Defines the endpoint of the OpenTOSCA container to deploy services with. + +* ```QISKIT_RUNTIME_HANDLER_ENDPOINT``` (default: 'http://localhost:8889'): Defines the endpoint of the [Qiskit Runtime Handler](https://github.com/UST-QuAntiL/qiskit-runtime-handler) which enables the automatic generation of hybrid programs from Qiskit programs. + +* ```QHANA_GET_PLUGIN_URL``` (default: 'http://localhost:5006/api/plugins/'): Defines the plugin url for QHAna. + +* ```QHANA_LIST_PLUGINS_URL``` (default: 'http://localhost:5006/api/plugins/?item-count=100'): Defines the plugin list url for QHAna. + +* ```QRM_USERNAME``` (default: ' '): Defines the GitHub username to access the [QRM-Repository](../QRM-Repository) + +* ```QRM_REPONAME``` (default: ' '): Defines the GitHub repository name to access the [QRM-Repository](../QRM-Repository) + +* ```QRM_REPOPATH``` (default: ' '): Defines the local path in the GitHub repository to the folder containing the [QRM-Repository](../QRM-Repository). This parameter is optional and if it is not set, the root folder of the repository is used. + +* ```SERVICE_DATA_CONFIG``` (default: 'http://localhost:8000/service-task'): Defines the configuration for the service task. + +* ```SCRIPT_SPLITTER_EDNPOINT``` (default: 'http://localhost:8891'): Defines the endpoint of the Script Splitter. + +* ```SCRIPT_SPLITTER_THRESHOLD``` (default: '5'): Defines the splitting threshold for the Script Splitter. + +* ```TRANSFORMATION_FRAMEWORK_ENDPOINT``` (default: 'http://localhost:8888'): Defines the endpoint of the QuantME Transformation Framework to use for the automated hardware selection. + +* ```WINERY_ENDPOINT``` (default: 'http://localhost:8081/winery'): Defines the endpoint of the Winery to retrieve deployment models for services from. + +* ```PROVENANCE_COLLECTION``` (default: 'false'): Defines if the intermediate results of the workflow executed should be collected. + +The value of an environment variable is accessed using `process.env.ENV_NAME`. If you want to add a new environment variable, add it to the [webpack.config](../../../../../components/bpmn-q/webpack.config.js) file and restart the application. \ No newline at end of file From 0e5f04465c49273415516acb02d409a9a884d564 Mon Sep 17 00:00:00 2001 From: mbeisel Date: Fri, 25 Aug 2023 11:23:45 +0200 Subject: [PATCH 4/7] Add new quantme data objects, enable retrieving them via GitHub, add QuantME plugin dependency (#76) --- .../configurations/ConfigurationEndpoint.js | 8 +- .../editor/plugin/PluginHandler.js | 2 +- .../configurations/quantmeDataObjects.json | 123 ++++++++++++++++++ .../QuantMEDataConfigurationsServer.js | 77 ++++++++++- components/bpmn-q/webpack.config.js | 2 +- 5 files changed, 202 insertions(+), 10 deletions(-) create mode 100644 components/bpmn-q/modeler-component/extensions/quantme/configurations/quantmeDataObjects.json diff --git a/components/bpmn-q/modeler-component/editor/configurations/ConfigurationEndpoint.js b/components/bpmn-q/modeler-component/editor/configurations/ConfigurationEndpoint.js index 17e56ecb..7cfc8c4c 100644 --- a/components/bpmn-q/modeler-component/editor/configurations/ConfigurationEndpoint.js +++ b/components/bpmn-q/modeler-component/editor/configurations/ConfigurationEndpoint.js @@ -17,13 +17,11 @@ export default class ConfigurationsEndpoint { * Fetch the configured endpoint and store the result in this._configurations */ fetchConfigurations() { - fetch(this._endpointUrl) - .then(response => response.json()) + .then(response => response.headers.get('content-type') === 'text/plain; charset=utf-8' ? response.text() : response.json()) .then(data => { - this._configurations = data; - console.log('Received ' + data.length + ' configurations: '); - console.log(data); + this._configurations = typeof data === "string" ? JSON.parse(data) : data; + console.log(this._configurations); }) .catch(error => { console.error('Error fetching configurations from ' + this._endpointUrl + ': \n' + error); diff --git a/components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js b/components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js index cc1d95fd..326af501 100644 --- a/components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js +++ b/components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js @@ -16,7 +16,7 @@ import GitHubTab from '../../extensions/quantme/configTabs/GitHubTab'; const PLUGINS = [ { plugin: QuantMEPlugin, - dependencies: [] + dependencies: ['DataFlowPlugin'] }, { plugin: DataFlowPlugin, diff --git a/components/bpmn-q/modeler-component/extensions/quantme/configurations/quantmeDataObjects.json b/components/bpmn-q/modeler-component/extensions/quantme/configurations/quantmeDataObjects.json new file mode 100644 index 00000000..aacfa956 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/quantme/configurations/quantmeDataObjects.json @@ -0,0 +1,123 @@ +[ + { + "name": "Quantum Circuit Object", + "id": "Quantum-Circuit-Object", + "description": "data object for storing and transferring all relevant data about a quantum circuit", + "appliesTo": "dataflow:DataMapObject", + "groupLabel": "Quantum Circuit Info", + "icon": { + "transform": "matrix(0.14, 0, 0, 0.14, 5, 5)", + "svg": "" + }, + "attributes": [ + { + "name": "quantum-circuit", + "label": "Quantum Circuit", + "type": "string", + "value": "", + "bindTo": { + "name": "content", + "type": "KeyValueMap" + } + }, + { + "name": "programming-language", + "label": "Programming Language", + "type": "string", + "value": "", + "bindTo": { + "name": "content", + "type": "KeyValueMap" + } + } + ] + }, + { + "name": "Result Object", + "id": "Result-Object", + "description": "data object to transfer the results of quantum computations", + "appliesTo": "dataflow:DataMapObject", + "groupLabel": "Result", + "icon": { + "transform": "matrix(0.24, 0, 0, 0.24, 3, 3)", + "svg": "" + }, + "attributes": [ + { + "name": "Execution-Result", + "label": "Execution Result", + "type": "string", + "bindTo": { + "name": "content", + "type": "KeyValueMap" + } + } + ] + }, + { + "name": "Evaluation Result Object", + "id": "Evaluation-Result-Object", + "description": "data object to transfer the evaluated results of quantum computations", + "appliesTo": "dataflow:DataMapObject", + "groupLabel": "Evaluation Result", + "icon": { + "transform": "matrix(0.09, 0, 0, 0.09, 3, 3)", + "svg": "" + }, + "attributes": [ + { + "name": "Evaluation Result", + "label": "Evaluation Result", + "type": "string", + "bindTo": { + "name": "content", + "type": "KeyValueMap" + } + } + ] + }, + { + "name": "Parameterization Object", + "id": "Parameterization-Object", + "description": "data object to transfer optimization parameters, e.g., in variational quantum algorithms", + "appliesTo": "dataflow:DataMapObject", + "groupLabel": "Parameterization", + "icon": { + "transform": "matrix(0.12, 0, 0, 0.12, 6, 4)", + "svg": "𝜶𝜷" + }, + "attributes": [ + { + "name": "Parameterization", + "label": "Parameterization", + "type": "string", + "bindTo": { + "name": "content", + "type": "KeyValueMap" + } + } + ] + }, + { + "name": "Initial State Object", + "id": "Initial-State-Object", + "description": "data object to transfer the initial state use d to warm-start a quantum computations", + "appliesTo": "dataflow:DataMapObject", + "groupLabel": "Initial State", + "icon": { + "transform": "matrix(0.16, 0, 0, 0.16, 6, 3)", + "svg": "0|1|" + }, + "attributes": [ + { + "name": "Initial State", + "label": "Initial State", + "type": "string", + "bindTo": { + "name": "content", + "type": "KeyValueMap" + } + } + ] + } +] diff --git a/components/bpmn-q/test/test-setup/QuantMEDataConfigurationsServer.js b/components/bpmn-q/test/test-setup/QuantMEDataConfigurationsServer.js index ea40e053..4e2b1620 100644 --- a/components/bpmn-q/test/test-setup/QuantMEDataConfigurationsServer.js +++ b/components/bpmn-q/test/test-setup/QuantMEDataConfigurationsServer.js @@ -1,3 +1,7 @@ +/* +THIS CLASS IS DEPRECATED AND SHALL ONLY BE USED FOR TESTING NEW SVG ICON SIZES + */ + const http = require('http'); const server = http.createServer((req, res) => { @@ -25,6 +29,7 @@ const server = http.createServer((req, res) => { }); server.listen(8100, () => { + // console.log(JSON.stringify(quantmeDataObjects)); console.log('Server listening on http://localhost:8100/'); }); @@ -36,8 +41,8 @@ const quantmeDataObjects = [ appliesTo: "dataflow:DataMapObject", groupLabel: 'Quantum Circuit Info', icon: { - transform: 'matrix(0.13, 0, 0, 0.13, 5, 5)', - svg: '', + transform: 'matrix(0.14, 0, 0, 0.14, 5, 5)', + svg: '', }, attributes: [ { @@ -69,7 +74,7 @@ const quantmeDataObjects = [ appliesTo: "dataflow:DataMapObject", groupLabel: 'Result', icon: { - transform: 'matrix(0.22, 0, 0, 0.22, 3, 3)', + transform: 'matrix(0.24, 0, 0, 0.24, 3, 3)', svg: '', }, attributes: [ @@ -84,4 +89,70 @@ const quantmeDataObjects = [ }, ] }, + { + name: 'Evaluation Result Object', + id: 'Evaluation-Result-Object', + description: "data object to transfer the evaluated results of quantum computations", + appliesTo: "dataflow:DataMapObject", + groupLabel: 'Evaluation Result', + icon: { + transform: 'matrix(0.09, 0, 0, 0.09, 3, 3)', + svg: '', + }, + attributes: [ + { + name: 'Evaluation Result', + label: 'Evaluation Result', + type: 'string', + bindTo: { + name: 'content', + type: 'KeyValueMap', + }, + }, + ] + }, + { + name: 'Parameterization Object', + id: 'Parameterization-Object', + description: "data object to transfer optimization parameters, e.g., in variational quantum algorithms", + appliesTo: "dataflow:DataMapObject", + groupLabel: 'Parameterization', + icon: { + transform: 'matrix(0.12, 0, 0, 0.12, 6, 4)', + svg: '𝜶𝜷', + }, + attributes: [ + { + name: 'Parameterization', + label: 'Parameterization', + type: 'string', + bindTo: { + name: 'content', + type: 'KeyValueMap', + }, + }, + ] + }, + { + name: 'Initial State Object', + id: 'Initial-State-Object', + description: "data object to transfer the initial state used to warm-start a quantum computations", + appliesTo: "dataflow:DataMapObject", + groupLabel: 'Initial State', + icon: { + transform: 'matrix(0.16, 0, 0, 0.16, 6, 3)', + svg: '0|1|', + }, + attributes: [ + { + name: 'Initial State', + label: 'Initial State', + type: 'string', + bindTo: { + name: 'content', + type: 'KeyValueMap', + }, + }, + ] + }, ]; \ No newline at end of file diff --git a/components/bpmn-q/webpack.config.js b/components/bpmn-q/webpack.config.js index d4037330..5f85d4c1 100644 --- a/components/bpmn-q/webpack.config.js +++ b/components/bpmn-q/webpack.config.js @@ -65,7 +65,7 @@ module.exports = { AUTOSAVE_INTERVAL: 300000, AWS_RUNTIME_HANDLER_ENDPOINT: 'http://localhost:8890', CAMUNDA_ENDPOINT: 'http://localhost:8080/engine-rest', - DATA_CONFIG: 'http://localhost:8100/data-objects', + DATA_CONFIG: 'https://raw.githubusercontent.com/PlanQK/workflow-modeler/master/components/bpmn-q/modeler-component/extensions/quantme/configurations/quantmeDataObject.json', DOWNLOAD_FILE_NAME: 'quantum-workflow-model', ENABLE_DATA_FLOW_PLUGIN: true, ENABLE_PLANQK_PLUGIN: true, From bd5991739cb137c20d9ea73268e7cb8e5ddbe3ab Mon Sep 17 00:00:00 2001 From: mbeisel Date: Fri, 25 Aug 2023 11:56:28 +0200 Subject: [PATCH 5/7] fix typo --- components/bpmn-q/webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bpmn-q/webpack.config.js b/components/bpmn-q/webpack.config.js index 5f85d4c1..e951dba6 100644 --- a/components/bpmn-q/webpack.config.js +++ b/components/bpmn-q/webpack.config.js @@ -65,7 +65,7 @@ module.exports = { AUTOSAVE_INTERVAL: 300000, AWS_RUNTIME_HANDLER_ENDPOINT: 'http://localhost:8890', CAMUNDA_ENDPOINT: 'http://localhost:8080/engine-rest', - DATA_CONFIG: 'https://raw.githubusercontent.com/PlanQK/workflow-modeler/master/components/bpmn-q/modeler-component/extensions/quantme/configurations/quantmeDataObject.json', + 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, ENABLE_PLANQK_PLUGIN: true, From 3e9ac783f00878d95275da758b8aeb167b87b8ac Mon Sep 17 00:00:00 2001 From: Benjamin Weder Date: Fri, 25 Aug 2023 12:19:57 +0200 Subject: [PATCH 6/7] Bump modeler version to 0.1.9 --- components/bpmn-q/package-lock.json | 4 ++-- components/bpmn-q/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/bpmn-q/package-lock.json b/components/bpmn-q/package-lock.json index 940a8e29..4a435456 100644 --- a/components/bpmn-q/package-lock.json +++ b/components/bpmn-q/package-lock.json @@ -1,12 +1,12 @@ { "name": "@planqk/quantum-workflow-modeler", - "version": "0.1.8", + "version": "0.1.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@planqk/quantum-workflow-modeler", - "version": "0.1.8", + "version": "0.1.9", "license": "Apache-2.0", "dependencies": { "@bpmn-io/properties-panel": "^1.3.1", diff --git a/components/bpmn-q/package.json b/components/bpmn-q/package.json index c8b35f89..4f3b14f9 100644 --- a/components/bpmn-q/package.json +++ b/components/bpmn-q/package.json @@ -1,6 +1,6 @@ { "name": "@planqk/quantum-workflow-modeler", - "version": "0.1.8", + "version": "0.1.9", "description": "", "package name": "@PlanQK/workflow-modeler", "main": "index.js", From 64100ce70016afcbeb9969b3e4bae47c99dd3ee9 Mon Sep 17 00:00:00 2001 From: LaviniaStiliadou Date: Fri, 25 Aug 2023 12:58:09 +0200 Subject: [PATCH 7/7] Fix/subprocess handling with QRM (#61) * expand/collapse subprocess based on replacement in QRM * remove isExpanded attribute from shapes * handle different namespace * Update package-lock.json * set isExpanded of nested subprocess * remove isExpand attribute also from nested subprocess, * fix test * remove isexpanded from nested subprocess * fix collapsed subprocess * change repo for maxcut qrm * fix replacement of collapsed subprocess * enable setting camunda_endpoint via env_vars * remove top-level isexpanded attribute * Remove unused method and fix method comment * Fix typo in method name --------- Co-authored-by: Benjamin Weder Co-authored-by: mbeisel --- .../editor/ModelerHandler.js | 31 ++- .../editor/util/TransformationUtilities.js | 34 ++- .../quantme/framework-config/config.js | 1 + .../replacement/QuantMETransformator.js | 234 ++++++++++++++++-- .../quantme/replacement/layouter/Layouter.js | 56 ++--- components/bpmn-q/package-lock.json | 54 +++- components/bpmn-q/package.json | 4 +- .../test/tests/helpers/DiagramHelper.js | 1 + .../quantme/quantme-transformation.spec.js | 122 +++++++-- components/bpmn-q/webpack.config.js | 2 +- 10 files changed, 432 insertions(+), 107 deletions(-) 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,