diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..ced1c8cf --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +components/bpmn-q/node_modules/ +npm-debug.log +yarn-error.log diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml new file mode 100644 index 00000000..e45e7288 --- /dev/null +++ b/.github/workflows/docker-publish.yaml @@ -0,0 +1,23 @@ + +on: + push: + branches: + - 'master' + +jobs: + docker-publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v4 + with: + push: true + tags: planqk/workflow-modeler:latest diff --git a/Dockerfile b/Dockerfile index 0e251fff..80733202 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,45 @@ -FROM node:18-alpine - +FROM node:18-alpine as builder LABEL maintainer = "Martin Beisel " -COPY "components/bpmn-q" /tmp -WORKDIR /tmp +COPY "components/bpmn-q" /app +WORKDIR /app +RUN npm ci + +ARG DATA_CONFIG +ARG OPENTOSCA_ENDPOINT +ARG WINERY_ENDPOINT +ARG NISQ_ANALYZER_ENDPOINT +ARG TRANSFORMATION_FRAMEWORK_ENDPOINT +ARG QISKIT_RUNTIME_HANDLER_ENDPOINT +ARG AWS_RUNTIME_HANDLER_ENDPOINT +ARG SCRIPT_SPLITTER_ENDPOINT +ARG SCRIPT_SPLITTER_THRESHOLD +ARG QRM_REPONAME +ARG QRM_USERNAME +ARG QRM_REPOPATH +ARG PROVENANCE_COLLECTION +ARG ENABLE_DATA_FLOW_PLUGIN +ARG ENABLE_PLANQK_PLUGIN +ARG ENABLE_QHANA_PLUGIN +ARG ENABLE_QUANTME_PLUGIN +ARG ENABLE_OPENTOSCA_PLUGIN +ARG AUTOSAVE_INTERVAL +ARG CAMUNDA_ENDPOINT +ARG DOWNLOAD_FILE_NAME +ARG GITHUB_TOKEN +ARG QHANA_GET_PLUGIN_URL +ARG QHANA_LIST_PLUGINS_URL +ARG SERVICE_DATA_CONFIG +ARG UPLOAD_BRANCH_NAME +ARG UPLOAD_FILE_NAME +ARG UPLOAD_GITHUB_REPO +ARG UPLOAD_GITHUB_USER -RUN npm install +RUN env +RUN npm run build -- --mode=production -EXPOSE 8080 -CMD npm run dev +FROM nginxinc/nginx-unprivileged:alpine +USER root +RUN rm -rf /usr/share/nginx/html +COPY --from=builder /app/public /usr/share/nginx/html +USER 101 diff --git a/README.md b/README.md index e4dfa808..6987bb3c 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,20 @@ npm run dev ``` to start the modeler in a simple html website which runs on localhost:8080. +## Execution in Docker +To serve the application from a Docker container execute: +``` +docker run --name workflow-modeler -p 8080:8080 planqk/workflow-modeler +``` +Afterwards the application is available in a browser on localhost:8080 + +To build and run an own image execute: +``` +docker build -t workflow-modeler [--build-arg =] . +docker run --name workflow-modeler -p 8080:8080 workflow-modeler +``` + + ## How to use this Library To use the Quantum Workflow Modeler component in your application you have to install its npm package which is published via GitHub packages. diff --git a/components/bpmn-q/karma.conf.js b/components/bpmn-q/karma.conf.js index d4e053ee..46995c50 100644 --- a/components/bpmn-q/karma.conf.js +++ b/components/bpmn-q/karma.conf.js @@ -1,32 +1,36 @@ // Karma configuration -const webpackConfig = require("./webpack.config.js"); +const webpackConfig = require('./webpack.config.js'); module.exports = function (config) { config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: "", + basePath: '', // frameworks to use // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter - frameworks: ["mocha", "webpack"], + frameworks: ['mocha', 'webpack'], // list of files / patterns to load in the browser files: [ - "test/tests/editor/configurations.spec.js", - "test/tests/editor/editor.spec.js", - "test/tests/editor/plugin.spec.js", - "test/tests/planqk/planqk-transformation.spec.js", - "test/tests/quantme/quantme-transformation.spec.js", - "test/tests/editor/utils/modelling-util.spec.js", - "test/tests/qhana/qhana-plugin-config.spec.js", - "test/tests/qhana/qhana-service-configs.spec.js", - "test/tests/quantme/data-object-configs.spec.js", - "test/tests/quantme/quantme-config.spec.js", - "test/tests/dataflow/data-flow-transformation.spec.js", - "test/tests/dataflow/data-flow-plugin-config.spec.js", - "test/tests/dataflow/data-flow-configurations-endpoint.spec.js", - "test/tests/dataflow/data-flow-palette.spec.js", - "test/tests/dataflow/data-flow-replace-menu.spec.js", + 'test/tests/editor/configurations.spec.js', + 'test/tests/editor/editor.spec.js', + 'test/tests/editor/plugin.spec.js', + 'test/tests/planqk/planqk-transformation.spec.js', + 'test/tests/editor/utils/modelling-util.spec.js', + 'test/tests/qhana/qhana-plugin-config.spec.js', + 'test/tests/qhana/qhana-service-configs.spec.js', + 'test/tests/quantme/quantme-transformation.spec.js', + 'test/tests/quantme/data-object-configs.spec.js', + 'test/tests/quantme/quantme-config.spec.js', + 'test/tests/opentosca/opentosca-config.spec.js', + 'test/tests/opentosca/deployment-utils.spec.js', + 'test/tests/opentosca/deployment-model-renderer.spec.js', + 'test/tests/dataflow/data-flow-transformation.spec.js', + 'test/tests/dataflow/data-flow-plugin-config.spec.js', + 'test/tests/dataflow/data-flow-configurations-endpoint.spec.js', + 'test/tests/dataflow/data-flow-palette.spec.js', + 'test/tests/dataflow/data-flow-replace-menu.spec.js', ], // list of files / patterns to exclude @@ -35,7 +39,7 @@ module.exports = function (config) { // preprocess matching files before serving them to the browser // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor preprocessors: { - "test/**/*.spec.js": ["webpack"], + 'test/**/*.spec.js': ['webpack'] }, webpack: webpackConfig, @@ -45,7 +49,7 @@ module.exports = function (config) { // start these browsers // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher - browsers: ["ChromeHeadless"], + browsers: ['ChromeHeadless'], // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits @@ -56,7 +60,7 @@ module.exports = function (config) { concurrency: 1, mochaReporter: { - output: "minimal", + output: "minimal" }, }); -}; +}; \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/QuantumWorkflowModeler.js b/components/bpmn-q/modeler-component/QuantumWorkflowModeler.js index dcd75bb0..d923a455 100644 --- a/components/bpmn-q/modeler-component/QuantumWorkflowModeler.js +++ b/components/bpmn-q/modeler-component/QuantumWorkflowModeler.js @@ -91,6 +91,7 @@ export class QuantumWorkflowModeler extends HTMLElement {
+
`; diff --git a/components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js b/components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js index a4f9f696..24e8c080 100644 --- a/components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js +++ b/components/bpmn-q/modeler-component/editor/plugin/PluginHandler.js @@ -1,10 +1,11 @@ -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 GeneralTab from "../config/GeneralTab"; -import GitHubTab from "../../extensions/quantme/configTabs/GitHubTab"; +import PlanQKPlugin from '../../extensions/planqk/PlanQKPlugin'; +import QuantMEPlugin from '../../extensions/quantme/QuantMEPlugin'; +import OpenTOSCAPlugin from "../../extensions/opentosca/OpenTOSCAPlugin"; +import DataFlowPlugin from '../../extensions/data-extension/DataFlowPlugin'; +import QHAnaPlugin from '../../extensions/qhana/QHAnaPlugin'; +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 @@ -16,20 +17,24 @@ import GitHubTab from "../../extensions/quantme/configTabs/GitHubTab"; const PLUGINS = [ { plugin: QuantMEPlugin, - dependencies: ["DataFlowPlugin"], + dependencies: ['DataFlowPlugin'] }, { plugin: DataFlowPlugin, - dependencies: [], + dependencies: [] }, { plugin: QHAnaPlugin, - dependencies: [], + dependencies: [] }, { plugin: PlanQKPlugin, - dependencies: [], + dependencies: [] }, + { + plugin: OpenTOSCAPlugin, + dependencies: [] + } ]; // list of currently active plugins in the current running instance of the modeler, defined based on the plugin configuration @@ -44,13 +49,8 @@ export function getActivePlugins() { 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) - ) { + const dependencyPlugin = PLUGINS.find((p) => p.plugin.name === dependency); + if (dependencyPlugin && !activePlugins.includes(dependencyPlugin.plugin)) { activePlugins.push(dependencyPlugin.plugin); loadPlugin(dependencyPlugin); } @@ -61,9 +61,7 @@ export function getActivePlugins() { for (const pluginConfig of getAllConfigs()) { const plugin = PLUGINS.find( - (p) => - p.plugin.name === pluginConfig.name && - checkEnabledStatus(p.plugin.name) + (p) => p.plugin.name === pluginConfig.name && checkEnabledStatus(p.plugin.name) ); if (plugin) { loadPlugin(plugin); @@ -74,16 +72,20 @@ export function getActivePlugins() { } } + + export function checkEnabledStatus(pluginName) { switch (pluginName) { - case "dataflow": + case 'dataflow': return process.env.ENABLE_DATA_FLOW_PLUGIN !== "false"; - case "planqk": + case 'planqk': return process.env.ENABLE_PLANQK_PLUGIN !== "false"; - case "qhana": + case 'qhana': return process.env.ENABLE_QHANA_PLUGIN !== "false"; - case "quantme": + case 'quantme': return process.env.ENABLE_QUANTME_PLUGIN !== "false"; + case 'opentosca': + return process.env.ENABLE_OPENTOSCA_PLUGIN !== "false"; } } /** @@ -93,6 +95,7 @@ export function checkEnabledStatus(pluginName) { * @returns {*[]} Array of additional modules defined by the active plugins. */ export function getAdditionalModules() { + const modules = []; // load all additional modules of the active plugins @@ -102,7 +105,7 @@ export function getAdditionalModules() { } } - console.log("\n Get Additional Modules"); + console.log('\n Get Additional Modules'); console.log(modules); return modules; } @@ -113,6 +116,7 @@ export function getAdditionalModules() { * @returns {*[]} Array of css style modules defined by the active plugins. */ export function getStyles() { + let styles = []; // load css styles of the active plugins @@ -122,7 +126,7 @@ export function getStyles() { } } - console.log("\n Get Plugin Styling"); + console.log('\n Get Plugin Styling'); console.log(styles); return styles; } @@ -143,7 +147,7 @@ export function getModdleExtension() { } } - console.log("\n Get Moddle Extensions: "); + console.log('\n Get Moddle Extensions: '); console.log(extensions); return extensions; } @@ -163,7 +167,7 @@ export function getTransformationButtons() { } } - console.log("\n Got " + transformationButtons.length + " Transformations"); + console.log('\n Got ' + transformationButtons.length + ' Transformations'); return transformationButtons; } @@ -181,7 +185,7 @@ export function getPluginButtons() { } } - console.log("\n Got " + pluginButtons.length + " Plugin Buttons"); + console.log('\n Got ' + pluginButtons.length + ' Plugin Buttons'); console.log(pluginButtons); return pluginButtons; @@ -194,19 +198,17 @@ export function getPluginButtons() { * @returns {*[]} Array of config tabs defined by the active plugins. */ export function getConfigTabs() { + // add default editor tab to configure editor configs - let configTabs = [ - { - tabId: "EditorTab", - tabTitle: "General", - configTab: GeneralTab, - }, - { - tabId: "GitHubTab", - tabTitle: "GitHub", - configTab: GitHubTab, - }, - ]; + let configTabs = [{ + tabId: '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()) { @@ -215,8 +217,8 @@ export function getConfigTabs() { } } - console.log("\n Got " + configTabs.length + " Config Tabs"); + console.log('\n Got ' + configTabs.length + ' Config Tabs'); console.log(configTabs); return configTabs; -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/editor/resources/styling/editor-ui.css b/components/bpmn-q/modeler-component/editor/resources/styling/editor-ui.css index 575061e6..44e829cc 100644 --- a/components/bpmn-q/modeler-component/editor/resources/styling/editor-ui.css +++ b/components/bpmn-q/modeler-component/editor/resources/styling/editor-ui.css @@ -13,6 +13,20 @@ color: #636363; } +.qwm-properties-btn { + box-sizing: border-box; + outline: none; + background-color: transparent; + border: solid; + border-radius: 6px; + font-size: 15px; + font-family: Arial, serif; + font-weight: bold; + margin: 2px; + padding: 8px; + color: #636363; +} + .qwm-shortcuts { right: 0px; position: absolute; diff --git a/components/bpmn-q/modeler-component/editor/ui/DeploymentButton.js b/components/bpmn-q/modeler-component/editor/ui/DeploymentButton.js index f51fc09f..7475bd74 100644 --- a/components/bpmn-q/modeler-component/editor/ui/DeploymentButton.js +++ b/components/bpmn-q/modeler-component/editor/ui/DeploymentButton.js @@ -1,8 +1,16 @@ -import React from "react"; -import NotificationHandler from "./notifications/NotificationHandler"; -import { deployWorkflowToCamunda } from "../util/IoUtilities"; -import { getCamundaEndpoint } from "../config/EditorConfigManager"; -import { getRootProcess } from "../util/ModellingUtilities"; +import React, {Fragment, useState} from 'react'; +import NotificationHandler from './notifications/NotificationHandler'; +import {deployWorkflowToCamunda} from '../util/IoUtilities'; +import {getCamundaEndpoint} from '../config/EditorConfigManager'; +import {getRootProcess} from '../util/ModellingUtilities'; +import {getServiceTasksToDeploy} from '../../extensions/opentosca/deployment/DeploymentUtils'; +import {getModeler} from '../ModelerHandler'; +import OnDemandDeploymentModal from './OnDemandDeploymentModal'; +import {startOnDemandReplacementProcess} from "../../extensions/opentosca/replacement/OnDemandTransformator"; + +const defaultState = { + windowOpenOnDemandDeployment: false, +}; /** * React button for starting the deployment of the workflow. @@ -12,66 +20,92 @@ import { getRootProcess } from "../util/ModellingUtilities"; * @constructor */ export default function DeploymentButton(props) { - const { modeler } = props; + const [windowOpenOnDemandDeployment, setWindowOpenOnDemandDeployment] = useState(false); + + const {modeler} = props; + + + /** + * Handle result of the on demand deployment dialog + * + * @param result the result from the dialog + */ + async function handleOnDemandDeployment(result) { + if (result && result.hasOwnProperty('onDemand')) { + // get XML of the current workflow + let xml = (await modeler.saveXML({format: true})).xml; + console.log("XML", xml) + if (result.onDemand === true) { + xml = await startOnDemandReplacementProcess(xml); + } + // deploy in any case + deploy(xml); + } + // handle cancellation (don't deploy) + setWindowOpenOnDemandDeployment(false); + + } /** * Deploy the current workflow to the Camunda engine */ - async function deploy() { + async function deploy(xml) { + NotificationHandler.getInstance().displayNotification({ - title: "Deployment started", - content: - "Deployment of the current Workflow to the Camunda Engine under " + - getCamundaEndpoint() + - " started.", + title: 'Deployment started', + content: 'Deployment of the current Workflow to the Camunda Engine under ' + getCamundaEndpoint() + ' started.', }); // get XML of the current workflow const rootElement = getRootProcess(modeler.getDefinitions()); - const xml = (await modeler.saveXML({ format: true })).xml; // check if there are views defined for the modeler and include them in the deployment let viewsDict = {}; if (modeler.views !== undefined) { - console.log("Adding additional views during deployment: ", modeler.views); + console.log('Adding additional views during deployment: ', modeler.views); viewsDict = modeler.views; } // start deployment of workflow and views let result = await deployWorkflowToCamunda(rootElement.id, xml, viewsDict); - if (result.status === "failed") { + if (result.status === 'failed') { NotificationHandler.getInstance().displayNotification({ - type: "error", - title: "Unable to deploy workflow", - content: - "Workflow deployment failed. Please check the configured Camunda engine endpoint!", - duration: 20000, + type: 'error', + title: 'Unable to deploy workflow', + content: 'Workflow deployment failed. Please check the configured Camunda engine endpoint!', + duration: 20000 }); } else { NotificationHandler.getInstance().displayNotification({ - type: "info", - title: "Workflow successfully deployed", - content: - "Workflow successfully deployed under deployment Id: " + - result.deployedProcessDefinition.deploymentId, - duration: 20000, + type: 'info', + title: 'Workflow successfully deployed', + content: 'Workflow successfully deployed under deployment Id: ' + result.deployedProcessDefinition.deploymentId, + duration: 20000 }); } } + async function onClick() { + let csarsToDeploy = getServiceTasksToDeploy(getRootProcess(getModeler().getDefinitions())); + if (csarsToDeploy.length > 0) { + setWindowOpenOnDemandDeployment(true); + } else { + deploy((await modeler.saveXML({format: true})).xml); + } + } + return ( - <> - - + {windowOpenOnDemandDeployment && ( + handleOnDemandDeployment(e)} + /> + )} + ); -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/editor/ui/OnDemandDeploymentModal.js b/components/bpmn-q/modeler-component/editor/ui/OnDemandDeploymentModal.js new file mode 100644 index 00000000..d7ded0da --- /dev/null +++ b/components/bpmn-q/modeler-component/editor/ui/OnDemandDeploymentModal.js @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* eslint-disable no-unused-vars */ +import React from 'react'; + +// polyfill upcoming structural components +import Modal from './modal/Modal'; + +const Title = Modal.Title || (({children}) =>

{children}

); +const Body = Modal.Body || (({children}) =>
{children}
); +const Footer = Modal.Footer || (({children}) =>
{children}
); + +export default function OnDemandDeploymentModal({onClose}) { + + const onOnDemand = (value) => onClose({ + onDemand: value, + }); + + return + + + Workflow Deployment + + + The current workflow contains service task with attached deployment models which support on-demand service deployment. + Would you like to use on-demand service deployment? + +
+
+ + +
+
+
; +} diff --git a/components/bpmn-q/modeler-component/editor/ui/notifications/NotificationHandler.js b/components/bpmn-q/modeler-component/editor/ui/notifications/NotificationHandler.js index 0ef15bfe..1088e8f4 100644 --- a/components/bpmn-q/modeler-component/editor/ui/notifications/NotificationHandler.js +++ b/components/bpmn-q/modeler-component/editor/ui/notifications/NotificationHandler.js @@ -1,7 +1,7 @@ import React from "react"; import Notifications from "./Notifications"; -export const NOTIFICATION_TYPES = ["info", "success", "error", "warning"]; +export const NOTIFICATION_TYPES = ['info', 'success', 'error', 'warning']; /** * Handler to manage notifications displayed to the user. Use getInstance() to get the current instance of the handler. @@ -9,6 +9,7 @@ export const NOTIFICATION_TYPES = ["info", "success", "error", "warning"]; * Implements the Singleton pattern. */ export default class NotificationHandler { + static instance = undefined; static getInstance() { @@ -37,13 +38,8 @@ export default class NotificationHandler { if (notifications) { this.notifications = notifications; } - return ( - - ); + return ; } /** @@ -54,13 +50,14 @@ export default class NotificationHandler { * @param title The title of the notification. * @param content The text displayed by the notification. * @param duration The duration in milliseconds. + * @returns {{update: update, close: close}} */ - displayNotification({ type = "info", title, content, duration = 4000 }) { - this.notificationRef.current.displayNotification({ + displayNotification({type = 'info', title, content, duration = 4000}) { + return this.notificationRef.current.displayNotification({ type: type, title: title, content: content, - duration: duration, + duration: duration }); } @@ -70,4 +67,4 @@ export default class NotificationHandler { closeNotifications() { this.notificationRef.current.closeNotifications(); } -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/editor/util/RenderUtilities.js b/components/bpmn-q/modeler-component/editor/util/RenderUtilities.js index e4dff35c..a9b64c0a 100644 --- a/components/bpmn-q/modeler-component/editor/util/RenderUtilities.js +++ b/components/bpmn-q/modeler-component/editor/util/RenderUtilities.js @@ -3,8 +3,8 @@ import { attr as svgAttr, create as svgCreate, innerSVG, - select as svgSelect, -} from "tiny-svg"; + select as svgSelect +} from 'tiny-svg'; /** * Draw svg path with the given attributes. @@ -15,8 +15,9 @@ import { * @returns {SVGPathElement} */ export function drawPath(parentGfx, d, attrs) { - const path = svgCreate("path"); - svgAttr(path, { d: d }); + + const path = svgCreate('path'); + svgAttr(path, {d: d}); svgAttr(path, attrs); svgAppend(parentGfx, path); @@ -24,30 +25,68 @@ export function drawPath(parentGfx, d, attrs) { return path; } +/** + * Draw a SVG rectangle with the given width, height, border radius and color + * + * Copied from https://github.com/bpmn-io/bpmn-js/blob/master/lib/draw/BpmnRenderer.js + * + * @param parentNode The parent element the svg rectangle is appended to. + * @param width The given width + * @param height The given height + * @param borderRadius The given border radius + * @param color The given color + * @returns {SVGRectElement} + */ +function drawRect(parentNode, width, height, borderRadius, color) { + const rect = svgCreate('rect'); + + svgAttr(rect, { + width: width, + height: height, + rx: borderRadius, + ry: borderRadius, + stroke: color, + strokeWidth: 2, + fill: color + }); + + svgAppend(parentNode, rect); + + return rect; +} + /** * Draw the given SVG in the parent element of type BPMN task * * @param parentGfx The parent element the SVG is drawn in * @param importSVG The SVG * @param svgAttributes Attributes for the SVG + * @param foreground true if SVG should be above Task + * @returns The created svg element */ -export function drawTaskSVG(parentGfx, importSVG, svgAttributes) { +export function drawTaskSVG(parentGfx, importSVG, svgAttributes, foreground) { const innerSvgStr = importSVG.svg, transformDef = importSVG.transform; - const groupDef = svgCreate("g"); - svgAttr(groupDef, { transform: transformDef }); + const groupDef = svgCreate('g'); + svgAttr(groupDef, {transform: transformDef}); innerSVG(groupDef, innerSvgStr); - // set task box opacity to 0 such that icon can be in the background - svgAttr(svgSelect(parentGfx, "rect"), { "fill-opacity": 0 }); + if(!foreground) { + // set task box opacity to 0 such that icon can be in the background + svgAttr(svgSelect(parentGfx, 'rect'), { 'fill-opacity': 0 }); + } if (svgAttributes) { svgAttr(groupDef, svgAttributes); } - // draw svg in the background - parentGfx.prepend(groupDef); + if(foreground) { + parentGfx.append(groupDef); + } else { + parentGfx.prepend(groupDef); + } + return groupDef; } /** @@ -61,8 +100,8 @@ export function drawDataElementSVG(parentGfx, importSVG, svgAttributes) { const innerSvgStr = importSVG.svg, transformDef = importSVG.transform; - const groupDef = svgCreate("g"); - svgAttr(groupDef, { transform: transformDef }); + const groupDef = svgCreate('g'); + svgAttr(groupDef, {transform: transformDef}); innerSVG(groupDef, innerSvgStr); if (svgAttributes) { @@ -70,4 +109,4 @@ export function drawDataElementSVG(parentGfx, importSVG, svgAttributes) { } parentGfx.append(groupDef); -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/data-extension/palette/DataFlowPaletteProvider.js b/components/bpmn-q/modeler-component/extensions/data-extension/palette/DataFlowPaletteProvider.js index 4bd7ae3c..423e8fae 100644 --- a/components/bpmn-q/modeler-component/extensions/data-extension/palette/DataFlowPaletteProvider.js +++ b/components/bpmn-q/modeler-component/extensions/data-extension/palette/DataFlowPaletteProvider.js @@ -1,6 +1,7 @@ -import * as consts from "../Constants"; +import * as consts from '../Constants'; export default class DataFlowPaletteProvider { + constructor(bpmnFactory, create, elementFactory, palette, translate) { this.bpmnFactory = bpmnFactory; this.create = create; @@ -15,14 +16,14 @@ export default class DataFlowPaletteProvider { } createPlanqkServiceTaskEntry() { - const { bpmnFactory, create, elementFactory, translate } = this; + const {bpmnFactory, create, elementFactory, translate} = this; // start creation of a DataMapObject function createDataMapObject(event) { const businessObject = bpmnFactory.create(consts.DATA_MAP_OBJECT); let shape = elementFactory.createShape({ type: consts.DATA_MAP_OBJECT, - businessObject: businessObject, + businessObject: businessObject }); create.start(event, shape); } @@ -32,7 +33,7 @@ export default class DataFlowPaletteProvider { const businessObject = bpmnFactory.create(consts.DATA_STORE_MAP); let shape = elementFactory.createShape({ type: consts.DATA_STORE_MAP, - businessObject: businessObject, + businessObject: businessObject }); create.start(event, shape); } @@ -42,52 +43,53 @@ export default class DataFlowPaletteProvider { const businessObject = bpmnFactory.create(consts.TRANSFORMATION_TASK); let shape = elementFactory.createShape({ type: consts.TRANSFORMATION_TASK, - businessObject: businessObject, + businessObject: businessObject }); create.start(event, shape); } + return { // add separator line to delimit the new group - "dataflow-separator": { - group: "dataflowExt", - separator: true, + 'dataflow-separator': { + group: 'dataflowExt', + separator: true }, - "create.dataflow-data-map-object": { - group: "dataflowExt", - className: "dataflow-data-map-object-palette-icon", - title: translate("Creates a Data Map Object to model data items"), + 'create.dataflow-data-map-object': { + group: 'dataflowExt', + className: 'dataflow-data-map-object-palette-icon', + title: translate('Creates a Data Map Object to model data items'), action: { click: createDataMapObject, dragstart: createDataMapObject, - }, + } }, - "create.dataflow-data-store-map": { - group: "dataflowExt", - className: "dataflow-data-store-map-task-palette-icon", - title: translate("Creates a Data Store Map to model data stores"), + 'create.dataflow-data-store-map': { + group: 'dataflowExt', + className: 'dataflow-data-store-map-task-palette-icon', + title: translate('Creates a Data Store Map to model data stores'), action: { click: createDataStoreMap, dragstart: createDataStoreMap, - }, + } }, - "create.data-flow-transformation-task": { - group: "dataflowExt", - className: "dataflow-transformation-task-palette-icon", - title: translate("Creates a task ot specify data transformations in"), + 'create.data-flow-transformation-task': { + group: 'dataflowExt', + className: 'dataflow-transformation-task-palette-icon', + title: translate('Creates a task ot specify data transformations in'), action: { click: createTransformationTask, dragstart: createTransformationTask, - }, - }, + } + } }; } } DataFlowPaletteProvider.$inject = [ - "bpmnFactory", - "create", - "elementFactory", - "palette", - "translate", -]; + 'bpmnFactory', + 'create', + 'elementFactory', + 'palette', + 'translate' +]; \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/OpenTOSCAPlugin.js b/components/bpmn-q/modeler-component/extensions/opentosca/OpenTOSCAPlugin.js new file mode 100644 index 00000000..63c1d74d --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/OpenTOSCAPlugin.js @@ -0,0 +1,27 @@ +import React from "react"; + +import OpenTOSCATab from "./configTabs/OpenTOSCATab"; + +import OpenTOSCAStyles from './styling/opentosca.css'; +import DeploymentPlugin from "./ui/deployment/services/DeploymentPlugin"; +import OpenTOSCAExtensionModule from "./modeling"; +let OpenTOSCAModdleExtension = require('./resources/opentosca4bpmn.json'); + + +/** + * Plugin Object of the OpenTOSCA extension. Used to register the plugin in the plugin handler of the modeler. + */ +export default { + buttons: [], + configTabs: [ + { + tabId: 'OpenTOSCAEndpointTab', + tabTitle: 'OpenTOSCA Plugin', + configTab: OpenTOSCATab, + } + ], + extensionModule: OpenTOSCAExtensionModule, + moddleDescription: OpenTOSCAModdleExtension, + name: 'opentosca', + styling: [OpenTOSCAStyles] +}; \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/configTabs/OpenTOSCATab.js b/components/bpmn-q/modeler-component/extensions/opentosca/configTabs/OpenTOSCATab.js new file mode 100644 index 00000000..ca6cb92b --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/configTabs/OpenTOSCATab.js @@ -0,0 +1,81 @@ +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/deployment/BindingUtils.js b/components/bpmn-q/modeler-component/extensions/opentosca/deployment/BindingUtils.js similarity index 63% rename from components/bpmn-q/modeler-component/extensions/quantme/deployment/BindingUtils.js rename to components/bpmn-q/modeler-component/extensions/opentosca/deployment/BindingUtils.js index ae581f87..32342208 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/deployment/BindingUtils.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/deployment/BindingUtils.js @@ -8,13 +8,10 @@ * * SPDX-License-Identifier: Apache-2.0 */ +import * as config from "../framework-config/config-manager"; -const QUANTME_NAMESPACE_PULL_ENCODED = encodeURIComponent( - encodeURIComponent("http://quantil.org/quantme/pull") -); -const QUANTME_NAMESPACE_PUSH_ENCODED = encodeURIComponent( - encodeURIComponent("http://quantil.org/quantme/push") -); +const QUANTME_NAMESPACE_PULL_ENCODED = encodeURIComponent(encodeURIComponent('http://quantil.org/quantme/pull')); +const QUANTME_NAMESPACE_PUSH_ENCODED = encodeURIComponent(encodeURIComponent('http://quantil.org/quantme/push')); /** * Check whether the given ServiceTask has an attached deployment model that should be bound using pull or push mode @@ -25,22 +22,19 @@ const QUANTME_NAMESPACE_PUSH_ENCODED = encodeURIComponent( * or undefined if unable to determine pull or push */ export function getBindingType(serviceTask) { - let urlSplit = serviceTask.deploymentModelUrl.split("servicetemplates/"); + let urlSplit = serviceTask.deploymentModelUrl.split('servicetemplates/'); if (urlSplit.length !== 2) { - console.warn( - "Deployment model url is invalid: %s", - serviceTask.deploymentModelUrl - ); + console.warn('Deployment model url is invalid: %s', serviceTask.deploymentModelUrl); return undefined; } let namespace = urlSplit[1]; if (namespace.startsWith(QUANTME_NAMESPACE_PUSH_ENCODED)) { - return "push"; + return 'push'; } if (namespace.startsWith(QUANTME_NAMESPACE_PULL_ENCODED)) { - return "pull"; + return 'pull'; } return undefined; @@ -55,41 +49,34 @@ export function getBindingType(serviceTask) { * @param modeling the modeling element to adapt properties of the workflow elements * @return {{success: boolean}} true if binding is successful, false otherwise */ -export function bindUsingPull( - topicName, - serviceTaskId, - elementRegistry, - modeling -) { - if ( - topicName === undefined || - serviceTaskId === undefined || - elementRegistry === undefined || - modeling === undefined - ) { - console.error( - "Topic name, service task id, element registry, and modeling required for binding using pull!" - ); +export function bindUsingPull(csar, serviceTaskId, elementRegistry, modeling) { + + if (csar.topicName === undefined || serviceTaskId === undefined || elementRegistry === undefined || modeling === undefined) { + console.error('Topic name, service task id, element registry, and modeling required for binding using pull!'); return { success: false }; } // retrieve service task to bind let serviceTask = elementRegistry.get(serviceTaskId); if (serviceTask === undefined) { - console.error( - "Unable to retrieve corresponding task for id: %s", - serviceTaskId - ); + console.error('Unable to retrieve corresponding task for id: %s', serviceTaskId); return { success: false }; } + let deploymentModelUrl = serviceTask.businessObject.get('opentosca:deploymentModelUrl'); + if (deploymentModelUrl.startsWith('{{ wineryEndpoint }}')) { + deploymentModelUrl = deploymentModelUrl.replace('{{ wineryEndpoint }}', config.getWineryEndpoint()); + } + // remove deployment model URL and set topic + modeling.updateProperties(serviceTask, { - deploymentModelUrl: undefined, - type: "external", - topic: topicName, + 'opentosca:deploymentModelUrl': deploymentModelUrl, + 'opentosca:deploymentBuildPlanInstanceUrl': csar.buildPlanUrl, + type: 'external', + topic: csar.topicName }); - return { success: true }; + return {success: true}; } /** @@ -100,39 +87,30 @@ export function bindUsingPull( * @param elementRegistry the element registry of the modeler to find workflow elements * @return {{success: boolean}} true if binding is successful, false otherwise */ -export async function bindUsingPush(csar, serviceTaskId, elementRegistry) { +export async function bindUsingPush(csar, serviceTaskId, elementRegistry, modeling) { let url = await extractSelfserviceApplicationUrl(csar.properties); let success = false; - if ( - csar === undefined || - serviceTaskId === undefined || - elementRegistry === undefined - ) { - console.error( - "CSAR details, service task id, and element registry required for binding using push!" - ); + if (csar === undefined || serviceTaskId === undefined || elementRegistry === undefined) { + console.error('CSAR details, service task id, and element registry required for binding using push!'); return { success: false }; } // retrieve service task to bind let serviceTask = elementRegistry.get(serviceTaskId); if (serviceTask === undefined) { - console.error( - "Unable to retrieve corresponding task for id: %s", - serviceTaskId - ); + console.error('Unable to retrieve corresponding task for id: %s', serviceTaskId); return { success: false }; } let extensionElements = serviceTask.businessObject.extensionElements.values; for (let i = 0; i < extensionElements.length; i++) { let extensionElement = extensionElements[i]; - if (extensionElement.$type === "camunda:Connector") { + if (extensionElement.$type === 'camunda:Connector') { let inputParameters = extensionElement.inputOutput.inputParameters; for (let j = 0; j < inputParameters.length; j++) { let inputParameter = inputParameters[j]; - if (inputParameter.name === "url") { + if (inputParameter.name === 'url') { let connectorUrl = serviceTask.businessObject.connectorUrl; inputParameter.value = url + connectorUrl; success = true; @@ -143,18 +121,20 @@ export async function bindUsingPush(csar, serviceTaskId, elementRegistry) { return { success: success }; } -async function extractSelfserviceApplicationUrl(propertiesUrl) { +async function extractSelfserviceApplicationUrl(propertiesUrl, csar) { + let properties = await fetchProperties(propertiesUrl); let json = JSON.parse(properties); - return json.selfserviceApplicationUrl; + const value = json.selfserviceApplicationUrl; + return value; } async function fetchProperties(url) { const response = await fetch(url, { headers: { - Accept: "application/json", - }, + Accept: "application/json" + } }); const json = await response.text(); return json; -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/deployment/DeploymentUtils.js b/components/bpmn-q/modeler-component/extensions/opentosca/deployment/DeploymentUtils.js similarity index 63% rename from components/bpmn-q/modeler-component/extensions/quantme/deployment/DeploymentUtils.js rename to components/bpmn-q/modeler-component/extensions/opentosca/deployment/DeploymentUtils.js index c3811dbc..c7e73507 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/deployment/DeploymentUtils.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/deployment/DeploymentUtils.js @@ -9,8 +9,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { getBindingType } from "./BindingUtils"; -import { getFlowElementsRecursively } from "../../../editor/util/ModellingUtilities"; +import {getBindingType} from './BindingUtils'; +import {getFlowElementsRecursively} from '../../../editor/util/ModellingUtilities'; /** * Get the ServiceTasks of the current workflow that have an attached deployment model to deploy the corresponding service starting from the given root element @@ -22,7 +22,7 @@ export function getServiceTasksToDeploy(startElement) { let csarsToDeploy = []; if (startElement === undefined) { - console.warn("Element to start is undefined!"); + console.warn('Element to start is undefined!'); return csarsToDeploy; } @@ -32,22 +32,21 @@ export function getServiceTasksToDeploy(startElement) { let flowElement = flowElements[i]; if (isDeployableServiceTask(flowElement)) { - console.log("Found deployable service task: ", flowElement); + console.log('Found deployable service task: ', flowElement); // check if CSAR was already added for another service task - let csarEntry = csarsToDeploy.find( - (serviceTask) => flowElement.deploymentModelUrl === serviceTask.url - ); + let csarEntry = csarsToDeploy.find(serviceTask => flowElement.deploymentModelUrl === serviceTask.url); if (csarEntry !== undefined) { - console.log("Adding to existing CSAR entry..."); + console.log('Adding to existing CSAR entry...'); csarEntry.serviceTaskIds.push(flowElement.id); } else { - csarsToDeploy.push({ - serviceTaskIds: [flowElement.id], - url: flowElement.deploymentModelUrl, - type: getBindingType(flowElement), - csarName: getCSARName(flowElement), - }); + csarsToDeploy.push( + { + serviceTaskIds: [flowElement.id], + url: flowElement.deploymentModelUrl, + type: getBindingType(flowElement), + csarName: getCSARName(flowElement) + }); } } } @@ -62,9 +61,9 @@ export function getServiceTasksToDeploy(startElement) { * @return {*} the CSAR name */ function getCSARName(serviceTask) { - let url = serviceTask.deploymentModelUrl.split("/?csar")[0]; - let urlSplit = url.split("/"); - return urlSplit[urlSplit.length - 1] + ".csar"; + let url = serviceTask.deploymentModelUrl.split('/?csar')[0]; + let urlSplit = url.split('/'); + return urlSplit[urlSplit.length - 1] + '.csar'; } /** @@ -73,11 +72,6 @@ function getCSARName(serviceTask) { * @param element the element to check * @return {*|boolean} true if the element is a ServiceTask and has an assigned deployment model, false otherwise */ -function isDeployableServiceTask(element) { - return ( - element.$type && - element.$type === "bpmn:ServiceTask" && - element.deploymentModelUrl && - getBindingType(element) !== undefined - ); -} +export function isDeployableServiceTask(element) { + return element.$type && element.$type === 'bpmn:ServiceTask' && element.deploymentModelUrl && getBindingType(element) !== undefined; +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/deployment/OpenTOSCAUtils.js b/components/bpmn-q/modeler-component/extensions/opentosca/deployment/OpenTOSCAUtils.js new file mode 100644 index 00000000..3ed27507 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/deployment/OpenTOSCAUtils.js @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import {fetch} from 'whatwg-fetch'; +import {performAjax} from '../utilities/Utilities'; + +/** + * Create a new ArtifactTemplate of the given type and add the given blob as file + * + * @param wineryEndpoint the endpoint of the Winery to create the ArtifactTemplate at + * @param localNamePrefix the prefix of the local name of the artifact to which a random suffix is added if an ArtifactTemplate with the name already exists + * @param namespace the namespace of the ArtifactTemplate to create + * @param type the type of the ArtifactTemplate to create + * @param blob the blob to upload to the ArtifactTemplate + * @param fileName the name of the file to upload as blob + * @return the final name of the created ArtifactTemplate + */ +export async function createNewArtifactTemplate(wineryEndpoint, localNamePrefix, namespace, type, blob, fileName) { + console.log('Creating new ArtifactTemplate of type: ', type); + + // retrieve the currently available ArtifactTemplates + let getArtifactsResult = await fetch(wineryEndpoint + '/artifacttemplates/'); + let getArtifactsResultJson = await getArtifactsResult.json(); + + console.log(getArtifactsResultJson); + + // get unique name for the ArtifactTemplate + let artifactTemplateLocalName = localNamePrefix; + if (getArtifactsResultJson.some(e => e.id === artifactTemplateLocalName && e.namespace === namespace)) { + let nameOccupied = true; + artifactTemplateLocalName += '-' + makeId(1); + while (nameOccupied) { + if (!getArtifactsResultJson.some(e => e.id === artifactTemplateLocalName && e.namespace === namespace)) { + nameOccupied = false; + } else { + artifactTemplateLocalName += makeId(1); + } + } + } + console.log('Creating ArtifactTemplate with name: ', artifactTemplateLocalName); + + // create ArtifactTemplate + let artifactCreationResponse = await fetch(wineryEndpoint + '/artifacttemplates/', { + method: 'POST', + body: JSON.stringify({localname: artifactTemplateLocalName, namespace: namespace, type: type}), + headers: {'Content-Type': 'application/json', 'Accept': 'application/json'} + }); + let artifactCreationResponseText = await artifactCreationResponse.text(); + + // get URL for the file upload to the ArtifactTemplate + let fileUrl = wineryEndpoint + '/artifacttemplates/' + artifactCreationResponseText + 'files'; + + // upload the blob + const fd = new FormData(); + fd.append('file', blob, fileName); + await performAjax(fileUrl, fd); + + return artifactTemplateLocalName; +} + +/** + * Create a new version of the ServiceTemplate with the given ID and namespace and return its URL or an error if the ServiceTemplate does not exist + * + * @param wineryEndpoint the endpoint of the Winery to create the new ServiceTemplate version + * @param serviceTemplateId the ID of the ServiceTemplate to create a new version from + * @param serviceTemplateNamespace the namespace of the ServiceTemplate to create a new version from + * @return the URL to the new version, or an error if the base ServiceTemplate is not available at the given Winery endpoint + */ +export async function createNewServiceTemplateVersion(wineryEndpoint, serviceTemplateId, serviceTemplateNamespace) { + console.log('Creating new version of Service Template with ID %s and namespace %s', serviceTemplateId, serviceTemplateNamespace); + + // retrieve the currently available ServiceTemplates + let getTemplatesResult = await fetch(wineryEndpoint + '/servicetemplates/'); + let getTemplatesResultJson = await getTemplatesResult.json(); + + // abort if base service template is not available + if (!getTemplatesResultJson.some(e => e.id === serviceTemplateId && e.namespace === serviceTemplateNamespace)) { + console.log('Required base ServiceTemplate for deploying Qiskit Runtime programs not available in Winery!'); + return {error: 'Required base ServiceTemplate for deploying Qiskit Runtime programs not available in Winery!'}; + } + + // get unique name for the new version + let version = makeId(5); + let nameOccupied = true; + while (nameOccupied) { + if (!getTemplatesResultJson.some(e => e.id === serviceTemplateId + '_' + version + '-w1-wip1' && e.namespace === serviceTemplateNamespace)) { + nameOccupied = false; + } else { + version += makeId(1); + } + } + console.log('Using component version: ', version); + + // create ServiceTemplate version + let versionUrl = wineryEndpoint + '/servicetemplates/' + encodeURIComponent(encodeURIComponent(serviceTemplateNamespace)) + '/' + serviceTemplateId; + console.log('Creating new version under URL:', versionUrl); + let versionCreationResponse = await fetch(versionUrl, { + method: 'POST', + body: JSON.stringify({ + version: { + componentVersion: version, currentVersion: false, editable: true, latestVersion: false, + releasable: false, wineryVersion: 1, workInProgressVersion: 1 + } + }), + headers: {'Content-Type': 'application/json', 'Accept': 'application/json'} + }); + + // assemble URL to the created ServiceTemplate version + let versionCreationResponseText = await versionCreationResponse.text(); + return wineryEndpoint + '/servicetemplates/' + versionCreationResponseText; +} + +function makeId(length) { + let result = ''; + let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/deployment/WineryUtils.js b/components/bpmn-q/modeler-component/extensions/opentosca/deployment/WineryUtils.js new file mode 100644 index 00000000..0072e717 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/deployment/WineryUtils.js @@ -0,0 +1,299 @@ +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as config from "../framework-config/config-manager"; +import {getWineryEndpoint} from "../framework-config/config-manager"; +import {fetch} from "whatwg-fetch"; + +const QUANTME_NAMESPACE_PUSH = 'http://quantil.org/quantme/push'; + +export async function createArtifactTemplate(name, artifactTypeQName) { + const artifactTemplate = { + localname: name, + namespace: QUANTME_NAMESPACE_PUSH + '/artifacttemplates', + type: artifactTypeQName, + }; + const response = await fetch(`${getWineryEndpoint()}/artifacttemplates`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'text/plain' + }, + body: JSON.stringify(artifactTemplate) + }); + return response.text(); +} + +export async function addFileToArtifactTemplate(artifactTemplateAddress, file) { + const formData = new FormData(); + formData.append('file', file); + const response = await fetch(`${getWineryEndpoint()}/artifacttemplates/${artifactTemplateAddress}files`, { + method: 'POST', + body: formData, + headers: { + 'Accept': '*/*' + }, + }); + return response.json(); +} + +export async function createArtifactTemplateWithFile(name, artifactType, file) { + const artifactTemplateAddress = await createArtifactTemplate(name, artifactType); + await addFileToArtifactTemplate(artifactTemplateAddress, file); + return artifactTemplateAddress; +} + +export async function createServiceTemplate(name) { + const serviceTemplate = { + localname: name, + namespace: QUANTME_NAMESPACE_PUSH, + }; + const response = await fetch(getWineryEndpoint() + '/servicetemplates', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'text/plain' + }, + body: JSON.stringify(serviceTemplate) + }); + return response.text(); +} + +export async function deleteArtifactTemplate(artifactTemplateName) { + // /artifacttemplates/http%253A%252F%252Fquantil.org%252Fquantme%252Fpush%252Fartifacttemplates/ArtifactTemplate-Activity_01b3qkz/ + const response = await fetch(`${getWineryEndpoint()}/artifacttemplates/${encodeURIComponent(encodeURIComponent(QUANTME_NAMESPACE_PUSH + '/'))}artifacttemplates/${encodeURIComponent(encodeURIComponent(artifactTemplateName))}`, { + method: 'DELETE', + headers: { + 'Accept': 'application/json' + }, + }); + return response.status === 204; +} + +export async function serviceTemplateExists(serviceTemplateName) { + const response = await fetch(`${getWineryEndpoint()}/servicetemplates/${encodeURIComponent(encodeURIComponent(QUANTME_NAMESPACE_PUSH))}/${encodeURIComponent(encodeURIComponent(serviceTemplateName))}`, { + method: 'GET', + }); + return response.status === 200; +} + +export async function addNodeWithArtifactToServiceTemplateByName(serviceTemplateName, nodeTypeQName, name, artifactTemplateQName, artifactName, artifactTypeQName) { + const serviceTemplateAddress = encodeURIComponent(encodeURIComponent(QUANTME_NAMESPACE_PUSH)) + '/' + encodeURIComponent(encodeURIComponent(serviceTemplateName)) + '/'; + await addNodeWithArtifactToServiceTemplate(serviceTemplateAddress, nodeTypeQName, name, artifactTemplateQName, artifactName, artifactTypeQName); + return serviceTemplateAddress; +} + +export async function addNodeToServiceTemplate(serviceTemplateAddress, nodeTypeQName, name) { + const nodeTemplate = { + "documentation": [], + "any": [], + "otherAttributes": {}, + "relationshipTemplates": [], + "nodeTemplates": [ + { + "documentation": [], + "any": [], + "otherAttributes": { + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": 1245, + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": 350 + }, + "properties": { + "propertyType": "KV", + "kvproperties": { + "Port": "", + "Name": "" + }, + "elementName": "properties", + "namespace": "http://opentosca.org/nodetypes/propertiesdefinition/winery" + }, + "id": nodeTypeQName.split(/}(.*)/s)[1], + "type": nodeTypeQName, + "name": name, + "minInstances": 1, + "maxInstances": 1, + "x": 1245, + "y": 350, + "capabilities": [], + "requirements": [], + "deploymentArtifacts": null, + "policies": null + } + ] + }; + const response = await fetch(`${getWineryEndpoint()}/servicetemplates/${serviceTemplateAddress}topologytemplate`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': '*/*' + }, + body: JSON.stringify(nodeTemplate) + }); + return response.status === 204; +} + +export async function addNodeWithArtifactToServiceTemplate(serviceTemplateAddress, nodeTypeQName, name, artifactTemplateQName, artifactName, artifactTypeQName) { + const nodeTemplate = { + "documentation": [], + "any": [], + "otherAttributes": {}, + "relationshipTemplates": [], + "nodeTemplates": [{ + "documentation": [], + "any": [], + "otherAttributes": { + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": 1245, + "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": 350 + }, + "properties": { + "propertyType": "KV", + "kvproperties": { + "Port": "", + "Name": "" + }, + "elementName": "properties", + "namespace": "http://opentosca.org/nodetypes/propertiesdefinition/winery" + }, + "id": nodeTypeQName.split(/}(.*)/s)[1], + "type": nodeTypeQName, + "name": name, + "minInstances": 1, + "maxInstances": 1, + "x": 1245, + "y": 350, + "capabilities": [], + "requirements": [], + "deploymentArtifacts": [{ + "documentation": [], + "any": [], + "otherAttributes": {}, + "name": artifactName, + "artifactType": artifactTypeQName, + "artifactRef": artifactTemplateQName + }], + "policies": null + } + ] + }; + const response = await fetch(`${getWineryEndpoint()}/servicetemplates/${serviceTemplateAddress}topologytemplate`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': '*/*' + }, + body: JSON.stringify(nodeTemplate) + }); + return response.status === 204; +} + +export async function getServiceTemplateXML(serviceTemplateAddress) { + const response = await fetch(`${getWineryEndpoint()}/servicetemplates/${serviceTemplateAddress}xml`, { + method: 'GET', + headers: { + 'Accept': 'application/xml' + }, + }); + return response.json(); +} + +export async function setServiceTemplateXML(serviceTemplateAddress, newXml) { + const response = await fetch(getWineryEndpoint() + '/servicetemplates/' + serviceTemplateAddress + 'xml', { + method: 'PUT', + body: newXml, + headers: {'Content-Type': 'application/xml'} + }); +} + +export async function insertTopNodeTag(serviceTemplateAddress, nodeTypeQName) { + const tag = { + name: "top-node", + value: nodeTypeQName.split(/}(.*)/s)[1], + }; + const response = await fetch(getWineryEndpoint() + '/servicetemplates/' + serviceTemplateAddress + "tags/", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': '*/*' + }, + body: JSON.stringify(tag) + }); + return response.text(); +} + +export async function createServiceTemplateWithNodeAndArtifact(name, nodeTypeQName, nodeName, artifactTemplateQName, artifactName, artifactTypeQName) { + const serviceTemplateAddress = await createServiceTemplate(name); + await addNodeWithArtifactToServiceTemplate(serviceTemplateAddress, nodeTypeQName, nodeName, artifactTemplateQName, artifactName, artifactTypeQName); + return serviceTemplateAddress; +} + +export async function getArtifactTemplateInfo(artifactTemplateAddress) { + const response = await fetch(`${getWineryEndpoint()}/artifacttemplates/${artifactTemplateAddress}`, { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }); + return response.json(); +} + +const nodeTypeQNameMapping = new Map([ + ['{http://opentosca.org/artifacttypes}WAR', '{http://opentosca.org/nodetypes}TomcatApplication_WAR-w1'], + ['{http://opentosca.org/artifacttypes}WAR17', '{http://opentosca.org/nodetypes}TomcatApplication_WAR-w1'], + ['{http://opentosca.org/artifacttypes}WAR8', '{http://opentosca.org/nodetypes}TomcatApplication_WAR-w1'], + ['{http://opentosca.org/artifacttypes}PythonArchiveArtifact', '{http://opentosca.org/nodetypes}PythonApp_3-w1'], +]); +export function getNodeTypeQName(artifactTypeQName) { + return nodeTypeQNameMapping.get(artifactTypeQName); +} + +export async function loadTopology(deploymentModelUrl) { + if (deploymentModelUrl.startsWith('{{ wineryEndpoint }}')) { + deploymentModelUrl = deploymentModelUrl.replace('{{ wineryEndpoint }}', config.getWineryEndpoint()); + } + let topology; + let tags; + try { + topology = await fetch(deploymentModelUrl.replace('?csar', 'topologytemplate')) + .then(res => res.json()); + tags = await fetch(deploymentModelUrl.replace('?csar', 'tags')) + .then(res => res.json()); + + } catch (e) { + throw new Error('An unexpected error occurred during loading the deployments models topology.'); + } + let topNode; + const topNodeTag = tags.find(tag => tag.name === "top-node"); + if (topNodeTag) { + const topNodeId = topNodeTag.value; + topNode = topology.nodeTemplates.find(nodeTemplate => nodeTemplate.id === topNodeId); + if (!topNode) { + throw new Error(`Top level node "${topNodeId}" not found.`); + } + } else { + let nodes = new Map(topology.nodeTemplates.map(nodeTemplate => [nodeTemplate.id, nodeTemplate])); + for (let relationship of topology.relationshipTemplates) { + if (relationship.name === "HostedOn") { + nodes.delete(relationship.targetElement.ref); + } + } + if (nodes.size === 1) { + topNode = nodes.values().next().value; + } + } + if (!topNode) { + throw new Error("No top level node found."); + } + + return { + topNode, + nodeTemplates: topology.nodeTemplates, + relationshipTemplates: topology.relationshipTemplates + }; +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/framework-config/config-manager.js b/components/bpmn-q/modeler-component/extensions/opentosca/framework-config/config-manager.js new file mode 100644 index 00000000..e9614967 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/framework-config/config-manager.js @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import defaultConfig from "./config"; +import {getPluginConfig} from '../../../editor/plugin/PluginConfigHandler'; + +let config = {}; + +/** + * Get the endpoint of the configured OpenTOSCA container + * + * @return {string} the currently specified endpoint of the OpenTOSCA container + */ +export function getOpenTOSCAEndpoint() { + if (config.opentoscaEndpoint === undefined) { + setOpenTOSCAEndpoint( + getPluginConfig('opentosca').opentoscaEndpoint + || defaultConfig.opentoscaEndpoint); + } + return config.opentoscaEndpoint; +} + +/** + * Set the endpoint of the OpenTOSCA container + * + * @param opentoscaEndpoint the endpoint of the OpenTOSCA container + */ +export function setOpenTOSCAEndpoint(opentoscaEndpoint) { + if (opentoscaEndpoint !== null && opentoscaEndpoint !== undefined) { + config.opentoscaEndpoint = opentoscaEndpoint.replace(/\/$/, ''); + } +} + +/** + * Get the endpoint of the configured Winery + * + * @return {string} the currently specified endpoint of the Winery + */ +export function getWineryEndpoint() { + if (config.wineryEndpoint === undefined) { + setWineryEndpoint( + getPluginConfig('opentosca').wineryEndpoint + || defaultConfig.wineryEndpoint); + } + return config.wineryEndpoint; +} + +/** + * Set the endpoint of the Winery + * + * @param wineryEndpoint the endpoint of the Winery + */ +export function setWineryEndpoint(wineryEndpoint) { + if (wineryEndpoint !== null && wineryEndpoint !== undefined) { + config.wineryEndpoint = wineryEndpoint.replace(/\/$/, ''); + } +} + +/** + * Reset all saved endpoints and configuration values back to default or the value of the respective plugin config + * by setting this.comfig to an empty js object. + */ +export function resetConfig() { + config = {}; +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/framework-config/config.js b/components/bpmn-q/modeler-component/extensions/opentosca/framework-config/config.js new file mode 100644 index 00000000..144ee909 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/framework-config/config.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// takes either the environment variables or the default values definded in webpack.config +const defaultConfig = { + opentoscaEndpoint: process.env.OPENTOSCA_ENDPOINT, + wineryEndpoint: process.env.WINERY_ENDPOINT, +}; +export default defaultConfig; \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/framework-config/index.js b/components/bpmn-q/modeler-component/extensions/opentosca/framework-config/index.js new file mode 100644 index 00000000..3acdba7f --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/framework-config/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +import * as configManager from './config-manager'; + +const config = configManager; +export default config; diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCARenderer.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCARenderer.js new file mode 100644 index 00000000..40299fda --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCARenderer.js @@ -0,0 +1,380 @@ +import { + connectRectangles +} from 'diagram-js/lib/layout/ManhattanLayout'; + +import { + createLine, +} from 'diagram-js/lib/util/RenderUtil'; + +import BpmnRenderer from 'bpmn-js/lib/draw/BpmnRenderer'; +import { getOrientation } from 'diagram-js/lib/layout/LayoutUtil'; + + +import buttonIcon from '../resources/show-deployment-button.svg?raw'; +import {drawTaskSVG} from '../../../editor/util/RenderUtilities'; +import NotificationHandler from '../../../editor/ui/notifications/NotificationHandler'; +import {append as svgAppend, attr as svgAttr, create as svgCreate, select, prepend as svgPrepend} from 'tiny-svg'; +import {query as domQuery} from 'min-dom'; +import { + isSnapped, + setSnapped, + topLeft, + bottomRight +} from 'diagram-js/lib/features/snapping/SnapUtil'; + +import {loadTopology} from "../deployment/WineryUtils"; + +const HIGH_PRIORITY = 14001; +const SERVICE_TASK_TYPE = 'bpmn:ServiceTask'; +const DEPLOYMENT_GROUP_ID = 'deployment'; +const DEPLOYMENT_REL_MARKER_ID = 'deployment-rel'; + +const NODE_WIDTH = 100; +const NODE_HEIGHT = 60; +const NODE_SHIFT_MARGIN = 10; +const STROKE_STYLE = { + strokeLinecap: 'round', + strokeLinejoin: 'round', + stroke: '#777777', + strokeWidth: 2, + strokeDasharray: 4, +}; + +export default class OpenTOSCARenderer extends BpmnRenderer { + constructor(config, eventBus, styles, pathMap, canvas, textRenderer, commandStack, elementRegistry) { + super(config, eventBus, styles, pathMap, canvas, textRenderer, HIGH_PRIORITY); + this.commandStack = commandStack; + this.elementRegistry = elementRegistry; + this.styles = styles; + this.textRenderer = textRenderer; + + // snap boundary events + eventBus.on([ + 'create.move', + 'create.end', + 'shape.move.move', + 'shape.move.end' + ], 140000, function(event) { + var context = event.context, + canExecute = context.canExecute, + target = context.target; + + var canAttach = canExecute && (canExecute === 'attach' || canExecute.attach); + const isRelevantEvent = context.shape.type === "bpmn:IntermediateThrowEvent" || context.shape.type === "bpmn:BoundaryEvent"; + + if (canAttach && isRelevantEvent && context.target.businessObject.get('opentosca:deploymentModelUrl') && getOrientation(event, target, -30) === "bottom-right") { + // Prevent snapping on deployment visualisation toggle button + event.stopPropagation(); + } + }); + + this.openToscaHandlers = { + [SERVICE_TASK_TYPE]: function (self, parentGfx, element) { + const task = self.renderer('bpmn:ServiceTask')(parentGfx, element); + self.maybeAddShowDeploymentModelButton(parentGfx, element); + return task; + } + }; + this.addMarkerDefinition(canvas); + this.registerShowDeploymentModelHandler(); + this.currentlyShownDeploymentsModels = new Map(); + } + + registerShowDeploymentModelHandler() { + this.commandStack.register("deploymentModel.showAll", { + execute: ({showDeploymentModel}) => { + const elementsWithDeploymentModel = this.elementRegistry + .filter(element => element.businessObject.get('opentosca:deploymentModelUrl')); + const changed = []; + for (const element of elementsWithDeploymentModel) { + const wasShownDeploymentModel = !!element.showDeploymentModel; + element.showDeploymentModel = showDeploymentModel; + element.wasShownDeploymentModel = wasShownDeploymentModel; + if (wasShownDeploymentModel !== showDeploymentModel) { + changed.push(element); + } + } + return changed; + }, + + revert({showDeploymentModel}) { + return this.execute({showDeploymentModel: !showDeploymentModel}); + } + }); + + this.commandStack.register("deploymentModel.show", { + execute: ({element, showDeploymentModel}) => { + const wasShownDeploymentModel = !!element.showDeploymentModel; + element.showDeploymentModel = showDeploymentModel; + element.wasShownDeploymentModel = wasShownDeploymentModel; + if (wasShownDeploymentModel !== showDeploymentModel) { + return [element]; + } + return []; + }, + + revert({element, showDeploymentModel}) { + return this.execute({element, showDeploymentModel: !showDeploymentModel}); + } + }); + } + + addMarkerDefinition(canvas) { + const marker = svgCreate('marker', { + id: DEPLOYMENT_REL_MARKER_ID, + viewBox: '0 0 8 8', + refX: 8, + refY: 4, + markerWidth: 8, + markerHeight: 8, + orient: 'auto' + }); + svgAppend(marker, svgCreate('path', { + d: 'M 0 0 L 8 4 L 0 8', + ...this.styles.computeStyle({}, ['no-fill'], { + ...STROKE_STYLE, + strokeWidth: 1, + strokeDasharray: 2 + }) + })); + + let defs = domQuery('defs', canvas._svg); + if (!defs) { + defs = svgCreate('defs'); + + svgPrepend(canvas._svg, defs); + } + svgAppend(defs, marker); + } + + maybeAddShowDeploymentModelButton(parentGfx, element) { + let deploymentModelUrl = element.businessObject.get('opentosca:deploymentModelUrl'); + if (!deploymentModelUrl) return; + + const button = drawTaskSVG(parentGfx, { + transform: 'matrix(0.3, 0, 0, 0.3, 85, 65)', + svg: buttonIcon + }, null, true); + button.style['pointer-events'] = 'all'; + button.style['cursor'] = 'pointer'; + button.addEventListener('click', (e) => { + e.preventDefault(); + element.deploymentModelTopology = undefined; + this.commandStack.execute("deploymentModel.show", { + element: element, + showDeploymentModel: !element.showDeploymentModel + }); + }); + if (element.showDeploymentModel) { + this.showDeploymentModel(parentGfx, element, deploymentModelUrl); + } else { + this.removeDeploymentModel(parentGfx, element); + } + } + + async showDeploymentModel(parentGfx, element, deploymentModelUrl) { + if (!element.deploymentModelTopology || element.loadedDeploymentModelUrl !== deploymentModelUrl) { + try { + const topology = await loadTopology(deploymentModelUrl); + element.loadedDeploymentModelUrl = deploymentModelUrl; + element.deploymentModelTopology = topology; + } catch (e) { + element.showDeploymentModel = false; + element.loadedDeploymentModelUrl = null; + element.deploymentModelTopology = null; + this.removeDeploymentModel(parentGfx, element); + console.error(e); + NotificationHandler.getInstance().displayNotification({ + type: 'warning', + title: 'Could not load topology', + content: e.message, + duration: 2000 + }); + return; + } + } + const groupDef = svgCreate('g', {id: DEPLOYMENT_GROUP_ID}); + parentGfx.append(groupDef); + + const {nodeTemplates, relationshipTemplates, topNode} = element.deploymentModelTopology; + + let ySubtract = parseInt(topNode.y); + let xSubtract = parseInt(topNode.x); + + const positions = new Map(); + for (let nodeTemplate of nodeTemplates) { + const position = { + x: (parseInt(nodeTemplate.x) - xSubtract) / 1.4, + y: (parseInt(nodeTemplate.y) - ySubtract) / 1.4, + }; + + positions.set(nodeTemplate.id, position); + if (nodeTemplate.id !== topNode.id) { + this.drawNodeTemplate(groupDef, nodeTemplate, position); + } + } + const boundingBox = { + left: Math.min(...[...positions.values()].map(p => p.x)) + element.x, + top: Math.min(...[...positions.values()].map(p => p.y)) + element.y, + right: Math.max(...[...positions.values()].map(p => p.x)) + NODE_WIDTH + element.x, + bottom: Math.max(...[...positions.values()].map(p => p.y)) + NODE_HEIGHT + element.y + }; + + const previousBoundingBox = this.currentlyShownDeploymentsModels.get(element.id)?.boundingBox; + if (JSON.stringify(previousBoundingBox) !== JSON.stringify(boundingBox)) { + this.mayBeMoveNeighborNodes(boundingBox, element); + } + + this.currentlyShownDeploymentsModels.set(element.id, { + boundingBox + }); + + for (let relationshipTemplate of relationshipTemplates) { + const start = positions.get(relationshipTemplate.sourceElement.ref); + const end = positions.get(relationshipTemplate.targetElement.ref); + this.drawRelationship(groupDef, + start, relationshipTemplate.sourceElement.ref === topNode.id, + end, relationshipTemplate.targetElement.ref === topNode.id); + } + } + + mayBeMoveNeighborNodes(newBoundingBox, element) { + let shifts = { + right: 0, + left: 0, + }; + for (const [otherElementId, otherDeploymentModel] of this.currentlyShownDeploymentsModels.entries()) { + if (otherElementId === element.id) continue; + const otherBoundingBox = otherDeploymentModel.boundingBox; + if (newBoundingBox.left < otherBoundingBox.right && newBoundingBox.right > otherBoundingBox.left && + newBoundingBox.top < otherBoundingBox.bottom && newBoundingBox.bottom > otherBoundingBox.top) { + const distRightShift = newBoundingBox.right - otherBoundingBox.left - shifts.right; + const distLeftShift = otherBoundingBox.right - newBoundingBox.left - shifts.left; + + if (distRightShift < distLeftShift && distRightShift > 0) { + shifts.right += distRightShift; + } else if (distLeftShift < distRightShift && distLeftShift > 0) { + shifts.left += distLeftShift; + } + } + } + + const allElements = this.elementRegistry.getAll(); + const commands = []; + + if (shifts.right || shifts.left) { + const xPosition = (newBoundingBox.left + newBoundingBox.right) / 2; + for (const otherElement of allElements) { + let otherXPosition = element.x + NODE_WIDTH / 2; + const otherElementBoundingBox = this.currentlyShownDeploymentsModels.get(otherElement.id)?.boundingBox; + if (otherElementBoundingBox) { + otherXPosition = (otherElementBoundingBox.left + otherElementBoundingBox.right) / 2; + } + let xShift; + if (shifts.right && otherXPosition >= xPosition && otherElement.id !== element.id) { + xShift = shifts.right + NODE_SHIFT_MARGIN; + } else if (shifts.left && otherXPosition <= xPosition && otherElement.id !== element.id) { + xShift = -shifts.left - NODE_SHIFT_MARGIN; + } else { + continue; + } + // Can not move elements without parent + if(!otherElement.parent) continue; + commands.push({ + cmd: 'shape.move', + context: { + shape: otherElement, + hints: {}, + delta: {x: xShift, y: 0} + } + }); + if (otherElementBoundingBox) { + otherElementBoundingBox.left += xShift; + otherElementBoundingBox.right += xShift; + } + } + } + + if (commands.length > 0) { + this.commandStack.execute('properties-panel.multi-command-executor', commands); + } + } + + removeDeploymentModel(parentGfx, element) { + this.currentlyShownDeploymentsModels.delete(element.id); + const group = select(parentGfx, '#' + DEPLOYMENT_GROUP_ID); + if (group) { + group.remove(); + } + } + + drawRelationship(parentGfx, start, startIsToplevel, end, endIsToplevel) { + const line = createLine(connectRectangles({ + width: NODE_WIDTH, + height: startIsToplevel ? 80 : NODE_HEIGHT, + ...start + }, { + width: NODE_WIDTH, + height: endIsToplevel ? 80 : NODE_HEIGHT, + ...end + }), this.styles.computeStyle({}, ['no-fill'], { + ...STROKE_STYLE, + markerEnd: `url(#${DEPLOYMENT_REL_MARKER_ID})` + }), 5); + parentGfx.prepend(line); + } + + drawNodeTemplate(parentGfx, nodeTemplate, position) { + const groupDef = svgCreate('g'); + svgAttr(groupDef, {transform: `matrix(1, 0, 0, 1, ${position.x.toFixed(2)}, ${position.y.toFixed(2)})`}); + const rect = svgCreate('rect', { + width: NODE_WIDTH, + height: NODE_HEIGHT, + fill: '#DDDDDD', + ...STROKE_STYLE + }); + + svgAppend(groupDef, rect); + + const text = this.textRenderer.createText(nodeTemplate.name, { + box: { + width: NODE_WIDTH, + height: NODE_HEIGHT, + }, + align: 'center-middle' + }); + svgAppend(groupDef, text); + parentGfx.append(groupDef); + } + + renderer(type) { + return this.handlers[type]; + } + + canRender(element) { + // only return true if handler for rendering is registered + return this.openToscaHandlers[element.type]; + } + + drawShape(parentNode, element) { + if (element.type in this.openToscaHandlers) { + const h = this.openToscaHandlers[element.type]; + return h(this, parentNode, element); + } + return super.drawShape(parentNode, element); + } +} + +OpenTOSCARenderer.$inject = [ + 'config', + 'eventBus', + 'styles', + 'pathMap', + 'canvas', + 'textRenderer', + 'commandStack', + 'elementRegistry' +]; + + + diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/index.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/index.js new file mode 100644 index 00000000..814c71ca --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/index.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +import ServiceTaskPropertiesProvider from './properties-provider/ServiceTaskPropertiesProvider' +import OpenTOSCARenderer from './OpenTOSCARenderer'; + +export default { + __init__: ['openToscaRenderer', 'serviceTaskPropertyProvider'], + openToscaRenderer: ['type', OpenTOSCARenderer], + serviceTaskPropertyProvider: ['type', ServiceTaskPropertiesProvider], +}; diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ArtifactUpload.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ArtifactUpload.js new file mode 100644 index 00000000..df10dcb5 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ArtifactUpload.js @@ -0,0 +1,29 @@ +import {HeaderButton} from '@bpmn-io/properties-panel'; +import React from 'react'; +import ArtifactModal from './ArtifactUploadModal'; +import {createRoot} from 'react-dom/client'; +import './artifact-modal.css'; +import {useService} from "bpmn-js-properties-panel"; + +/** + * Entry to display the button which opens the Artifact Upload modal + */ +export function ArtifactUpload(props) { + const {translate, element} = props; + const commandStack = useService('commandStack'); + + + const onClick = () => { + const root = createRoot(document.getElementById("modal-container")); + root.render( root.unmount()} element={element} commandStack={commandStack}/>); + }; + + return HeaderButton({ + id: 'artifact-upload-button', + description: translate('Upload Artifact'), + className: "qwm-artifact-upload-btn", + children: translate('Upload Artifact'), + title: translate('Upload Artifact'), + onClick, + }); +} diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ArtifactUploadModal.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ArtifactUploadModal.js new file mode 100644 index 00000000..2a818167 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ArtifactUploadModal.js @@ -0,0 +1,250 @@ +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* eslint-disable no-unused-vars */ +import React, {useState} from 'react'; +import Modal from '../../../../editor/ui/modal/Modal'; +import './artifact-modal.css'; +import '../../../../editor/config/config-modal.css'; +import { + createArtifactTemplateWithFile, + createServiceTemplateWithNodeAndArtifact, + getNodeTypeQName, + getArtifactTemplateInfo, + insertTopNodeTag, + serviceTemplateExists, + addNodeWithArtifactToServiceTemplateByName, + deleteArtifactTemplate +} from '../../deployment/WineryUtils'; +import NotificationHandler from '../../../../editor/ui/notifications/NotificationHandler'; +import {getWineryEndpoint} from "../../framework-config/config-manager"; + + +// polyfill upcoming structural components +const Title = Modal.Title; +const Body = Modal.Body; +const Footer = Modal.Footer; + +/** + * Modal that allows the user to create OpenTOSCA deployment models based on a given artifact. + * An artifact can either be a local file or a Dockerimage reference. + * + * @param onClose Function called when the modal is closed. + * @param wineryEndpoint Endpoint url of winery. + * @param element Service Task represented as element + * @returns {JSX.Element} The modal as React component + * @constructor + */ +export default function ArtifactUploadModal({onClose, element, commandStack}) { + const [uploadFile, setUploadFile] = useState(null); + const [textInputDockerImage, setTextInputDockerImage] = useState(''); + const [selectedTab, setSelectedTab] = useState("artifact"); + const [selectedOption, setSelectedOption] = useState(""); + const [selectedOptionName, setSelectedOptionName] = useState(""); + const [artifactTypes, setArtifactTypes] = useState([]); + const [acceptTypes, setAcceptTypes] = useState(''); + + + async function updateArtifactSelect() { + const response = await fetch(`${getWineryEndpoint()}/artifacttypes/?includeVersions=true`, { + headers: { + 'Accept': 'application/json', + }, + }).then(res => res.json()); + + const artifactTypes = response + .filter(option => option.name.includes("WAR") || option.name.includes("PythonArchive")); + setArtifactTypes(artifactTypes); + } + + const allowedFileTypes = { + zip: '.tar.gz', + war: '.war', + }; + + async function createServiceTemplate() { + const {close: closeNotification} = NotificationHandler.getInstance().displayNotification({ + type: 'info', + title: 'Uploading Artifact...', + content: 'Uploading artifact for ' + element.id, + duration: 1000 * 60 * 60 // very long time out because this notification gets closed after the API calls are finished + }); + let serviceTemplateAddress; + let doesExist = false; + try { + const namePrefix = element.businessObject.name ?? ""; + const artifactTemplateName = `${namePrefix}ArtifactTemplate-${element.id}`; + await deleteArtifactTemplate(artifactTemplateName); + const artifactTemplateAddress = await createArtifactTemplateWithFile(artifactTemplateName, selectedOption, uploadFile); + const artifactTemplateInfo = await getArtifactTemplateInfo(artifactTemplateAddress); + const artifactTemplateQName = artifactTemplateInfo.serviceTemplateOrNodeTypeOrNodeTypeImplementation[0].type; + const nodeTypeQName = getNodeTypeQName(selectedOption); + const serviceTemplateName = `${namePrefix}ServiceTemplate-${element.id}`; + const doesExist = await serviceTemplateExists(serviceTemplateName); + if(doesExist) { + serviceTemplateAddress = await addNodeWithArtifactToServiceTemplateByName(serviceTemplateName, nodeTypeQName, + `${namePrefix}Node-${element.id}`, artifactTemplateQName, + `${namePrefix}Artifact-${element.id}`, selectedOption); + } else { + serviceTemplateAddress = await createServiceTemplateWithNodeAndArtifact(serviceTemplateName, nodeTypeQName, + `${namePrefix}Node-${element.id}`, artifactTemplateQName, + `${namePrefix}Artifact-${element.id}`, selectedOption); + } + await insertTopNodeTag(serviceTemplateAddress, nodeTypeQName); + } catch (e) { + setTimeout(closeNotification, 1); + NotificationHandler.getInstance().displayNotification({ + type: 'error', + title: 'Service Template Creation failed', + content: 'Service Template could not be created due to an internal error.', + duration: 2000 + }); + return; + } + const deploymentModelUrl = `{{ wineryEndpoint }}/servicetemplates/${serviceTemplateAddress}?csar`; + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: element.businessObject, + properties: { + 'opentosca:deploymentModelUrl': deploymentModelUrl + } + }); + setTimeout(closeNotification, 1); + const content = doesExist ? 'updated' : 'created'; + NotificationHandler.getInstance().displayNotification({ + type: 'info', + title: 'Service Template ' + content.charAt(0).toUpperCase() + content.slice(1), + content: 'Service Template including Nodetype with attached Deployment Artifact of chosen type was successfully ' + content + '.', + duration: 4000 + }); + } + + + const onSubmit = async () => { + // Process the uploaded file or text input here + console.log('Uploaded file:', uploadFile); + console.log('Text input:', textInputDockerImage); + if (selectedTab === "artifact") { + if (uploadFile !== null && selectedOption !== "") { + onClose(); + await createServiceTemplate(); + } else { + onClose(); + setTimeout(function () { + NotificationHandler.getInstance().displayNotification({ + type: 'error', + title: 'No file selected!', + content: 'Please select a file to create an artifact!', + duration: 4000 + }); + }, 300); + + } + } + }; + + const handleOptionChange = (e) => { + const {value} = e.target; + setSelectedOption(value); + setSelectedOptionName(artifactTypes.find(x => x.qName === value).name); + if (value.includes("WAR")) { + setAcceptTypes(allowedFileTypes.war); + } else if (value.includes("PythonArchive")) { + setAcceptTypes(allowedFileTypes.zip); + } + }; + + const isOptionSelected = selectedOption !== ""; + + if (artifactTypes.length === 0) { + updateArtifactSelect(); + } + + + return ( + + Artifact Upload + + +
+
+
setSelectedTab("artifact")} + > + Local File +
+
setSelectedTab("docker")} + style={{display: "none"}} + > + Docker Image +
+
+ + {selectedTab === "artifact" && ( +
+
+
+ + +
+ {isOptionSelected && ( +
+
+ + setUploadFile(e.target.files[0])} + /> +
+
+ )} +
+
+ )} + {selectedTab === "docker" && ( +
+ + setTextInputDockerImage(e.target.value)} + /> +
+ )} +
+ + +
+
+ + +
+
+
+ ); +} diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/Connector.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/Connector.js new file mode 100644 index 00000000..a312bf82 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/Connector.js @@ -0,0 +1,235 @@ +import { SelectEntry } from "@bpmn-io/properties-panel"; +import React from "@bpmn-io/properties-panel/preact/compat"; +import { useService } from "bpmn-js-properties-panel"; +import { getModeler } from "../../../../editor/ModelerHandler"; + +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +const yaml = require('js-yaml'); +/** + * Entry to display the endpoints of the uploaded openapi specification for BPMN service task. + */ +export function Connector({ element, translate, urls }) { + + const modeling = useService('modeling'); + const debounce = useService('debounceInput'); + + let arrValues = []; + for (let i = 0; i < urls.length; i++) { + arrValues.push({ + label: urls[i], + value: urls[i] + }); + } + + const selectOptions = function (element) { + return arrValues; + } + + const get = function () { + return element.businessObject.get('quantme:connectorUrl'); + }; + + const setValue = function (value) { + const moddle = getModeler().get('moddle'); + const entry = moddle.create("camunda:Entry", { key: "Accept", value: "application/json" }, { key: "Content-Type", value: "application/json" }); + + const map = moddle.create("camunda:Map", { entries: [entry] }); + + entry.$parent = map; + + const headersInputParameter = moddle.create("camunda:InputParameter", { + definition: map, name: 'headers' + }); + const methodInputParameter = moddle.create("camunda:InputParameter", { + name: 'method', value: 'POST' + }); + const urlInputParameter = moddle.create("camunda:InputParameter", { + name: 'url', value: '' + }); + + let endpointParameters = determineInputParameters(element.businessObject.yaml, value); + let scriptValue = constructScript(endpointParameters); + const script = moddle.create("camunda:Script", { scriptFormat: 'Groovy', value: scriptValue, resource: 'Inline' }); + + const payloadInputParameter = moddle.create("camunda:InputParameter", { + definition: script, name: 'payload' + }); + let inputParameters = []; + inputParameters.push(headersInputParameter); + inputParameters.push(methodInputParameter); + inputParameters.push(urlInputParameter); + inputParameters.push(payloadInputParameter) + + let outputParameters = []; + + outputParameters = determineOutputParameters(element.businessObject.yaml); + let camundaOutputParameters = constructCamundaOutputParameters(outputParameters); + + let inputOutput = moddle.create('camunda:InputOutput', { inputParameters: inputParameters, outputParameters: camundaOutputParameters }) + element.businessObject.extensionElements = moddle.create('bpmn:ExtensionElements', { + values: [ + moddle.create('camunda:Connector', { + connectorId: 'http-connector', + inputOutput: inputOutput + }), + ], + }); + return modeling.updateProperties(element, { connectorUrl: value || '' }); + }; + + + return <> + {()} + ; +} + +function determineInputParameters(yamlData, schemePath) { + // Parse the YAML data + const data = yaml.load(yamlData); + + // Initialize an object to store the input parameters + const inputParameters = {}; + let scheme = ""; + + // Extract the request bodies and their parameters + for (const [path, methods] of Object.entries(data.paths)) { + if(path === schemePath) { + for (const [method, details] of Object.entries(methods)) { + if (details.requestBody) { + const requestBody = details.requestBody; + const content = requestBody.content; + for (const [contentType, contentDetails] of Object.entries(content)) { + if (contentDetails.schema) { + scheme = contentDetails.schema; + const properties = scheme.properties || {}; + inputParameters[path] = properties; + } + } + } + } + }} + + if (scheme.$ref) { + const document = yaml.load(yamlData); + scheme = String(scheme.$ref).replace('#/', '').replaceAll('/', '.'); + + // Access the dynamically determined schema + const schemaPath = scheme; + scheme = getObjectByPath(document, schemaPath); + } + // Function to access an object property by path + function getObjectByPath(obj, path) { + const parts = path.split('.'); + let currentObj = obj; + for (const part of parts) { + if (!currentObj || !currentObj.hasOwnProperty(part)) { + return undefined; + } + currentObj = currentObj[part]; + } + return currentObj; + } + + // Access the properties of the schema + const properties = Object.keys(scheme.properties); + return properties; +} + +function determineOutputParameters(yamlData) { + // Parse the YAML data + const data = yaml.load(yamlData); + + // Initialize an object to store the input parameters + let outputParameters = []; + + // Extract the request bodies and their parameters + for (const [path, methods] of Object.entries(data.paths)) { + for (const [method, details] of Object.entries(methods)) { + if (details.responses) { + const response = details.responses; + // Access the properties of the schema + // Access the schema referenced by "200" + const statusCode = "200"; + let schema = response[statusCode].content["application/json"].schema; + if(schema.$ref) { + const schemaPath = schema.$ref.replace("#/", "").replaceAll("/", "."); + schema = getObjectByPath2(data, schemaPath); + } + // Function to access an object property by path + function getObjectByPath2(obj, path) { + const parts = path.split('.'); + let currentObj = obj; + for (const part of parts) { + if (!currentObj || !currentObj.hasOwnProperty(part)) { + return undefined; + } + currentObj = currentObj[part]; + } + return currentObj; + } + // Access the properties of the schema + outputParameters = Object.keys(schema.properties); + } + } + } + return outputParameters; +} + +function constructCamundaOutputParameters(parameters) { + let outputParameters = []; + for (let param of parameters) { + let moddle = getModeler().get('moddle'); + const script = moddle.create("camunda:Script", { + scriptFormat: 'Groovy', value: 'def resp = connector.getVariable("response");\n' + + 'resp = new groovy.json.JsonSlurper().parseText(resp);\n' + 'def ' + param + ' = resp.get(' + param + ');\n' + 'println(' + param + ');\n' + 'return ' + param + ';', resource: 'Inline' + }); + + const outputParameter = moddle.create("camunda:OutputParameter", { + definition: script, name: param + }); + outputParameters.push(outputParameter); + + } + return outputParameters; +} + + + +function constructScript(parameters) { + let script = 'import groovy.json.JsonBuilder;\n'; + let jsonString = 'def request = [:];\n'; + for (let param of parameters) { + script += 'def ' + param + ' = execution.getVariable("' + param + '");\n' +'println(' + param + ');\n'; + jsonString += 'request.put("' + param + '",' + param + ');\n'; + } + //jsonString = removeLastComma(jsonString); + jsonString += 'requeststring = new JsonBuilder(request).toPrettyString();\nreturn requeststring;'; + script += jsonString; + //script += 'myJson = JSON.stringify(myJson)\nmyJson = myJson'; + return script; +} + +function removeLastComma(str) { + var lastIndex = str.lastIndexOf(","); + if (lastIndex === -1) { + return str; // If comma is not found, return the original string + } else { + return str.slice(0, lastIndex) + str.slice(lastIndex + 1); + } +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/Deployment.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/Deployment.js new file mode 100644 index 00000000..195a3321 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/Deployment.js @@ -0,0 +1,87 @@ +import {SelectEntry} from "@bpmn-io/properties-panel"; +import React from "@bpmn-io/properties-panel/preact/compat"; +import {useService} from "bpmn-js-properties-panel"; +import {getImplementationType} from "../../../quantme/utilities/ImplementationTypeHelperExtension"; + +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +const jquery = require('jquery'); + +const QUANTME_NAMESPACE_PULL = 'http://quantil.org/quantme/pull'; +const QUANTME_NAMESPACE_PUSH = 'http://quantil.org/quantme/push'; + +/** + * Entry to display the custom Implementation option deployment for BPMN service task. Through this option you can define + * a CSAR as implementation of a service task. + */ +export function Deployment({element, translate, wineryEndpoint}) { + + const modeling = useService('modeling'); + const debounce = useService('debounceInput'); + + const selectOptions = function (element) { + const arrValues = []; + jquery.ajax({ + url: wineryEndpoint + '/servicetemplates/?grouped', + method: 'GET', + success: function (result) { + for (let i = 0; i < result.length; i++) { + if (result[i].text === QUANTME_NAMESPACE_PULL || result[i].text === QUANTME_NAMESPACE_PUSH) { + result[i].children.forEach(element => arrValues.push({ + label: element.text, + value: concatenateCsarEndpoint('{{ wineryEndpoint }}', result[i].id, element.text) + })); + } + } + }, + async: false + }); + if (arrValues.length === 0) { + arrValues.push({label: 'No CSARs available', value: ''}); + } + return arrValues; + }; + + const get = function () { + return element.businessObject.get('opentosca:deploymentModelUrl'); + }; + + const setValue = function (value) { + return modeling.updateProperties(element, {deploymentModelUrl: value || ''}); + }; + + const validate = function (values) { + return values === undefined || values===''? translate('Must provide a CSAR') : ''; + }; + + const hidden = function () { + const implType = getImplementationType(element); + console.log('getImplementationType returns ' + implType); + return !(implType === 'deploymentModel'); + }; + + return <> + {!hidden() && ()} + ; +} + +function concatenateCsarEndpoint(wineryEndpoint, namespace, csarName) { + return wineryEndpoint + '/servicetemplates/' + encodeURIComponent(namespace) + '/' + csarName + '/?csar'; +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/DmnImplementationProps.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/DmnImplementationProps.js new file mode 100644 index 00000000..03b4d0c2 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/DmnImplementationProps.js @@ -0,0 +1,361 @@ +import { + getBusinessObject +} from 'bpmn-js/lib/util/ModelUtil'; + +import { + TextFieldEntry, + isTextFieldEntryEdited, + SelectEntry, + isSelectEntryEdited +} from '@bpmn-io/properties-panel'; +import {useService} from "bpmn-js-properties-panel"; +import {getImplementationType} from "../../../quantme/utilities/ImplementationTypeHelperExtension"; + +export function DmnImplementationProps(props) { + const { + element + } = props; + + const entries = []; + + const implementationType = getImplementationType(element); + const bindingType = getDecisionRefBinding(element); + + if (implementationType !== 'dmn') { + return entries; + } + + // (1) decisionRef + entries.push({ + id: 'decisionRef', + component: DecisionRef, + isEdited: isTextFieldEntryEdited + }); + + + // (2) binding + entries.push({ + id: 'decisionRefBinding', + component: Binding, + isEdited: isSelectEntryEdited + }); + + // (3) version + if (bindingType === 'version') { + entries.push({ + id: 'decisionRefVersion', + component: Version, + isEdited: isTextFieldEntryEdited + }); + } + + // (4) versionTag + if (bindingType === 'versionTag') { + entries.push({ + id: 'decisionRefVersionTag', + component: VersionTag, + isEdited: isTextFieldEntryEdited + }); + } + + // (5) tenantId + entries.push({ + id: 'decisionRefTenantId', + component: TenantId, + isEdited: isTextFieldEntryEdited + }); + + // (6) resultVariable + entries.push({ + id: 'decisionRefResultVariable', + component: ResultVariable, + isEdited: isTextFieldEntryEdited + }); + + // (7) mapDecisionResult + if (getResultVariable(element)) { + entries.push({ + id: 'mapDecisionResult', + component: MapDecisionResult, + isEdited: isSelectEntryEdited + }); + } + + return entries; +} + +function DecisionRef(props) { + const {element} = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + const debounce = useService('debounceInput'); + + const businessObject = getBusinessObject(element); + + const getValue = () => { + return businessObject.get('camunda:decisionRef'); + }; + + const setValue = (value) => { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: { + 'camunda:decisionRef': value || '' + } + }); + }; + + return TextFieldEntry({ + element, + id: 'decisionRef', + label: translate('Decision reference'), + getValue, + setValue, + debounce + }); +} + +function Binding(props) { + const {element} = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + + const getValue = () => { + return getDecisionRefBinding(element); + }; + + const setValue = (value) => { + const businessObject = getBusinessObject(element); + + // reset version properties on binding type change + const updatedProperties = { + 'camunda:decisionRefVersion': undefined, + 'camunda:decisionRefVersionTag': undefined, + 'camunda:decisionRefBinding': value + }; + + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: updatedProperties + }); + }; + + // Note: default is "latest", + // cf. https://docs.camunda.org/manual/latest/reference/bpmn20/custom-extensions/extension-attributes/#decisionrefbinding + const getOptions = () => { + + const options = [ + {value: 'deployment', label: translate('deployment')}, + {value: 'latest', label: translate('latest')}, + {value: 'version', label: translate('version')}, + {value: 'versionTag', label: translate('versionTag')} + ]; + + return options; + }; + + return SelectEntry({ + element, + id: 'decisionRefBinding', + label: translate('Binding'), + getValue, + setValue, + getOptions + }); +} + +function Version(props) { + const {element} = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + const debounce = useService('debounceInput'); + + const businessObject = getBusinessObject(element); + + const getValue = () => { + return businessObject.get('camunda:decisionRefVersion'); + }; + + const setValue = (value) => { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: { + 'camunda:decisionRefVersion': value + } + }); + }; + + return TextFieldEntry({ + element, + id: 'decisionRefVersion', + label: translate('Version'), + getValue, + setValue, + debounce + }); +} + +function VersionTag(props) { + const {element} = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + const debounce = useService('debounceInput'); + + const businessObject = getBusinessObject(element); + + const getValue = () => { + return businessObject.get('camunda:decisionRefVersionTag'); + }; + + const setValue = (value) => { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: { + 'camunda:decisionRefVersionTag': value + } + }); + }; + + return TextFieldEntry({ + element, + id: 'decisionRefVersionTag', + label: translate('Version tag'), + getValue, + setValue, + debounce + }); +} + +function TenantId(props) { + const {element} = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + const debounce = useService('debounceInput'); + + const businessObject = getBusinessObject(element); + + const getValue = () => { + return businessObject.get('camunda:decisionRefTenantId'); + }; + + const setValue = (value) => { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: { + 'camunda:decisionRefTenantId': value + } + }); + }; + + return TextFieldEntry({ + element, + id: 'decisionRefTenantId', + label: translate('Tenant ID'), + getValue, + setValue, + debounce + }); +} + +function ResultVariable(props) { + const {element} = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + const debounce = useService('debounceInput'); + + const businessObject = getBusinessObject(element); + + const getValue = () => { + return getResultVariable(businessObject); + }; + + // Note: camunda:mapDecisionResult got cleaned up in modeling behavior + // cf. https://github.com/camunda/camunda-bpmn-js/blob/main/lib/camunda-platform/features/modeling/behavior/UpdateResultVariableBehavior.js + const setValue = (value) => { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: { + 'camunda:resultVariable': value + } + }); + }; + + return TextFieldEntry({ + element, + id: 'decisionRefResultVariable', + label: translate('Result variable'), + getValue, + setValue, + debounce + }); +} + +function MapDecisionResult(props) { + const {element} = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + + const businessObject = getBusinessObject(element); + + const getValue = () => { + return businessObject.get('camunda:mapDecisionResult') || 'resultList'; + }; + + const setValue = (value) => { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: { + 'camunda:mapDecisionResult': value + } + }); + }; + + // Note: default is "resultList", + // cf. https://docs.camunda.org/manual/latest/reference/bpmn20/custom-extensions/extension-attributes/#mapdecisionresult + const getOptions = () => { + const options = [ + {value: 'collectEntries', label: translate('collectEntries (List)')}, + {value: 'resultList', label: translate('resultList (List>)')}, + {value: 'singleEntry', label: translate('singleEntry (TypedValue)')}, + {value: 'singleResult', label: translate('singleResult (Map)')} + ]; + + return options; + }; + + return SelectEntry({ + element, + id: 'mapDecisionResult', + label: translate('Map decision result'), + getValue, + setValue, + getOptions + }); +} + + +// helper //////////////////// + +function getDecisionRefBinding(element) { + const businessObject = getBusinessObject(element); + return businessObject.get('camunda:decisionRefBinding') || 'latest'; +} + +function getResultVariable(element) { + const businessObject = getBusinessObject(element); + return businessObject.get('camunda:resultVariable'); +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ImplementationProps.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ImplementationProps.js new file mode 100644 index 00000000..b958dba8 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ImplementationProps.js @@ -0,0 +1,359 @@ +import { TextFieldEntry, isTextFieldEntryEdited } from '@bpmn-io/properties-panel'; +import { DmnImplementationProps } from './DmnImplementationProps'; +import { ImplementationTypeProps } from './ImplementationTypeProps'; +import { useService } from "bpmn-js-properties-panel"; +import { getImplementationType } from "../../../quantme/utilities/ImplementationTypeHelperExtension" +import { + getServiceTaskLikeBusinessObject, +} from "../../../../editor/util/camunda-utils/ImplementationTypeUtils"; +import { getExtensionElementsList } from "../../../../editor/util/camunda-utils/ExtensionElementsUtil"; +import { Deployment } from "./Deployment"; +import { Connector } from './Connector'; +import { YamlUpload } from './YamlUpload'; +import { ArtifactUpload } from './ArtifactUpload'; +const yaml = require('js-yaml'); +const QUANTME_NAMESPACE_PULL = 'http://quantil.org/quantme/pull'; + +/** + * Properties group for service tasks. Extends the original implementation by adding a new selection option to the + * implementation entry: deployment. + * + * @param props + * @return {{component: function(*): preact.VNode, isEdited: function(*): *, id: string}[]|*[]} + * @constructor + */ +export function ImplementationProps(props) { + const { + element, + wineryEndpoint, + translate, + } = props; + + if (!getServiceTaskLikeBusinessObject(element)) { + return []; + } + + const implementationType = getImplementationType(element); + + // (1) display implementation type select + const entries = [ + ...ImplementationTypeProps({ element }) + ]; + + // (2) display implementation properties based on type + if (implementationType === 'class') { + entries.push({ + id: 'javaClass', + component: JavaClass, + isEdited: isTextFieldEntryEdited + }); + } else if (implementationType === 'expression') { + entries.push( + { + id: 'expression', + component: Expression, + isEdited: isTextFieldEntryEdited + }, + { + id: 'expressionResultVariable', + component: ResultVariable, + isEdited: isTextFieldEntryEdited + } + ); + } else if (implementationType === 'delegateExpression') { + entries.push( + { + id: 'delegateExpression', + component: DelegateExpression, + isEdited: isTextFieldEntryEdited + } + ); + } else if (implementationType === 'dmn') { + entries.push(...DmnImplementationProps({ element })); + } else if (implementationType === 'external') { + entries.push( + { + id: 'externalTopic', + component: Topic, + isEdited: isTextFieldEntryEdited + } + ); + } else if (implementationType === 'connector') { + entries.push( + { + id: 'connectorId', + component: ConnectorId, + isEdited: isTextFieldEntryEdited + } + ); + + // custom extension + } else if (implementationType === 'deploymentModel') { + entries.push({ + id: 'deployment', + element, + translate, + wineryEndpoint, + component: Deployment, + isEdited: isTextFieldEntryEdited + }); + entries.push({ + id: 'yamlUpload', + component: YamlUpload, + isEdited: isTextFieldEntryEdited + }) + if (!element.businessObject.deploymentModelUrl.includes(encodeURIComponent(encodeURIComponent(QUANTME_NAMESPACE_PULL))) && element.businessObject.yaml !== undefined) { + const urls = extractUrlsFromYaml(element.businessObject.yaml); + entries.push({ + id: 'connector', + element, + translate, + urls, + component: Connector, + isEdited: isTextFieldEntryEdited + }) + } + entries.push({ + id: 'artifactUpload', + element, + translate, + component: ArtifactUpload, + isEdited: isTextFieldEntryEdited + }); + } + + + return entries; +} + +function extractUrlsFromYaml(content) { + const doc = yaml.load(content); + + // Extract URLs from paths + const paths = Object.keys(doc.paths); + const urls = paths.map((path) => { + const method = Object.keys(doc.paths[path])[0]; + const url = `${path}`; + return url; + }); + + return urls; +} + +export function JavaClass(props) { + const { + element, + businessObject = getServiceTaskLikeBusinessObject(element), + id = 'javaClass' + } = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + const debounce = useService('debounceInput'); + + const getValue = () => { + return businessObject.get('camunda:class'); + }; + + const setValue = (value) => { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: { + 'camunda:class': value || '' + } + }); + }; + + return TextFieldEntry({ + element, + id, + label: translate('Java class'), + getValue, + setValue, + debounce + }); +} + +export function Expression(props) { + const { + element, + businessObject = getServiceTaskLikeBusinessObject(element), + id = 'expression' + } = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + const debounce = useService('debounceInput'); + + const getValue = () => { + return businessObject.get('camunda:expression'); + }; + + const setValue = (value) => { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: { + 'camunda:expression': value || '' + } + }); + }; + + return TextFieldEntry({ + element, + id, + label: translate('Expression'), + getValue, + setValue, + debounce + }); +} + +function ResultVariable(props) { + const { element } = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + const debounce = useService('debounceInput'); + + const businessObject = getServiceTaskLikeBusinessObject(element); + + const getValue = () => { + return businessObject.get('camunda:resultVariable'); + }; + + const setValue = (value) => { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: { + 'camunda:resultVariable': value + } + }); + }; + + return TextFieldEntry({ + element, + id: 'expressionResultVariable', + label: translate('Result variable'), + getValue, + setValue, + debounce + }); +} + +export function DelegateExpression(props) { + const { + element, + businessObject = getServiceTaskLikeBusinessObject(element), + id = 'delegateExpression' + } = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + const debounce = useService('debounceInput'); + + const getValue = () => { + return businessObject.get('camunda:delegateExpression'); + }; + + const setValue = (value) => { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: { + 'camunda:delegateExpression': value || '' + } + }); + }; + + return TextFieldEntry({ + element, + id, + label: translate('Delegate expression'), + getValue, + setValue, + debounce + }); +} + +function Topic(props) { + const { element } = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + const debounce = useService('debounceInput'); + + const businessObject = getServiceTaskLikeBusinessObject(element); + + const getValue = () => { + return businessObject.get('camunda:topic'); + }; + + const setValue = (value) => { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: businessObject, + properties: { + 'camunda:topic': value + } + }); + }; + + return TextFieldEntry({ + element, + id: 'externalTopic', + label: translate('Topic'), + getValue, + setValue, + debounce + }); +} + +function ConnectorId(props) { + const { element } = props; + + const commandStack = useService('commandStack'); + const translate = useService('translate'); + const debounce = useService('debounceInput'); + + const connector = getConnector(element); + + const getValue = () => { + return connector.get('camunda:connectorId'); + }; + + const setValue = (value) => { + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: connector, + properties: { + 'camunda:connectorId': value + } + }); + }; + + return TextFieldEntry({ + element, + id: 'connectorId', + label: translate('Connector ID'), + getValue, + setValue, + debounce + }); +} + + +// helper ////////////////// + +function getConnectors(businessObject) { + return getExtensionElementsList(businessObject, 'camunda:Connector'); +} + +function getConnector(element) { + const businessObject = getServiceTaskLikeBusinessObject(element); + const connectors = getConnectors(businessObject); + + return connectors[0]; +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ImplementationTypeProps.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ImplementationTypeProps.js new file mode 100644 index 00000000..b6c3eed9 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ImplementationTypeProps.js @@ -0,0 +1,273 @@ +import { + sortBy, without +} from 'min-dash'; + +import {SelectEntry, isSelectEntryEdited} from '@bpmn-io/properties-panel'; +import {useService} from "bpmn-js-properties-panel"; +import {createElement} from "../../../../editor/util/camunda-utils/ElementUtil"; +import { + getServiceTaskLikeBusinessObject, isDeploymentCapable, + isDmnCapable, + isExternalCapable, isServiceTaskLike +} from "../../../../editor/util/camunda-utils/ImplementationTypeUtils"; +import {getExtensionElementsList} from "../../../../editor/util/camunda-utils/ExtensionElementsUtil"; +import {getImplementationType} from "../../../quantme/utilities/ImplementationTypeHelperExtension"; + + +const DELEGATE_PROPS = { + 'camunda:class': undefined, + 'camunda:expression': undefined, + 'camunda:delegateExpression': undefined, + 'camunda:resultVariable': undefined +}; + +const DMN_CAPABLE_PROPS = { + 'camunda:decisionRef': undefined, + 'camunda:decisionRefBinding': 'latest', + 'camunda:decisionRefVersion': undefined, + 'camunda:mapDecisionResult': 'resultList', + 'camunda:decisionRefTenantId': undefined +}; + +const EXTERNAL_CAPABLE_PROPS = { + 'camunda:type': undefined, + 'camunda:topic': undefined +}; + +const IMPLEMENTATION_TYPE_NONE_LABEL = '', + IMPLEMENTATION_TYPE_JAVA_LABEL = 'Java class', + IMPLEMENTATION_TYPE_EXPRESSION_LABEL = 'Expression', + IMPLEMENTATION_TYPE_DELEGATE_LABEL = 'Delegate expression', + IMPLEMENTATION_TYPE_DMN_LABEL = 'DMN', + IMPLEMENTATION_TYPE_EXTERNAL_LABEL = 'External', + IMPLEMENTATION_TYPE_CONNECTOR_LABEL = 'Connector', + IMPLEMENTATION_TYPE_DEPLOYMENT_LABEL = 'Deployment Model'; + + +export function ImplementationTypeProps(props) { + return [ + { + id: 'implementationType', + component: ImplementationType, + isEdited: isSelectEntryEdited + }, + ]; +} + + +function ImplementationType(props) { + const {element} = props; + + const bpmnFactory = useService('bpmnFactory'); + const commandStack = useService('commandStack'); + const translate = useService('translate'); + + const getValue = () => { + return getImplementationType(element) || ''; + }; + + const setValue = (value) => { + + const oldType = getImplementationType(element); + const businessObject = getServiceTaskLikeBusinessObject(element); + const commands = []; + + let updatedProperties = DELEGATE_PROPS; + let extensionElements = businessObject.get('extensionElements'); + + // (1) class, expression, delegateExpression + if (isDelegateType(value)) { + + updatedProperties = { + ...updatedProperties, + [value]: isDelegateType(oldType) ? businessObject.get(`camunda:${oldType}`) : '' + }; + + } + + // (2) dmn + if (isDmnCapable(businessObject)) { + updatedProperties = { + ...updatedProperties, + ...DMN_CAPABLE_PROPS + }; + + if (value === 'dmn') { + updatedProperties = { + ...updatedProperties, + 'camunda:decisionRef': '' + }; + } + } + + // (3) external + // Note: error event definition elements got cleaned up in modeling behavior + // cf. https://github.com/camunda/camunda-bpmn-js/blob/main/lib/camunda-platform/features/modeling/behavior/DeleteErrorEventDefinitionBehavior.js + if (isExternalCapable(businessObject)) { + updatedProperties = { + ...updatedProperties, + ...EXTERNAL_CAPABLE_PROPS + }; + + if (value === 'external') { + updatedProperties = { + ...updatedProperties, + 'camunda:type': 'external', + 'camunda:topic': '' + }; + } + } + + // (4) connector + if (isServiceTaskLike(businessObject)) { + + // (4.1) remove all connectors on type change + const connectors = getConnectors(businessObject); + + if (connectors.length) { + commands.push({ + cmd: 'element.updateModdleProperties', + context: { + element, + moddleElement: extensionElements, + properties: { + values: without(extensionElements.get('values'), value => connectors.includes(value)) + } + } + }); + } + + // (4.2) create connector + if (value === 'connector') { + + // ensure extension elements + if (!extensionElements) { + extensionElements = createElement( + 'bpmn:ExtensionElements', + {values: []}, + businessObject, + bpmnFactory + ); + + commands.push(UpdateModdlePropertiesCommand(element, businessObject, {extensionElements})); + } + + const connector = createElement( + 'camunda:Connector', + {}, + extensionElements, + bpmnFactory + ); + + commands.push({ + cmd: 'element.updateModdleProperties', + context: { + element, + moddleElement: extensionElements, + properties: { + values: [...extensionElements.get('values'), connector] + } + } + }); + } + + } + + // (5) deployment + if (isDeploymentCapable(businessObject)) { + updatedProperties = { + ...updatedProperties, + 'opentosca:deploymentModelUrl': undefined + }; + + if (value === 'deploymentModel') { + updatedProperties = { + ...updatedProperties, + 'opentosca:deploymentModelUrl': '' + }; + } + } + + + // (5) collect all property updates + commands.push(UpdateModdlePropertiesCommand(element, businessObject, updatedProperties)); + + // (6) commit all updates + commandStack.execute('properties-panel.multi-command-executor', commands); + }; + + const getOptions = () => { + const businessObject = getServiceTaskLikeBusinessObject(element); + + const options = [ + {value: '', label: translate(IMPLEMENTATION_TYPE_NONE_LABEL)}, + {value: 'class', label: translate(IMPLEMENTATION_TYPE_JAVA_LABEL)}, + {value: 'expression', label: translate(IMPLEMENTATION_TYPE_EXPRESSION_LABEL)}, + {value: 'delegateExpression', label: translate(IMPLEMENTATION_TYPE_DELEGATE_LABEL)} + ]; + + if (isDmnCapable(businessObject)) { + options.push({value: 'dmn', label: translate(IMPLEMENTATION_TYPE_DMN_LABEL)}); + } + + if (isExternalCapable(businessObject)) { + options.push({value: 'external', label: translate(IMPLEMENTATION_TYPE_EXTERNAL_LABEL)}); + } + + if (isServiceTaskLike(businessObject)) { + options.push({value: 'connector', label: translate(IMPLEMENTATION_TYPE_CONNECTOR_LABEL)}); + } + + // add deployment + if (isDeploymentCapable(businessObject)) { + options.push({value: 'deploymentModel', label: translate(IMPLEMENTATION_TYPE_DEPLOYMENT_LABEL)}); + } + + return sortByPriority(options); + }; + + return SelectEntry({ + element, + id: 'implementationType', + label: translate('Type'), + getValue, + setValue, + getOptions + }); +} + + +// helper /////////////////////// + +function isDelegateType(type) { + return ['class', 'expression', 'delegateExpression'].includes(type); +} + +function getConnectors(businessObject) { + return getExtensionElementsList(businessObject, 'camunda:Connector'); +} + +function UpdateModdlePropertiesCommand(element, businessObject, newProperties) { + return { + cmd: 'element.updateModdleProperties', + context: { + element, + moddleElement: businessObject, + properties: newProperties + } + }; +} + +function sortByPriority(options) { + const priorities = { + [IMPLEMENTATION_TYPE_NONE_LABEL]: 0, + [IMPLEMENTATION_TYPE_JAVA_LABEL]: 3, + [IMPLEMENTATION_TYPE_EXPRESSION_LABEL]: 4, + [IMPLEMENTATION_TYPE_DELEGATE_LABEL]: 5, + [IMPLEMENTATION_TYPE_DMN_LABEL]: 1, + [IMPLEMENTATION_TYPE_EXTERNAL_LABEL]: 2, + [IMPLEMENTATION_TYPE_CONNECTOR_LABEL]: 6 + }; + + return sortBy(options, o => priorities[o.label]); +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ServiceTaskPropertiesProvider.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ServiceTaskPropertiesProvider.js new file mode 100644 index 00000000..0fa95dff --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/ServiceTaskPropertiesProvider.js @@ -0,0 +1,81 @@ +import {ImplementationProps} from "./ImplementationProps"; +import {Group} from "@bpmn-io/properties-panel"; +import {getWineryEndpoint} from '../../framework-config/config-manager'; + +const LOW_PRIORITY = 500; + +/** + * A provider with a `#getGroups(element)` method that exposes groups for a diagram element. + * + * @param propertiesPanel + * @param injector + * @param {Function} translate + * @param eventBus + */ +export default function ServiceTaskPropertiesProvider(propertiesPanel, injector, translate, eventBus) { + // subscribe to config updates to retrieve the currently defined Winery endpoint + const self = this; + let wineryEndpoint; + eventBus.on('config.updated', function (config) { + wineryEndpoint = config.wineryEndpoint; + }); + + /** + * Return the groups provided for the given element. + * + * @param element + * + * @return {(Object[]) => (Object[])} groups middleware + */ + this.getGroups = function (element) { + + /** + * We return a middleware that modifies + * the existing groups. + * + * @param {Object[]} groups + * + * @return {Object[]} modified groups + */ + return function (groups) { + // update ServiceTasks with the deployment extension + if (element.type && element.type === 'bpmn:ServiceTask') { + groups[2] = ImplementationGroup(element, injector, getWineryEndpoint()); + } + return groups; + }; + }; + + propertiesPanel.registerProvider(LOW_PRIORITY, this); +} + +ServiceTaskPropertiesProvider.$inject = ['propertiesPanel', 'injector', 'translate', 'eventBus']; + + +/** + * Properties group to show customized implementation options entry for service tasks. + * + * @param element The element to show the properties for. + * @param injector The injector of the bpmn-js modeler + * @param wineryEndpoint The winery endpoint of the QuantME plugin + * @return {null|{component: ((function(*): preact.VNode)|*), entries: *[], label, id: string}} + * @constructor + */ +function ImplementationGroup(element, injector, wineryEndpoint) { + const translate = injector.get('translate'); + + const group = { + label: translate('Implementation'), + id: 'CamundaPlatform__Implementation', + component: Group, + entries: [ + ...ImplementationProps({element, wineryEndpoint, translate}) + ] + }; + + if (group.entries.length) { + return group; + } + + return null; +} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/YamlModal.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/YamlModal.js similarity index 59% rename from components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/YamlModal.js rename to components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/YamlModal.js index 8b28a177..d320c2bb 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/YamlModal.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/YamlModal.js @@ -10,10 +10,10 @@ */ /* eslint-disable no-unused-vars */ -import React, { useState } from "react"; -import Modal from "../../../../../editor/ui/modal/Modal"; -import "./yaml-modal.css"; -import "../../../../../editor/config/config-modal.css"; +import React, { useState } from 'react'; +import Modal from '../../../../editor/ui/modal/Modal'; +import './yaml-modal.css'; +import '../../../../editor/config/config-modal.css'; // polyfill upcoming structural components const Title = Modal.Title; @@ -34,21 +34,20 @@ export default function YamlModal(props) { const onSubmit = async () => { // Process the uploaded file or text input here - console.log("Uploaded file:", uploadFile); + console.log('Uploaded file:', uploadFile); var reader = new FileReader(); reader.onload = function () { var fileContent = reader.result; element.businessObject.yaml = fileContent; - commandStack.execute("element.updateModdleProperties", { + commandStack.execute('element.updateModdleProperties', { element, moddleElement: element.businessObject, properties: { - yaml: fileContent, - }, + 'yaml': fileContent + } }); }; reader.readAsText(uploadFile); - // Call close callback onClose(); }; @@ -60,43 +59,31 @@ export default function YamlModal(props) { - - - - + + + +
File - { - setUploadFile(e.target.files[0]); - }} - /> -
File + { setUploadFile(e.target.files[0]); }} + /> +
+
- -
); -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/YamlUpload.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/YamlUpload.js new file mode 100644 index 00000000..8d56b51a --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/YamlUpload.js @@ -0,0 +1,30 @@ +import { HeaderButton } from '@bpmn-io/properties-panel'; +import { useService } from 'bpmn-js-properties-panel'; +import React from 'react'; +import YamlModal from './YamlModal'; +import { createRoot } from 'react-dom/client'; +import './yaml-modal.css'; + +/** + * Entry to display the button which opens the Yaml Model, a dialog which allows to upload yml files. + */ +export function YamlUpload(props) { + const { element } = props; + const translate = useService('translate'); + const commandStack = useService('commandStack'); + + const onClick = () => { + const root = createRoot(document.getElementById("modal-container")); + root.render( root.unmount()} element={element} commandStack={commandStack}/>); + }; + + return HeaderButton({ + element, + id: 'upload-yaml-button', + text: translate('Upload YAML'), + description: 'Upload YML', + className: "upload-yaml-button", + children: 'Upload YAML', + onClick, + }); +} diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/artifact-modal.css b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/artifact-modal.css new file mode 100644 index 00000000..2ec3d4c8 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/artifact-modal.css @@ -0,0 +1,109 @@ +.tab { + flex: 1; + padding: 10px; + border-radius: 3px; + text-align: center; + cursor: pointer; + font-weight: bold; + font-stretch: normal; + font-style: normal; + line-height: normal; + letter-spacing: normal; + color: #fdfdfe; + margin: 1px; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.2); + border: solid 1px var(--blue-darken-62); + background-color: var(--blue-base-65); +} + +.tab.active { + background-color: var(--blue-darken-55); +} + +.tab:hover { + border: solid 1px var(--blue-darken-55); + background-color: var(--blue-darken-62); +} + +.tab-buttons-container { + display: flex; + flex-direction: row; + align-items: center; + overflow: auto; + min-width: 120px; + max-height: 263px; + direction: ltr; +} + + +.qwm-artifact-upload-btn { + outline: none; + background-color: var(--color-grey-225-10-97); + border: 1px solid var(--color-grey-225-10-75); + border-radius: 4px; + margin: 2px 32px 6px 12px; + padding: 2px 6px 2px 6px; + fill: var(--add-entry-fill-color); +} + +.qwm-artifact-upload-btn:hover { + color: white; + border: 1px solid white; + background-color: var(--color-blue-205-100-50); + fill: var(--add-entry-hover-fill-color); +} + +.upload-tab-content { + display: flex; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center; + padding: 3px 6px 2px; + column-gap: 10px; +} + +.dockerimage-input { + flex-grow: 2; +} + +.upload-properties-panel-input { + padding: 3px 6px 2px; + border: 1px solid hsl(225, 10%, 75%); + border-radius: 2px; + background-color: rgb(247, 247, 248); + font-size: 14px; + font-family: inherit; +} + +.upload-properties-panel-label { + display: block; + font-size: var(--text-size-small); + margin: 2px 0 1px; +} + +.upload-artifact-tab { + display: flex; + flex-direction: row; + align-items: center; + padding: 3px 6px 2px; + font-size: 14px; + font-family: inherit; + width: 100% +} + +.upload-artifact-selector { + flex: 1; + max-width: 282px; +} + +.upload-file-upload { + flex: 1; + max-width: 282px; + overflow: clip; + text-overflow: ellipsis; +} + +.upload-file-upload-button { + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/yaml-modal.css b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/yaml-modal.css new file mode 100644 index 00000000..f90424cb --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/yaml-modal.css @@ -0,0 +1,16 @@ +.qwm .upload-yaml-button { + outline: none; + background-color: var(--color-grey-225-10-97); + border: 1px solid var(--color-grey-225-10-75); + border-radius: 4px; + margin: 2px 32px 6px 12px; + padding: 2px 6px 2px 6px; + fill: var(--add-entry-fill-color); +} + +.qwm .upload-yaml-button:hover { + color: white; + border: 1px solid white; + background-color: var(--color-blue-205-100-50); + fill: var(--add-entry-hover-fill-color); +} diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/replacement/OnDemandTransformator.js b/components/bpmn-q/modeler-component/extensions/opentosca/replacement/OnDemandTransformator.js new file mode 100644 index 00000000..9d3b0489 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/replacement/OnDemandTransformator.js @@ -0,0 +1,230 @@ +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import {createTempModelerFromXml} from '../../../editor/ModelerHandler'; +import {getXml} from '../../../editor/util/IoUtilities'; +import {isDeployableServiceTask} from "../deployment/DeploymentUtils"; +import * as config from "../framework-config/config-manager"; +import {makeId} from "../deployment/OpenTOSCAUtils"; +import {getCamundaEndpoint} from "../../../editor/config/EditorConfigManager"; +import {createElement} from "../../../editor/util/camunda-utils/ElementUtil"; +import {useService} from "bpmn-js-properties-panel"; + + +const fetchMethod = ` +function fetch(method, url, body) { + try { + var resourceURL = new java.net.URL(url); + + var urlConnection = resourceURL.openConnection(); + urlConnection.setRequestMethod(method); + urlConnection.setRequestProperty("Accept", "application/json"); + if (body) { + urlConnection.setDoOutput(true); + urlConnection.setRequestProperty("Content-Type", "application/json"); + var outputStream = urlConnection.getOutputStream() + var outputStreamWriter = new java.io.OutputStreamWriter(outputStream) + outputStreamWriter.write(body); + outputStreamWriter.flush(); + outputStreamWriter.close(); + outputStream.close(); + } + + var inputStream = new java.io.InputStreamReader(urlConnection + .getInputStream()); + var bufferedReader = new java.io.BufferedReader(inputStream); + var inputLine = "" + var text = ""; + var i = 5; + while ((inputLine = bufferedReader.readLine()) != null) { + text += inputLine + } + bufferedReader.close(); + java.lang.System.out.println("Response from " + url + ": " + text); + return text; + } catch (e) { + java.lang.System.err.println(e); + throw e; + } +}`; + +function createDeploymentScript(params) { + return ` +var params = ${JSON.stringify(params)}; +params.csarName = "ondemand_" + (Math.random().toString().substring(3)); + +${fetchMethod} + + +var createCsarResponse = fetch('POST', params.opentoscaEndpoint, JSON.stringify({ + enrich: 'false', + name: params.csarName, + url: params.deploymentModelUrl +})) + + +var serviceTemplates = JSON.parse(fetch('GET', params.opentoscaEndpoint + "/" + params.csarName + ".csar/servicetemplates")) +var buildPlansUrl = serviceTemplates.service_templates[0]._links.self.href + '/buildplans' +var buildPlans = JSON.parse(fetch('GET', buildPlansUrl)) +var buildPlanUrl = buildPlans.plans[0]._links.self.href +var inputParameters = JSON.parse(fetch('GET', buildPlanUrl)).input_parameters +for(var i = 0; i < inputParameters.length; i++) { + if(inputParameters[i].name === "camundaEndpoint") { + inputParameters[i].value = params.opentoscaEndpoint + } else if(inputParameters[i].name === "camundaTopic") { + inputParameters[i].value = params.camundaTopic + } else { + inputParameters[i].value = "null" + } +} +var createInstanceResponse = fetch('POST', buildPlanUrl + "/instances", JSON.stringify(inputParameters)) +execution.setVariable(params.subprocessId + "_deploymentBuildPlanInstanceUrl", buildPlanUrl + "/instances/" + createInstanceResponse);`; +} + +function createWaitScript(params) { + return ` +var params = ${JSON.stringify(params)}; + +${fetchMethod} +var buildPlanInstanceUrl = execution.getVariable(params.subprocessId + "_deploymentBuildPlanInstanceUrl"); +var instanceUrl; +for(var i = 0; i < 30; i++) { + try { + instanceUrl = JSON.parse(fetch('GET', buildPlanInstanceUrl))._links.service_template_instance.href; + if (instanceUrl) break; + } catch (e) { + } + java.lang.Thread.sleep(2000); +} + +java.lang.System.out.println("InstanceUrl: " + instanceUrl); + +for(var i = 0; i < 30 * 8; i++) { + try { + var createInstanceResponse = fetch('GET', instanceUrl); + var instance = JSON.parse(createInstanceResponse).service_template_instances; + if (instance && instance.state === "CREATED") { + break; + } + } catch (e) { + } + java.lang.Thread.sleep(2000); +} + +var properties = JSON.parse(fetch('GET', instanceUrl + "/properties")); + +execution.setVariable("selfserviceApplicationUrl", properties.selfserviceApplicationUrl); +java.lang.Thread.sleep(12000); +`; +} + +/** + * Initiate the replacement process for the QuantME tasks that are contained in the current process model + * + * @param xml the BPMN diagram in XML format + * @param currentQRMs the set of currently in the framework available QRMs + * @param endpointConfig endpoints of the services required for the dynamic hardware selection + */ +export async function startOnDemandReplacementProcess(xml) { + const modeler = await createTempModelerFromXml(xml); + const modeling = modeler.get('modeling'); + const elementRegistry = modeler.get('elementRegistry'); + const bpmnReplace = modeler.get('bpmnReplace'); + const bpmnAutoResizeProvider = modeler.get('bpmnAutoResizeProvider'); + const bpmnFactory = modeler.get('bpmnFactory'); + bpmnAutoResizeProvider.canResize = () => false; + + const serviceTasks = elementRegistry.filter(({businessObject}) => isDeployableServiceTask(businessObject)); + + for (const serviceTask of serviceTasks) { + let deploymentModelUrl = serviceTask.businessObject.get('opentosca:deploymentModelUrl'); + if (deploymentModelUrl.startsWith('{{ wineryEndpoint }}')) { + deploymentModelUrl = deploymentModelUrl.replace('{{ wineryEndpoint }}', config.getWineryEndpoint()); + } + + const extensionElements = serviceTask.businessObject.extensionElements; + + let subProcess = bpmnReplace.replaceElement(serviceTask, {type: 'bpmn:SubProcess'}); + + subProcess.businessObject.set("opentosca:onDemandDeployment", true); + subProcess.businessObject.set("opentosca:deploymentModelUrl", deploymentModelUrl); + + const startEvent = modeling.createShape({ + type: 'bpmn:StartEvent' + }, {x: 200, y: 200}, subProcess); + + let topicName = makeId(12); + const serviceTask1 = modeling.appendShape(startEvent, { + type: 'bpmn:ScriptTask' + }, {x: 400, y: 200}); + serviceTask1.businessObject.set("scriptFormat", "javascript"); + serviceTask1.businessObject.set("script", createDeploymentScript( + { + opentoscaEndpoint: config.getOpenTOSCAEndpoint(), + deploymentModelUrl: deploymentModelUrl, + subprocessId: subProcess.id, + camundaTopic: topicName, + camundaEndpoint: getCamundaEndpoint() + } + )); + serviceTask1.businessObject.set("name", "Create deployment"); + + const serviceTask2 = modeling.appendShape(serviceTask1, { + type: 'bpmn:ScriptTask', + }, {x: 600, y: 200}); + serviceTask2.businessObject.set("scriptFormat", "javascript"); + serviceTask2.businessObject.set("script", createWaitScript( + {subprocessId: subProcess.id} + )); + serviceTask2.businessObject.set("name", "Wait for deployment"); + + const serviceTask3 = modeling.appendShape(serviceTask2, { + type: 'bpmn:ServiceTask', + }, {x: 800, y: 200}); + + serviceTask3.businessObject.set("name", "Call service"); + if (!extensionElements) { + serviceTask3.businessObject.set("camunda:type", "external"); + serviceTask3.businessObject.set("camunda:topic", topicName); + + } else { + const values = extensionElements.values; + for (let value of values) { + if (value.inputOutput === undefined) continue; + for (let param of value.inputOutput.inputParameters) { + if (param.name === "url") { + param.value = `\${selfserviceApplicationUrl.concat(${JSON.stringify(param.value || "")})}`; + break; + } + } + } + + const newExtensionElements = createElement( + 'bpmn:ExtensionElements', + {values}, + serviceTask2.businessObject, + bpmnFactory + ); + subProcess.businessObject.set("extensionElements", undefined); + serviceTask3.businessObject.set("extensionElements", newExtensionElements); + } + const endTask = modeling.appendShape(serviceTask3, { + type: 'bpmn:EndEvent' + }, {x: 1000, y: 200}, subProcess); + + } + + // layout diagram after successful transformation + let updatedXml = await getXml(modeler); + console.log(updatedXml); + + return updatedXml; +} diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/config-icon.png b/components/bpmn-q/modeler-component/extensions/opentosca/resources/config-icon.png new file mode 100644 index 00000000..c1395539 Binary files /dev/null and b/components/bpmn-q/modeler-component/extensions/opentosca/resources/config-icon.png differ diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/hide-icon.png b/components/bpmn-q/modeler-component/extensions/opentosca/resources/hide-icon.png new file mode 100644 index 00000000..53abf9ae Binary files /dev/null and b/components/bpmn-q/modeler-component/extensions/opentosca/resources/hide-icon.png differ diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/info.png b/components/bpmn-q/modeler-component/extensions/opentosca/resources/info.png new file mode 100644 index 00000000..3da2d8ff Binary files /dev/null and b/components/bpmn-q/modeler-component/extensions/opentosca/resources/info.png differ diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/opentosca-icon.png b/components/bpmn-q/modeler-component/extensions/opentosca/resources/opentosca-icon.png new file mode 100644 index 00000000..dac4de79 Binary files /dev/null and b/components/bpmn-q/modeler-component/extensions/opentosca/resources/opentosca-icon.png differ diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/opentosca4bpmn.json b/components/bpmn-q/modeler-component/extensions/opentosca/resources/opentosca4bpmn.json new file mode 100644 index 00000000..ee1b10a4 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/resources/opentosca4bpmn.json @@ -0,0 +1,28 @@ +{ + "name": "OpenTOSCA4BPMN", + "uri": "https://github.com/UST-QuAntiL/OpenTOSCA", + "prefix": "opentosca", + "xml": { + "tagAlias": "lowerCase" + }, + "types": [ + { + "name": "ServiceTask", + "extends": [ "bpmn:ServiceTask" ], + "properties": [ + { + "name": "deploymentModelUrl", + "isAttr": true, + "type": "String" + }, + { + "name": "connectorUrl", + "isAttr": true, + "type": "String" + } + ] + } + ], + "enumerations": [], + "associations": [] +} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/resources/service-deployment-icon.png b/components/bpmn-q/modeler-component/extensions/opentosca/resources/service-deployment-icon.png similarity index 100% rename from components/bpmn-q/modeler-component/extensions/quantme/resources/service-deployment-icon.png rename to components/bpmn-q/modeler-component/extensions/opentosca/resources/service-deployment-icon.png diff --git a/components/bpmn-q/modeler-component/extensions/quantme/resources/service-deployment-icon.svg b/components/bpmn-q/modeler-component/extensions/opentosca/resources/service-deployment-icon.svg similarity index 100% rename from components/bpmn-q/modeler-component/extensions/quantme/resources/service-deployment-icon.svg rename to components/bpmn-q/modeler-component/extensions/opentosca/resources/service-deployment-icon.svg diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/show-deployment-button.svg b/components/bpmn-q/modeler-component/extensions/opentosca/resources/show-deployment-button.svg new file mode 100644 index 00000000..8734f9a8 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/resources/show-deployment-button.svg @@ -0,0 +1 @@ +D \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/show-icon.png b/components/bpmn-q/modeler-component/extensions/opentosca/resources/show-icon.png new file mode 100644 index 00000000..fd1f8e24 Binary files /dev/null and b/components/bpmn-q/modeler-component/extensions/opentosca/resources/show-icon.png differ diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/styling/opentosca.css b/components/bpmn-q/modeler-component/extensions/opentosca/styling/opentosca.css new file mode 100644 index 00000000..be326f0e --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/styling/opentosca.css @@ -0,0 +1,127 @@ +@import url('~bpmn-font/dist/css/bpmn-embedded.css'); + +.qwm .config:before { + content: ""; + display: block; + width: 15px; + height: 15px; + background-size: contain; + background: url("../resources/config-icon.png") no-repeat center center; + float: left; +} + +.qwm .app-icon-opentosca:before { + content: ""; + width: 15px; + height: 15px; + background-image: url("../resources/opentosca-icon.png"); + background-size: contain; + background-repeat: no-repeat; + display: inline-block; + float: left; +} + +.qwm .app-icon-service-deployment:before { + content: ""; + width: 15px; + height: 15px; + background-image: url("../resources/service-deployment-icon.png"); + background-size: contain; + background-repeat: no-repeat; + display: inline-block; + float: left; +} + +.qwm .indent { + margin-left: 5px; +} + +.qwm .spaceUnder { + padding-bottom: 1em; +} + +.qwm .spaceUnderSmall { + padding-bottom: 0.3em; +} + +.qwm .spaceAbove { + padding-top: 1em; +} + +.qwm .hidden { + display: none; +} + +.qwm .djs-label { + font-family: 'Arial', sans-serif; +} + +.qwm .adaptation-tab { + overflow: hidden; + border: 1px solid #ccc; + background-color: #f1f1f1; +} + +/* Style the buttons that are used to open the tab content */ +.qwm .adaptation-tab-button { + background-color: inherit; + float: left; + border: none; + outline: none; + cursor: pointer; + transition: 0.3s; + padding: 14px 16px; +} + +/* Change background color of buttons on hover */ +.qwm .adaptation-tab-button:hover { + background-color: #ddd; +} + +/* Create an active/current tablink class */ +.qwm .adaptation-tab-button.active { + background-color: #ccc; +} + +.qwm .rewrite-failed-button:after { + content: ""; + display: block; + width: 15px; + height: 15px; + background-size: contain; + background: url("../resources/info.png") no-repeat center center; + float: right; + padding-left: 20px; +} + +.qwm .rewrite-failed-button:disabled{ + background-color: #f44336; + color: #000000; +} + +.qwm .rewrite-successful-button:disabled{ + background-color: #008000; + color: #000000; +} + +.qwm .show-icon:before { + content: ""; + display: block; + width: 16px; + height: 16px; + background: url("../resources/show-icon.png") no-repeat center center; + background-size: contain; + float: left; +} + + +.qwm .hide-icon:before { + content: ""; + display: block; + width: 16px; + height: 16px; + background: url("../resources/hide-icon.png") no-repeat center center; + background-size: contain; + float: left; +} + diff --git a/components/bpmn-q/modeler-component/extensions/quantme/ui/deployment/services/DeploymentPlugin.js b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/DeploymentPlugin.js similarity index 56% rename from components/bpmn-q/modeler-component/extensions/quantme/ui/deployment/services/DeploymentPlugin.js rename to components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/DeploymentPlugin.js index e8bf6403..46ccce5d 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/ui/deployment/services/DeploymentPlugin.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/DeploymentPlugin.js @@ -10,44 +10,41 @@ */ /* eslint-disable no-unused-vars*/ -import React, { Fragment, PureComponent } from "react"; - -import ServiceDeploymentOverviewModal from "./ServiceDeploymentOverviewModal"; -import ServiceDeploymentInputModal from "./ServiceDeploymentInputModal"; -import ServiceDeploymentBindingModal from "./ServiceDeploymentBindingModal"; - -import { - createServiceInstance, - uploadCSARToContainer, -} from "../../../deployment/OpenTOSCAUtils"; -import { bindUsingPull, bindUsingPush } from "../../../deployment/BindingUtils"; -import { getServiceTasksToDeploy } from "../../../deployment/DeploymentUtils"; -import { getModeler } from "../../../../../editor/ModelerHandler"; +import React, {Fragment, PureComponent} from 'react'; + +import ServiceDeploymentOverviewModal from './ServiceDeploymentOverviewModal'; +import ServiceDeploymentInputModal from './ServiceDeploymentInputModal'; +import ServiceDeploymentBindingModal from './ServiceDeploymentBindingModal'; + +import {createServiceInstance, uploadCSARToContainer} from '../../../deployment/OpenTOSCAUtils'; +import {bindUsingPull, bindUsingPush} from '../../../deployment/BindingUtils'; +import {getServiceTasksToDeploy} from '../../../deployment/DeploymentUtils'; +import {getModeler} from "../../../../../editor/ModelerHandler"; import NotificationHandler from "../../../../../editor/ui/notifications/NotificationHandler"; -import { getRootProcess } from "../../../../../editor/util/ModellingUtilities"; +import {getRootProcess} from '../../../../../editor/util/ModellingUtilities'; +import ExtensibleButton from "../../../../../editor/ui/ExtensibleButton"; const defaultState = { windowOpenDeploymentOverview: false, windowOpenDeploymentInput: false, - windowOpenDeploymentBinding: false, + windowOpenDeploymentBinding: false }; export default class DeploymentPlugin extends PureComponent { + constructor(props) { super(props); this.state = defaultState; - this.handleDeploymentOverviewClosed = - this.handleDeploymentOverviewClosed.bind(this); - this.handleDeploymentInputClosed = - this.handleDeploymentInputClosed.bind(this); - this.handleDeploymentBindingClosed = - this.handleDeploymentBindingClosed.bind(this); + this.handleDeploymentOverviewClosed = this.handleDeploymentOverviewClosed.bind(this); + this.handleDeploymentInputClosed = this.handleDeploymentInputClosed.bind(this); + this.handleDeploymentBindingClosed = this.handleDeploymentBindingClosed.bind(this); } componentDidMount() { this.modeler = getModeler(); + this.commandStack = this.modeler.get("commandStack"); } /** @@ -58,14 +55,14 @@ export default class DeploymentPlugin extends PureComponent { */ handleProgress(progressBar, progress) { if (!progressBar.innerHTML) { - progressBar.innerHTML = "0%"; + progressBar.innerHTML = '0%'; } - let currentWidth = parseInt(progressBar.innerHTML.replace(/% ?/g, "")); + let currentWidth = parseInt(progressBar.innerHTML.replace(/% ?/g, '')); for (let i = 0; i < progress; i++) { currentWidth++; - progressBar.style.width = currentWidth + "%"; - progressBar.innerHTML = currentWidth + "%"; + progressBar.style.width = currentWidth + '%'; + progressBar.innerHTML = currentWidth + '%'; } } @@ -75,8 +72,10 @@ export default class DeploymentPlugin extends PureComponent { * @param result the result from the close operation */ async handleDeploymentOverviewClosed(result) { + // handle click on 'Next' button - if (result && result.hasOwnProperty("next") && result.next === true) { + if (result && result.hasOwnProperty('next') && result.next === true) { + // make progress bar visible and hide buttons result.refs.progressBarDivRef.current.hidden = false; result.refs.footerRef.current.hidden = true; @@ -90,24 +89,17 @@ export default class DeploymentPlugin extends PureComponent { // upload all CSARs for (let i = 0; i < csarList.length; i++) { let csar = csarList[i]; - console.log("Uploading CSAR to OpenTOSCA container: ", csar); - - let uploadResult = await uploadCSARToContainer( - this.modeler.config.opentoscaEndpoint, - csar.csarName, - csar.url, - this.modeler.config.wineryEndpoint - ); + console.log('Uploading CSAR to OpenTOSCA container: ', csar); + + let uploadResult = await uploadCSARToContainer(this.modeler.config.opentoscaEndpoint, csar.csarName, csar.url, this.modeler.config.wineryEndpoint); if (uploadResult.success === false) { + // notify user about failed CSAR upload NotificationHandler.getInstance().displayNotification({ - type: "error", - title: "Unable to upload CSAR to the OpenTOSCA Container", - content: - "CSAR defined for ServiceTasks with Id '" + - csar.serviceTaskIds + - "' could not be uploaded to the connected OpenTOSCA Container!", - duration: 20000, + type: 'error', + title: 'Unable to upload CSAR to the OpenTOSCA Container', + content: 'CSAR defined for ServiceTasks with Id \'' + csar.serviceTaskIds + '\' could not be uploaded to the connected OpenTOSCA Container!', + duration: 20000 }); // abort process @@ -133,7 +125,7 @@ export default class DeploymentPlugin extends PureComponent { windowOpenDeploymentOverview: false, windowOpenDeploymentInput: true, windowOpenDeploymentBinding: false, - csarList: csarList, + csarList: csarList }); return; } @@ -152,8 +144,10 @@ export default class DeploymentPlugin extends PureComponent { * @param result the result from the close operation */ async handleDeploymentInputClosed(result) { + // handle click on 'Next' button - if (result && result.hasOwnProperty("next") && result.next === true) { + if (result && result.hasOwnProperty('next') && result.next === true) { + // make progress bar visible and hide buttons result.refs.progressBarDivRef.current.hidden = false; result.refs.footerRef.current.hidden = true; @@ -167,23 +161,18 @@ export default class DeploymentPlugin extends PureComponent { // create service instances for all CSARs for (let i = 0; i < csarList.length; i++) { let csar = csarList[i]; - console.log("Creating service instance for CSAR: ", csar); + console.log('Creating service instance for CSAR: ', csar); - let instanceCreationResponse = await createServiceInstance( - csar, - this.modeler.config.camundaEndpoint - ); + let instanceCreationResponse = await createServiceInstance(csar, this.modeler.config.camundaEndpoint); csar.properties = instanceCreationResponse.properties; if (instanceCreationResponse.success === false) { + // notify user about failed instance creation NotificationHandler.getInstance().displayNotification({ - type: "error", - title: "Unable to create service instace", - content: - "Unable to create service instance for CSAR '" + - csar.csarName + - "'. Aborting process!", - duration: 20000, + type: 'error', + title: 'Unable to create service instace', + content: 'Unable to create service instance for CSAR \'' + csar.csarName + '\'. Aborting process!', + duration: 20000 }); // abort process @@ -229,8 +218,10 @@ export default class DeploymentPlugin extends PureComponent { * @param result the result from the close operation */ handleDeploymentBindingClosed(result) { + // handle click on 'Next' button - if (result && result.hasOwnProperty("next") && result.next === true) { + if (result && result.hasOwnProperty('next') && result.next === true) { + // iterate through each CSAR and related ServiceTask and perform the binding with the created service instance let csarList = result.csarList; for (let i = 0; i < csarList.length; i++) { @@ -238,39 +229,24 @@ export default class DeploymentPlugin extends PureComponent { let serviceTaskIds = csar.serviceTaskIds; for (let j = 0; j < serviceTaskIds.length; j++) { + // bind the service instance using the specified binding pattern let bindingResponse = undefined; - if (csar.type === "pull") { - bindingResponse = bindUsingPull( - csar.topicName, - serviceTaskIds[j], - this.modeler.get("elementRegistry"), - this.modeler.get("modeling") - ); - } else if (csar.type === "push") { - bindingResponse = bindUsingPush( - csar, - serviceTaskIds[j], - this.modeler.get("elementRegistry") - ); + if (csar.type === 'pull') { + bindingResponse = bindUsingPull(csar, serviceTaskIds[j], this.modeler.get('elementRegistry'), this.modeler.get('modeling')); + } else if (csar.type === 'push') { + bindingResponse = bindUsingPush(csar, serviceTaskIds[j], this.modeler.get('elementRegistry'), this.modeler.get('modeling')); } // abort if binding pattern is invalid or binding fails - if ( - bindingResponse === undefined || - bindingResponse.success === false - ) { + if (bindingResponse === undefined || bindingResponse.success === false) { + // notify user about failed binding NotificationHandler.getInstance().displayNotification({ - type: "error", - title: "Unable to perform binding", - content: - "Unable to bind ServiceTask with Id '" + - serviceTaskIds[j] + - "' using binding pattern '" + - csar.type + - "'. Aborting process!", - duration: 20000, + type: 'error', + title: 'Unable to perform binding', + content: 'Unable to bind ServiceTask with Id \'' + serviceTaskIds[j] + '\' using binding pattern \'' + csar.type + '\'. Aborting process!', + duration: 20000 }); // abort process @@ -286,11 +262,10 @@ export default class DeploymentPlugin extends PureComponent { // notify user about successful binding NotificationHandler.getInstance().displayNotification({ - type: "info", - title: "Binding completed", - content: - "Binding of the deployed service instances completed. The resulting workflow can now be deployed to the Camunda engine!", - duration: 20000, + type: 'info', + title: 'Binding completed', + content: 'Binding of the deployed service instances completed. The resulting workflow can now be deployed to the Camunda engine!', + duration: 20000 }); } @@ -305,66 +280,74 @@ export default class DeploymentPlugin extends PureComponent { * Get the list of ServiceTasks to deploy a service for to display them in the modal */ getServiceTasksToDeployForModal() { + if (!this.modeler) { - console.warn("Modeler not available, unable to retrieve ServiceTasks!"); + console.warn('Modeler not available, unable to retrieve ServiceTasks!'); return []; } // get all ServiceTasks with associated deployment model - let csarsToDeploy = getServiceTasksToDeploy( - getRootProcess(this.modeler.getDefinitions()) - ); + let csarsToDeploy = getServiceTasksToDeploy(getRootProcess(this.modeler.getDefinitions())); if (csarsToDeploy.length === 0) { NotificationHandler.getInstance().displayNotification({ - type: "info", - title: "No ServiceTasks with associated deployment models", - content: - "The workflow does not contain ServiceTasks with associated deployment models. No service deployment required!", - duration: 20000, + type: 'info', + title: 'No ServiceTasks with associated deployment models', + content: 'The workflow does not contain ServiceTasks with associated deployment models. No service deployment required!', + duration: 20000 }); } return csarsToDeploy; } + showDeployment(show) { + this.commandStack.execute("deploymentModel.showAll", { + showDeploymentModel: show + }); + } + render() { // render deployment button and pop-up menu - return ( - -
- -
- {this.state.windowOpenDeploymentOverview && ( - - )} - {this.state.windowOpenDeploymentInput && ( - - )} - {this.state.windowOpenDeploymentBinding && ( - - )} -
- ); + return ( + this.showDeployment(true)}> + Show Deployment + , + , + ]}/> + {this.state.windowOpenDeploymentOverview && ( + + )} + {this.state.windowOpenDeploymentInput && ( + + )} + {this.state.windowOpenDeploymentBinding && ( + + )} + ); } -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/ui/deployment/services/ServiceDeploymentBindingModal.js b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentBindingModal.js similarity index 100% rename from components/bpmn-q/modeler-component/extensions/quantme/ui/deployment/services/ServiceDeploymentBindingModal.js rename to components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentBindingModal.js diff --git a/components/bpmn-q/modeler-component/extensions/quantme/ui/deployment/services/ServiceDeploymentInputModal.js b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentInputModal.js similarity index 100% rename from components/bpmn-q/modeler-component/extensions/quantme/ui/deployment/services/ServiceDeploymentInputModal.js rename to components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentInputModal.js diff --git a/components/bpmn-q/modeler-component/extensions/quantme/ui/deployment/services/ServiceDeploymentOverviewModal.js b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentOverviewModal.js similarity index 100% rename from components/bpmn-q/modeler-component/extensions/quantme/ui/deployment/services/ServiceDeploymentOverviewModal.js rename to components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentOverviewModal.js diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/utilities/Utilities.js b/components/bpmn-q/modeler-component/extensions/opentosca/utilities/Utilities.js new file mode 100644 index 00000000..f1aaefea --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/utilities/Utilities.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import $ from 'jquery'; + + +export function performAjax(targetUrl, dataToSend) { + return new Promise(function (resolve, reject) { + $.ajax({ + type: 'POST', + url: targetUrl, + data: dataToSend, + processData: false, + contentType: false, + beforeSend: function () { + }, + success: function (data) { + resolve(data); + }, + error: function (err) { + reject(err); + } + }); + }); +} \ 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 index 0145572f..325081d1 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/configTabs/QuantMETab.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/configTabs/QuantMETab.js @@ -1,6 +1,6 @@ -import React, { useState } from "react"; -import { getModeler } from "../../../editor/ModelerHandler"; -import * as config from "../framework-config/config-manager"; +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 @@ -9,153 +9,89 @@ import * as config from "../framework-config/config-manager"; * @return {JSX.Element} The tab as a React component * @constructor */ -export default function BPMNConfigTab() { - const [dataConfigurationsEndpoint, setDataConfigurationsEndpoint] = useState( - config.getQuantMEDataConfigurationsEndpoint() - ); +export default function QuantMETab() { + 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 === "false" - ? false - : Boolean(hybridRuntimeProvenance); + 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"); + const editorActions = modeler.get('editorActions'); + const eventBus = modeler.get('eventBus'); // register editor action listener for changes in config entries - if ( - !editorActions._actions.hasOwnProperty( - "qiskitRuntimeHandlerEndpointChanged" - ) - ) { + if (!editorActions._actions.hasOwnProperty('qiskitRuntimeHandlerEndpointChanged')) { editorActions.register({ - qiskitRuntimeHandlerEndpointChanged: function ( - qiskitRuntimeHandlerEndpoint - ) { - self.modeler.config.qiskitRuntimeHandlerEndpoint = - qiskitRuntimeHandlerEndpoint; - eventBus.fire("config.updated", self.modeler.config); - }, + qiskitRuntimeHandlerEndpointChanged: function (qiskitRuntimeHandlerEndpoint) { + self.modeler.config.qiskitRuntimeHandlerEndpoint = qiskitRuntimeHandlerEndpoint; + eventBus.fire('config.updated', self.modeler.config); + } }); } - if ( - !editorActions._actions.hasOwnProperty("awsRuntimeHandlerEndpointChanged") - ) { + if (!editorActions._actions.hasOwnProperty('awsRuntimeHandlerEndpointChanged')) { editorActions.register({ awsRuntimeHandlerEndpointChanged: function (awsRuntimeHandlerEndpoint) { - self.modeler.config.awsRuntimeHandlerEndpoint = - awsRuntimeHandlerEndpoint; - eventBus.fire("config.updated", self.modeler.config); - }, + self.modeler.config.awsRuntimeHandlerEndpoint = awsRuntimeHandlerEndpoint; + eventBus.fire('config.updated', self.modeler.config); + } }); } - if ( - !editorActions._actions.hasOwnProperty("hybridRuntimeProvenanceChanged") - ) { + if (!editorActions._actions.hasOwnProperty('hybridRuntimeProvenanceChanged')) { editorActions.register({ hybridRuntimeProvenanceChanged: function (hybridRuntimeProvenance) { self.modeler.config.hybridRuntimeProvenance = hybridRuntimeProvenance; - eventBus.fire("config.updated", self.modeler.config); - }, + 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")) { + if (!editorActions._actions.hasOwnProperty('nisqAnalyzerEndpointChanged')) { editorActions.register({ nisqAnalyzerEndpointChanged: function (nisqAnalyzerEndpoint) { self.modeler.config.nisqAnalyzerEndpoint = nisqAnalyzerEndpoint; - }, + } }); } - if ( - !editorActions._actions.hasOwnProperty( - "transformationFrameworkEndpointChanged" - ) - ) { + if (!editorActions._actions.hasOwnProperty('transformationFrameworkEndpointChanged')) { editorActions.register({ - transformationFrameworkEndpointChanged: function ( - transformationFrameworkEndpoint - ) { - modeler.config.transformationFrameworkEndpoint = - transformationFrameworkEndpoint; - }, + transformationFrameworkEndpointChanged: function (transformationFrameworkEndpoint) { + modeler.config.transformationFrameworkEndpoint = transformationFrameworkEndpoint; + } }); } - if (!editorActions._actions.hasOwnProperty("scriptSplitterEndpointChanged")) { + if (!editorActions._actions.hasOwnProperty('scriptSplitterEndpointChanged')) { editorActions.register({ scriptSplitterEndpointChanged: function (scriptSplitterEndpoint) { modeler.config.scriptSplitterEndpoint = scriptSplitterEndpoint; - eventBus.fire("config.updated", self.modeler.config); - }, + eventBus.fire('config.updated', self.modeler.config); + } }); } - if ( - !editorActions._actions.hasOwnProperty("scriptSplitterThresholdChanged") - ) { + if (!editorActions._actions.hasOwnProperty('scriptSplitterThresholdChanged')) { editorActions.register({ scriptSplitterThresholdChanged: function (scriptSplitterEndpoint) { modeler.config.scriptSplitterThreshold = scriptSplitterEndpoint; - }, + } }); } // save changed config entries on close - BPMNConfigTab.prototype.onClose = () => { + QuantMETab.prototype.onClose = () => { modeler.config.dataConfigurationsEndpoint = dataConfigurationsEndpoint; - modeler.config.opentoscaEndpoint = opentoscaEndpoint; - modeler.config.wineryEndpoint = wineryEndpoint; modeler.config.nisqAnalyzerEndpoint = nisqAnalyzerEndpoint; - modeler.config.transformationFrameworkEndpoint = - transformationFrameworkEndpoint; + 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); @@ -165,180 +101,127 @@ export default function BPMNConfigTab() { 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); - }} - /> -
- - ); + return <> +

QuantME data configuration endpoint:

+ + + + + + + +
Data Configurations Endpoint + setDataConfigurationsEndpoint(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 = () => { +QuantMETab.prototype.config = () => { const modeler = getModeler(); - modeler.config.transformationFrameworkEndpoint = - config.getTransformationFrameworkEndpoint(); + 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/deployment/OpenTOSCAUtils.js b/components/bpmn-q/modeler-component/extensions/quantme/deployment/OpenTOSCAUtils.js deleted file mode 100644 index 8697d4b9..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/deployment/OpenTOSCAUtils.js +++ /dev/null @@ -1,437 +0,0 @@ -/** - * Copyright (c) 2023 Institute of Architecture of Application Systems - - * University of Stuttgart - * - * This program and the accompanying materials are made available under the - * terms the Apache Software License 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import { fetch } from "whatwg-fetch"; -import { performAjax } from "../utilities/Utilities"; - -/** - * Upload the CSAR located at the given URL to the connected OpenTOSCA Container and return the corresponding URL and required input parameters - * - * @param opentoscaEndpoint the endpoint of the OpenTOSCA Container - * @param csarName the name of the CSAR to upload - * @param url the URL pointing to the CSAR - * @param wineryEndpoint the endpoint of the Winery containing the CSAR to upload - */ -export async function uploadCSARToContainer( - opentoscaEndpoint, - csarName, - url, - wineryEndpoint -) { - if (opentoscaEndpoint === undefined) { - console.error("OpenTOSCA endpoint is undefined. Unable to upload CSARs..."); - return { success: false }; - } - - try { - if (url.startsWith("{{ wineryEndpoint }}")) { - url = url.replace("{{ wineryEndpoint }}", wineryEndpoint); - } - console.log( - "Checking if CSAR at following URL is already uploaded to the OpenTOSCA Container: ", - url - ); - - // check if CSAR is already uploaded - let getCSARResult = await getBuildPlanForCSAR(opentoscaEndpoint, csarName); - - if (!getCSARResult.success) { - console.log("CSAR is not yet uploaded. Uploading..."); - - let body = { - enrich: "false", - name: csarName, - url: url, - }; - - // upload the CSAR - await fetch(opentoscaEndpoint, { - method: "POST", - body: JSON.stringify(body), - headers: { "Content-Type": "application/json" }, - }); - - // check successful upload and retrieve corresponding url - getCSARResult = await getBuildPlanForCSAR(opentoscaEndpoint, csarName); - } - - if (!getCSARResult.success) { - console.error("Uploading CSAR failed!"); - return { success: false }; - } - - // retrieve input parameters for the build plan - let buildPlanResult = await fetch(getCSARResult.url); - let buildPlanResultJson = await buildPlanResult.json(); - - return { - success: true, - url: getCSARResult.url, - inputParameters: buildPlanResultJson.input_parameters, - }; - } catch (e) { - console.error("Error while uploading CSAR: " + e); - return { success: false }; - } -} - -/** - * Get the link to the build plan of the CSAR with the given name if it is uploaded to the OpenTOSCA Container - * - * @param opentoscaEndpoint the endpoint of the OpenTOSCA Container - * @param csarName the name of the csar - * @return the status whether the given CSAR is uploaded and the corresponding build plan link if available - */ -async function getBuildPlanForCSAR(opentoscaEndpoint, csarName) { - // get all currently deployed CSARs - let response = await fetch(opentoscaEndpoint); - let responseJson = await response.json(); - - let deployedCSARs = responseJson.csars; - if (deployedCSARs === undefined) { - // no CSARs available - return { success: false }; - } - - for (let i = 0; i < deployedCSARs.length; i++) { - let deployedCSAR = deployedCSARs[i]; - if (deployedCSAR.id === csarName) { - console.log("Found uploaded CSAR with id: %s", csarName); - let url = deployedCSAR._links.self.href; - - // retrieve the URl to the build plan required to get the input parameters and to instantiate the CSAR - return getBuildPlanUrl(url); - } - } - - // unable to find CSAR - return { success: false }; -} - -/** - * Get the URL to the build plan of the given CSAR - * - * @param csarUrl the URL to a CSAR - * @return the URL to the build plan for the given CSAR - */ -async function getBuildPlanUrl(csarUrl) { - let response = await fetch(csarUrl + "/servicetemplates"); - let responseJson = await response.json(); - - if ( - !responseJson.service_templates || - responseJson.service_templates.length !== 1 - ) { - console.error( - "Unable to find service template in CSAR at URL: %s", - csarUrl - ); - return { success: false }; - } - - let buildPlansUrl = - responseJson.service_templates[0]._links.self.href + "/buildplans"; - response = await fetch(buildPlansUrl); - responseJson = await response.json(); - - if (!responseJson.plans || responseJson.plans.length !== 1) { - console.error("Unable to find build plan at URL: %s", buildPlansUrl); - return { success: false }; - } - - return { success: true, url: responseJson.plans[0]._links.self.href }; -} - -/** - * Create an instance of the ServiceTemplate contained in the given CSAR - * - * @param csar the details about the CSAR to create an instance from the contained ServiceTemplate - * @param camundaEngineEndpoint the endpoint of the Camunda engine to bind services using the pulling pattern - * @return the result of the instance creation (success, endpoint, topic on which the service listens, ...) - */ -export async function createServiceInstance(csar, camundaEngineEndpoint) { - let result = { success: false }; - - let inputParameters = csar.inputParameters; - if (csar.type === "pull") { - // get special parameters that are required to bind services using external tasks / the pulling pattern - let camundaTopicParam = inputParameters.find( - (param) => param.name === "camundaTopic" - ); - let camundaEndpointParam = inputParameters.find( - (param) => param.name === "camundaEndpoint" - ); - - // abort if parameters are not available - if (camundaTopicParam === undefined || camundaEndpointParam === undefined) { - console.error( - "Unable to pass topic to poll to service instance creation. Service binding will fail!" - ); - return result; - } - - // generate topic for the binding - let topicName = makeId(12); - - camundaTopicParam.value = topicName; - camundaEndpointParam.value = camundaEngineEndpoint; - result.topicName = topicName; - } - - // trigger instance creation - let instanceCreationResponse = await fetch(csar.buildPlanUrl + "/instances", { - method: "POST", - body: JSON.stringify(inputParameters), - headers: { "Content-Type": "application/json" }, - }); - let instanceCreationResponseJson = await instanceCreationResponse.json(); - - // wait for the service instance to be created - await new Promise((r) => setTimeout(r, 5000)); - - // get service template instance to poll for completness - let buildPlanResponse = await fetch( - csar.buildPlanUrl + "/instances/" + instanceCreationResponseJson - ); - let buildPlanResponseJson = await buildPlanResponse.json(); - - // retry polling 10 times, creation of the build time takes some time - for (let retry = 0; retry < 10; retry++) { - // stop retries in case of correct response - if (buildPlanResponseJson._links) { - break; - } - - await new Promise((r) => setTimeout(r, 5000)); - - console.log("Retry fetching build plan"); - - buildPlanResponse = await fetch( - csar.buildPlanUrl + "/instances/" + instanceCreationResponseJson - ); - buildPlanResponseJson = await buildPlanResponse.json(); - } - - if (!buildPlanResponseJson._links) { - console.log( - "Unable to fetch build plans for " + - csar.buildPlanUrl + - "/instances/" + - instanceCreationResponseJson - ); - result.success = false; - return result; - } - - let pollingUrl = buildPlanResponseJson._links.service_template_instance.href; - - let state = "CREATING"; - console.log("Polling for finished service instance at URL: %s", pollingUrl); - let properties = ""; - while (!(state === "CREATED" || state === "FAILED")) { - // wait 5 seconds for next poll - await new Promise((r) => setTimeout(r, 5000)); - // poll for current state - let pollingResponse = await fetch(pollingUrl); - let pollingResponseJson = await pollingResponse.json(); - console.log("Polling response: ", pollingResponseJson); - properties = pollingResponseJson._links.properties.href; - - state = pollingResponseJson.state; - } - - result.success = true; - result.properties = properties; - return result; -} - -/** - * Create a new ArtifactTemplate of the given type and add the given blob as file - * - * @param wineryEndpoint the endpoint of the Winery to create the ArtifactTemplate at - * @param localNamePrefix the prefix of the local name of the artifact to which a random suffix is added if an ArtifactTemplate with the name already exists - * @param namespace the namespace of the ArtifactTemplate to create - * @param type the type of the ArtifactTemplate to create - * @param blob the blob to upload to the ArtifactTemplate - * @param fileName the name of the file to upload as blob - * @return the final name of the created ArtifactTemplate - */ -export async function createNewArtifactTemplate( - wineryEndpoint, - localNamePrefix, - namespace, - type, - blob, - fileName -) { - console.log("Creating new ArtifactTemplate of type: ", type); - - // retrieve the currently available ArtifactTemplates - let getArtifactsResult = await fetch(wineryEndpoint + "/artifacttemplates/"); - let getArtifactsResultJson = await getArtifactsResult.json(); - - console.log(getArtifactsResultJson); - - // get unique name for the ArtifactTemplate - let artifactTemplateLocalName = localNamePrefix; - if ( - getArtifactsResultJson.some( - (e) => e.id === artifactTemplateLocalName && e.namespace === namespace - ) - ) { - let nameOccupied = true; - artifactTemplateLocalName += "-" + makeId(1); - while (nameOccupied) { - if ( - !getArtifactsResultJson.some( - (e) => e.id === artifactTemplateLocalName && e.namespace === namespace - ) - ) { - nameOccupied = false; - } else { - artifactTemplateLocalName += makeId(1); - } - } - } - console.log( - "Creating ArtifactTemplate with name: ", - artifactTemplateLocalName - ); - - // create ArtifactTemplate - let artifactCreationResponse = await fetch( - wineryEndpoint + "/artifacttemplates/", - { - method: "POST", - body: JSON.stringify({ - localname: artifactTemplateLocalName, - namespace: namespace, - type: type, - }), - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - } - ); - let artifactCreationResponseText = await artifactCreationResponse.text(); - - // get URL for the file upload to the ArtifactTemplate - let fileUrl = - wineryEndpoint + - "/artifacttemplates/" + - artifactCreationResponseText + - "files"; - - // upload the blob - const fd = new FormData(); - fd.append("file", blob, fileName); - await performAjax(fileUrl, fd); - - return artifactTemplateLocalName; -} - -/** - * Create a new version of the ServiceTemplate with the given ID and namespace and return its URL or an error if the ServiceTemplate does not exist - * - * @param wineryEndpoint the endpoint of the Winery to create the new ServiceTemplate version - * @param serviceTemplateId the ID of the ServiceTemplate to create a new version from - * @param serviceTemplateNamespace the namespace of the ServiceTemplate to create a new version from - * @return the URL to the new version, or an error if the base ServiceTemplate is not available at the given Winery endpoint - */ -export async function createNewServiceTemplateVersion( - wineryEndpoint, - serviceTemplateId, - serviceTemplateNamespace -) { - console.log( - "Creating new version of Service Template with ID %s and namespace %s", - serviceTemplateId, - serviceTemplateNamespace - ); - - // retrieve the currently available ServiceTemplates - let getTemplatesResult = await fetch(wineryEndpoint + "/servicetemplates/"); - let getTemplatesResultJson = await getTemplatesResult.json(); - - // abort if base service template is not available - if ( - !getTemplatesResultJson.some( - (e) => - e.id === serviceTemplateId && e.namespace === serviceTemplateNamespace - ) - ) { - console.log( - "Required base ServiceTemplate for deploying Qiskit Runtime programs not available in Winery!" - ); - return { - error: - "Required base ServiceTemplate for deploying Qiskit Runtime programs not available in Winery!", - }; - } - - // get unique name for the new version - let version = makeId(5); - let nameOccupied = true; - while (nameOccupied) { - if ( - !getTemplatesResultJson.some( - (e) => - e.id === serviceTemplateId + "_" + version + "-w1-wip1" && - e.namespace === serviceTemplateNamespace - ) - ) { - nameOccupied = false; - } else { - version += makeId(1); - } - } - console.log("Using component version: ", version); - - // create ServiceTemplate version - let versionUrl = - wineryEndpoint + - "/servicetemplates/" + - encodeURIComponent(encodeURIComponent(serviceTemplateNamespace)) + - "/" + - serviceTemplateId; - console.log("Creating new version under URL:", versionUrl); - let versionCreationResponse = await fetch(versionUrl, { - method: "POST", - body: JSON.stringify({ - version: { - componentVersion: version, - currentVersion: false, - editable: true, - latestVersion: false, - releasable: false, - wineryVersion: 1, - workInProgressVersion: 1, - }, - }), - headers: { "Content-Type": "application/json", Accept: "application/json" }, - }); - - // assemble URL to the created ServiceTemplate version - let versionCreationResponseText = await versionCreationResponse.text(); - return wineryEndpoint + "/servicetemplates/" + versionCreationResponseText; -} - -function makeId(length) { - let result = ""; - let characters = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - let charactersLength = characters.length; - for (let i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - return result; -} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/framework-config/config-manager.js b/components/bpmn-q/modeler-component/extensions/quantme/framework-config/config-manager.js index 9ee39d6c..7968a0e1 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/framework-config/config-manager.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/framework-config/config-manager.js @@ -10,7 +10,7 @@ */ import defaultConfig from "./config"; -import { getPluginConfig } from "../../../editor/plugin/PluginConfigHandler"; +import { getPluginConfig } from '../../../editor/plugin/PluginConfigHandler'; let config = {}; @@ -20,9 +20,8 @@ let config = {}; export function getQuantMEDataConfigurationsEndpoint() { if (config.quantmeDataConfigurationsEndpoint === undefined) { setQuantMEDataConfigurationsEndpoint( - getPluginConfig("quantme").quantmeDataConfigurationsEndpoint || - defaultConfig.quantmeDataConfigurationsEndpoint - ); + getPluginConfig('quantme').quantmeDataConfigurationsEndpoint + || defaultConfig.quantmeDataConfigurationsEndpoint); } return config.quantmeDataConfigurationsEndpoint; } @@ -30,13 +29,8 @@ export function getQuantMEDataConfigurationsEndpoint() { /** * Set the endpoint for Data Object Configurations */ -export function setQuantMEDataConfigurationsEndpoint( - dataConfigurationsEndpoint -) { - if ( - dataConfigurationsEndpoint !== null && - dataConfigurationsEndpoint !== undefined - ) { +export function setQuantMEDataConfigurationsEndpoint(dataConfigurationsEndpoint) { + if (dataConfigurationsEndpoint !== null && dataConfigurationsEndpoint !== undefined) { config.quantmeDataConfigurationsEndpoint = dataConfigurationsEndpoint; } } @@ -47,9 +41,8 @@ export function setQuantMEDataConfigurationsEndpoint( export function getNisqAnalyzerEndpoint() { if (config.nisqAnalyzerEndpoint === undefined) { setNisqAnalyzerEndpoint( - getPluginConfig("quantme").nisqAnalyzerEndpoint || - defaultConfig.nisqAnalyzerEndpoint - ); + getPluginConfig('quantme').nisqAnalyzerEndpoint + || defaultConfig.nisqAnalyzerEndpoint); } return config.nisqAnalyzerEndpoint; } @@ -69,9 +62,8 @@ export function setNisqAnalyzerEndpoint(nisqAnalyzerEndpoint) { export function getTransformationFrameworkEndpoint() { if (config.transformationFrameworkEndpoint === undefined) { setTransformationFrameworkEndpoint( - getPluginConfig("quantme").transformationFrameworkEndpoint || - defaultConfig.transformationFrameworkEndpoint - ); + getPluginConfig('quantme').transformationFrameworkEndpoint + || defaultConfig.transformationFrameworkEndpoint); } return config.transformationFrameworkEndpoint; } @@ -79,68 +71,12 @@ export function getTransformationFrameworkEndpoint() { /** * Set the Transformation Framework endpoint */ -export function setTransformationFrameworkEndpoint( - transformationFrameworkEndpoint -) { - if ( - transformationFrameworkEndpoint !== null && - transformationFrameworkEndpoint !== undefined - ) { +export function setTransformationFrameworkEndpoint(transformationFrameworkEndpoint) { + if (transformationFrameworkEndpoint !== null && transformationFrameworkEndpoint !== undefined) { config.transformationFrameworkEndpoint = transformationFrameworkEndpoint; } } -/** - * Get the endpoint of the configured OpenTOSCA container - * - * @return {string} the currently specified endpoint of the OpenTOSCA container - */ -export function getOpenTOSCAEndpoint() { - if (config.opentoscaEndpoint === undefined) { - setOpenTOSCAEndpoint( - getPluginConfig("quantme").opentoscaEndpoint || - defaultConfig.opentoscaEndpoint - ); - } - return config.opentoscaEndpoint; -} - -/** - * Set the endpoint of the OpenTOSCA container - * - * @param opentoscaEndpoint the endpoint of the OpenTOSCA container - */ -export function setOpenTOSCAEndpoint(opentoscaEndpoint) { - if (opentoscaEndpoint !== null && opentoscaEndpoint !== undefined) { - config.opentoscaEndpoint = opentoscaEndpoint.replace(/\/$/, ""); - } -} - -/** - * Get the endpoint of the configured Winery - * - * @return {string} the currently specified endpoint of the Winery - */ -export function getWineryEndpoint() { - if (config.wineryEndpoint === undefined) { - setWineryEndpoint( - getPluginConfig("quantme").wineryEndpoint || defaultConfig.wineryEndpoint - ); - } - return config.wineryEndpoint; -} - -/** - * Set the endpoint of the Winery - * - * @param wineryEndpoint the endpoint of the Winery - */ -export function setWineryEndpoint(wineryEndpoint) { - if (wineryEndpoint !== null && wineryEndpoint !== undefined) { - config.wineryEndpoint = wineryEndpoint.replace(/\/$/, ""); - } -} - /** * Get the local path to the folder in the repository containing the QRMs * @@ -149,9 +85,8 @@ export function setWineryEndpoint(wineryEndpoint) { export function getQRMRepositoryPath() { if (config.githubRepositoryPath === undefined) { setQRMRepositoryPath( - getPluginConfig("quantme").githubRepositoryPath || - defaultConfig.githubRepositoryPath - ); + getPluginConfig('quantme').githubRepositoryPath + || defaultConfig.githubRepositoryPath); } return config.githubRepositoryPath; } @@ -175,9 +110,8 @@ export function setQRMRepositoryPath(repositoryPath) { export function getQRMRepositoryName() { if (config.githubRepositoryName === undefined) { setQRMRepositoryName( - getPluginConfig("quantme").githubRepositoryName || - defaultConfig.githubRepositoryName - ); + getPluginConfig('quantme').githubRepositoryName + || defaultConfig.githubRepositoryName); } return config.githubRepositoryName; } @@ -201,8 +135,8 @@ export function setQRMRepositoryName(repositoryName) { export function getQRMRepositoryUserName() { if (config.githubUsername === undefined) { setQRMUserName( - getPluginConfig("quantme").githubUsername || defaultConfig.githubUsername - ); + getPluginConfig('quantme').githubUsername + || defaultConfig.githubUsername); } return config.githubUsername; } @@ -226,8 +160,8 @@ export function setQRMUserName(userName) { export function getGitHubToken() { if (config.githubToken === undefined) { setGitHubToken( - getPluginConfig("quantme").githubToken || defaultConfig.githubToken - ); + getPluginConfig('quantme').githubToken + || defaultConfig.githubToken); } return config.githubToken; } @@ -251,9 +185,8 @@ export function setGitHubToken(githubToken) { export function getQiskitRuntimeHandlerEndpoint() { if (config.qiskitRuntimeHandlerEndpoint === undefined) { setQiskitRuntimeHandlerEndpoint( - getPluginConfig("quantme").qiskitRuntimeHandlerEndpoint || - defaultConfig.qiskitRuntimeHandlerEndpoint - ); + getPluginConfig('quantme').qiskitRuntimeHandlerEndpoint + || defaultConfig.qiskitRuntimeHandlerEndpoint); } return config.qiskitRuntimeHandlerEndpoint; } @@ -277,9 +210,8 @@ export function setQiskitRuntimeHandlerEndpoint(endpoint) { export function getScriptSplitterEndpoint() { if (config.scriptSplitterEndpoint === undefined) { setScriptSplitterEndpoint( - getPluginConfig("quantme").scriptSplitterEndpoint || - defaultConfig.scriptSplitterEndpoint - ); + getPluginConfig('quantme').scriptSplitterEndpoint + || defaultConfig.scriptSplitterEndpoint); } return config.scriptSplitterEndpoint; } @@ -303,9 +235,8 @@ export function setScriptSplitterEndpoint(endpoint) { export function getScriptSplitterThreshold() { if (config.scriptSplitterThreshold === undefined) { setScriptSplitterThreshold( - getPluginConfig("quantme").scriptSplitterThreshold || - defaultConfig.scriptSplitterThreshold - ); + getPluginConfig('quantme').scriptSplitterThreshold + || defaultConfig.scriptSplitterThreshold); } return config.scriptSplitterThreshold; } @@ -329,9 +260,8 @@ export function setScriptSplitterThreshold(threshold) { export function getHybridRuntimeProvenance() { if (config.hybridRuntimeProvenance === undefined) { setHybridRuntimeProvenance( - getPluginConfig("quantme").hybridRuntimeProvenance || - defaultConfig.hybridRuntimeProvenance - ); + getPluginConfig('quantme').hybridRuntimeProvenance + || defaultConfig.hybridRuntimeProvenance); } return config.hybridRuntimeProvenance; } @@ -342,10 +272,7 @@ export function getHybridRuntimeProvenance() { * @param hybridRuntimeProvenance the new value of the hybrid runtime provenance flag */ export function setHybridRuntimeProvenance(hybridRuntimeProvenance) { - if ( - hybridRuntimeProvenance !== null && - hybridRuntimeProvenance !== undefined - ) { + if (hybridRuntimeProvenance !== null && hybridRuntimeProvenance !== undefined) { config.hybridRuntimeProvenance = hybridRuntimeProvenance; } } @@ -358,9 +285,8 @@ export function setHybridRuntimeProvenance(hybridRuntimeProvenance) { export function getAWSRuntimeHandlerEndpoint() { if (config.awsRuntimeHandlerEndpoint === undefined) { setAWSRuntimeHandlerEndpoint( - getPluginConfig("quantme").awsRuntimeHandlerEndpoint || - defaultConfig.awsRuntimeHandlerEndpoint - ); + getPluginConfig('quantme').awsRuntimeHandlerEndpoint + || defaultConfig.awsRuntimeHandlerEndpoint); } return config.awsRuntimeHandlerEndpoint; } @@ -390,10 +316,7 @@ export function getUploadGithubRepositoryName() { * Set the upload Github Repositoryname */ export function setUploadGithubRepositoryName(uploadGithubRepositoryName) { - if ( - uploadGithubRepositoryName !== null && - uploadGithubRepositoryName !== undefined - ) { + if (uploadGithubRepositoryName !== null && uploadGithubRepositoryName !== undefined) { config.uploadGithubRepositoryName = uploadGithubRepositoryName; } } @@ -412,10 +335,7 @@ export function getUploadGithubRepositoryOwner() { * Set the Upload Github Repository User */ export function setUploadGithubRepositoryOwner(uploadGithubRepositoryOwner) { - if ( - uploadGithubRepositoryOwner !== null && - uploadGithubRepositoryOwner !== undefined - ) { + if (uploadGithubRepositoryOwner !== null && uploadGithubRepositoryOwner !== undefined) { config.uploadGithubRepositoryOwner = uploadGithubRepositoryOwner; } } @@ -464,4 +384,4 @@ export function setUploadBranchName(uploadBranchName) { */ export function resetConfig() { config = {}; -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/QuantMEPropertiesProvider.js b/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/QuantMEPropertiesProvider.js index eda70d0f..f6d5e35e 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/QuantMEPropertiesProvider.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/QuantMEPropertiesProvider.js @@ -1,4 +1,4 @@ -import { is } from "bpmn-js/lib/util/ModelUtil"; +import { is } from 'bpmn-js/lib/util/ModelUtil'; import * as consts from "../../Constants"; import * as dataConsts from "../../../data-extension/Constants"; import { @@ -14,14 +14,11 @@ import { ParameterOptimizationTaskEntries, VariationalQuantumAlgorithmTaskEntries, WarmStartingTaskEntries, - CuttingResultCombinationTaskEntries, + CuttingResultCombinationTaskEntries } from "./QuantMETaskProperties"; -import { ImplementationProps } from "./service-task/ImplementationProps"; -import { Group } from "@bpmn-io/properties-panel"; -import { getWineryEndpoint } from "../../framework-config/config-manager"; -import * as configConsts from "../../../../editor/configurations/Constants"; -import { instance as dataObjectConfigs } from "../../configurations/DataObjectConfigurations"; -import ConfigurationsProperties from "../../../../editor/configurations/ConfigurationsProperties"; +import * as configConsts from '../../../../editor/configurations/Constants'; +import { instance as dataObjectConfigs } from '../../configurations/DataObjectConfigurations'; +import ConfigurationsProperties from '../../../../editor/configurations/ConfigurationsProperties'; const LOW_PRIORITY = 500; @@ -34,11 +31,14 @@ const LOW_PRIORITY = 500; * @param eventBus * @param bpmnFactory */ -export default function QuantMEPropertiesProvider( - propertiesPanel, - injector, - translate -) { +export default function QuantMEPropertiesProvider(propertiesPanel, injector, translate, eventBus, bpmnFactory) { + // subscribe to config updates to retrieve the currently defined Winery endpoint + const self = this; + let wineryEndpoint; + eventBus.on('config.updated', function (config) { + wineryEndpoint = config.wineryEndpoint; + }); + /** * Return the groups provided for the given element. * @@ -47,6 +47,7 @@ export default function QuantMEPropertiesProvider( * @return {(Object[]) => (Object[])} groups middleware */ this.getGroups = function (element) { + /** * We return a middleware that modifies * the existing groups. @@ -56,34 +57,19 @@ export default function QuantMEPropertiesProvider( * @return {Object[]} modified groups */ return function (groups) { + // add properties of QuantME tasks to panel - if (element.type && element.type.startsWith("quantme:")) { + if (element.type && element.type.startsWith('quantme:')) { groups.unshift(createQuantMEGroup(element, translate)); } - // update ServiceTasks with the deployment extension - if (element.type && element.type === "bpmn:ServiceTask") { - groups[2] = ImplementationGroup(element, injector, getWineryEndpoint()); - } - // add properties group for displaying the properties defined by the configurations if a configuration // is applied to the current element if (is(element, dataConsts.DATA_MAP_OBJECT)) { - const selectedConfiguration = - dataObjectConfigs().getQuantMEDataConfiguration( - element.businessObject.get(configConsts.SELECT_CONFIGURATIONS_ID) - ); + + const selectedConfiguration = dataObjectConfigs().getQuantMEDataConfiguration(element.businessObject.get(configConsts.SELECT_CONFIGURATIONS_ID)); if (selectedConfiguration) { - groups.splice( - 1, - 0, - createQuantMEDataGroup( - element, - injector, - translate, - selectedConfiguration - ) - ); + groups.splice(1, 0, createQuantMEDataGroup(element, injector, translate, selectedConfiguration)); } } return groups; @@ -93,11 +79,7 @@ export default function QuantMEPropertiesProvider( propertiesPanel.registerProvider(LOW_PRIORITY, this); } -QuantMEPropertiesProvider.$inject = [ - "propertiesPanel", - "injector", - "translate", -]; +QuantMEPropertiesProvider.$inject = ['propertiesPanel', 'injector', 'translate', 'eventBus', 'bpmnFactory']; /** * Create properties group to display custom QuantME properties in the properties panel. The entries of this group @@ -108,47 +90,24 @@ QuantMEPropertiesProvider.$inject = [ * @return {{entries: ([{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *},{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *}]|[{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *},{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *}]|[{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *},{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *}]|[{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *},{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *},{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *},{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *}]|[{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *},{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *},{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *},{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *}]|*|[{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *},{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *},{component: function({element: *}): *, isEdited: function(*): *, id: string, element: *}]), id: string, label}} */ function createQuantMEGroup(element, translate) { + // add required properties to general tab return { - id: "quantmeServiceDetails", - label: translate("Details"), - entries: QuantMEProps(element), + id: 'quantmeServiceDetails', + label: translate('Details'), + entries: QuantMEProps(element) }; } -/** - * Properties group to show customized implementation options entry for service tasks. - * - * @param element The element to show the properties for. - * @param injector The injector of the bpmn-js modeler - * @param wineryEndpoint The winery endpoint of the QuantME plugin - * @return {null|{component: ((function(*): preact.VNode)|*), entries: *[], label, id: string}} - * @constructor - */ -function ImplementationGroup(element, injector, wineryEndpoint) { - const translate = injector.get("translate"); - - const group = { - label: translate("Implementation"), - id: "CamundaPlatform__Implementation", - component: Group, - entries: [...ImplementationProps({ element, wineryEndpoint, translate })], - }; - - if (group.entries.length) { - return group; - } - - return null; -} - /** * Add the property entries for the QuantME attributes to the given group based on the type of the QuantME element * * @param element the QuantME element */ function QuantMEProps(element) { + switch (element.type) { + case consts.QUANTUM_COMPUTATION_TASK: return QuantumComputationTaskProperties(element); @@ -184,7 +143,8 @@ function QuantMEProps(element) { case consts.CUTTING_RESULT_COMBINATION_TASK: return CuttingResultCombinationTaskEntries(element); default: - console.log("Unsupported QuantME element of type: ", element.type); + console.log('Unsupported QuantME element of type: ', element.type); + } } @@ -198,14 +158,10 @@ function QuantMEProps(element) { * @return {{entries: (*), id: string, label}} */ function createQuantMEDataGroup(element, injector, translate, configuration) { + return { - id: "QuantMEDataGroupProperties", - label: translate(configuration.groupLabel || "Data Properties"), - entries: ConfigurationsProperties( - element, - injector, - translate, - configuration - ), + id: 'QuantMEDataGroupProperties', + label: translate(configuration.groupLabel || 'Data Properties'), + entries: ConfigurationsProperties(element, injector, translate, configuration) }; -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/Connector.js b/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/Connector.js deleted file mode 100644 index 6505c614..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/Connector.js +++ /dev/null @@ -1,271 +0,0 @@ -import { SelectEntry } from "@bpmn-io/properties-panel"; -import React from "@bpmn-io/properties-panel/preact/compat"; -import { useService } from "bpmn-js-properties-panel"; -import { getModeler } from "../../../../../editor/ModelerHandler"; - -/** - * Copyright (c) 2023 Institute of Architecture of Application Systems - - * University of Stuttgart - * - * This program and the accompanying materials are made available under the - * terms the Apache Software License 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: Apache-2.0 - */ -const yaml = require("js-yaml"); -/** - * Entry to display the endpoints of the uploaded openapi specification for BPMN service task. - */ -export function Connector({ element, translate, urls }) { - const modeling = useService("modeling"); - const debounce = useService("debounceInput"); - - let arrValues = []; - for (let i = 0; i < urls.length; i++) { - arrValues.push({ - label: urls[i], - value: urls[i], - }); - } - - const selectOptions = function () { - return arrValues; - }; - - const get = function () { - return element.businessObject.get("quantme:connectorUrl"); - }; - - const setValue = function (value) { - const moddle = getModeler().get("moddle"); - const entry = moddle.create( - "camunda:Entry", - { key: "Accept", value: "application/json" }, - { key: "Content-Type", value: "application/json" } - ); - - const map = moddle.create("camunda:Map", { entries: [entry] }); - - entry.$parent = map; - - const headersInputParameter = moddle.create("camunda:InputParameter", { - definition: map, - name: "headers", - }); - const methodInputParameter = moddle.create("camunda:InputParameter", { - name: "method", - value: "POST", - }); - const urlInputParameter = moddle.create("camunda:InputParameter", { - name: "url", - value: "", - }); - - let endpointParameters = determineInputParameters( - element.businessObject.yaml, - value - ); - let scriptValue = constructScript(endpointParameters); - const script = moddle.create("camunda:Script", { - scriptFormat: "Groovy", - value: scriptValue, - resource: "Inline", - }); - - const payloadInputParameter = moddle.create("camunda:InputParameter", { - definition: script, - name: "payload", - }); - let inputParameters = []; - inputParameters.push(headersInputParameter); - inputParameters.push(methodInputParameter); - inputParameters.push(urlInputParameter); - inputParameters.push(payloadInputParameter); - - let outputParameters = []; - - outputParameters = determineOutputParameters(element.businessObject.yaml); - let camundaOutputParameters = - constructCamundaOutputParameters(outputParameters); - - let inputOutput = moddle.create("camunda:InputOutput", { - inputParameters: inputParameters, - outputParameters: camundaOutputParameters, - }); - element.businessObject.extensionElements = moddle.create( - "bpmn:ExtensionElements", - { - values: [ - moddle.create("camunda:Connector", { - connectorId: "http-connector", - inputOutput: inputOutput, - }), - ], - } - ); - return modeling.updateProperties(element, { connectorUrl: value || "" }); - }; - - return ( - <> - { - - } - - ); -} - -function determineInputParameters(yamlData, schemePath) { - // Parse the YAML data - const data = yaml.load(yamlData); - - // Initialize an object to store the input parameters - const inputParameters = {}; - let scheme = ""; - - // Extract the request bodies and their parameters - for (const [path, methods] of Object.entries(data.paths)) { - if (path === schemePath) { - for (const [details] of Object.entries(methods)) { - if (details.requestBody) { - const requestBody = details.requestBody; - const content = requestBody.content; - for (const [contentDetails] of Object.entries(content)) { - if (contentDetails.schema) { - scheme = contentDetails.schema; - inputParameters[path] = scheme.properties || {}; - } - } - } - } - } - } - - const document = yaml.load(yamlData); - scheme = String(scheme.$ref).replace("#/", "").replaceAll("/", "."); - - // Access the dynamically determined schema - const schemaPath = scheme; - const schema = getObjectByPath(document, schemaPath); - - // Function to access an object property by path - function getObjectByPath(obj, path) { - const parts = path.split("."); - let currentObj = obj; - for (const part of parts) { - if (!currentObj || !currentObj.hasOwnProperty(part)) { - return undefined; - } - currentObj = currentObj[part]; - } - return currentObj; - } - - // Access the properties of the schema - const properties = Object.keys(schema.properties); - return properties; -} - -// Function to access an object property by path -function getObjectByPath2(obj, path) { - const parts = path.split("."); - let currentObj = obj; - for (const part of parts) { - if (!currentObj || !currentObj.hasOwnProperty(part)) { - return undefined; - } - currentObj = currentObj[part]; - } - return currentObj; -} - -function determineOutputParameters(yamlData) { - // Parse the YAML data - const data = yaml.load(yamlData); - - // Initialize an object to store the input parameters - let outputParameters = []; - - // Extract the request bodies and their parameters - for (const [methods] of Object.entries(data.paths)) { - for (const [details] of Object.entries(methods)) { - if (details.responses) { - const response = details.responses; - // Access the properties of the schema - // Access the schema referenced by "200" - const statusCode = "200"; - const schemaRef = - response[statusCode].content["application/json"].schema.$ref; - const schemaPath = schemaRef.replace("#/", "").replaceAll("/", "."); - const schema = getObjectByPath2(data, schemaPath); - - // Access the properties of the schema - outputParameters = Object.keys(schema.properties); - } - } - } - return outputParameters; -} - -function constructCamundaOutputParameters(parameters) { - let outputParameters = []; - for (let param of parameters) { - let moddle = getModeler().get("moddle"); - const script = moddle.create("camunda:Script", { - scriptFormat: "Groovy", - value: - 'def resp = connector.getVariable("response");\n' + - "resp = new groovy.json.JsonSlurper().parseText(resp);\n" + - "def " + - param + - " = resp.get(" + - param + - ");\n" + - "println(" + - param + - ");\n" + - "return " + - param + - ";", - resource: "Inline", - }); - - const outputParameter = moddle.create("camunda:OutputParameter", { - definition: script, - name: param, - }); - outputParameters.push(outputParameter); - } - return outputParameters; -} - -function constructScript(parameters) { - let script = "import groovy.json.JsonBuilder;\n"; - let jsonString = "def request = [:];\n"; - for (let param of parameters) { - script += - "def " + - param + - ' = execution.getVariable("' + - param + - '");\n' + - "println(" + - param + - ");\n"; - jsonString += 'request.put("' + param + '",' + param + ");\n"; - } - //jsonString = removeLastComma(jsonString); - jsonString += - "requeststring = new JsonBuilder(request).toPrettyString();\nreturn requeststring;"; - script += jsonString; - //script += 'myJson = JSON.stringify(myJson)\nmyJson = myJson'; - return script; -} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/Deployment.js b/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/Deployment.js deleted file mode 100644 index 0b5c0e24..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/Deployment.js +++ /dev/null @@ -1,125 +0,0 @@ -import { SelectEntry } from "@bpmn-io/properties-panel"; -import React from "@bpmn-io/properties-panel/preact/compat"; -import { useService } from "bpmn-js-properties-panel"; -import { getImplementationType } from "../../../utilities/ImplementationTypeHelperExtension"; - -/** - * Copyright (c) 2023 Institute of Architecture of Application Systems - - * University of Stuttgart - * - * This program and the accompanying materials are made available under the - * terms the Apache Software License 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -const jquery = require("jquery"); - -const QUANTME_NAMESPACE_PULL = "http://quantil.org/quantme/pull"; -const QUANTME_NAMESPACE_PUSH = "http://quantil.org/quantme/push"; - -/** - * Entry to display the custom Implementation option deployment for BPMN service task. Through this option you can define - * a CSAR as implementation of a service task. - */ -export function Deployment({ element, translate, wineryEndpoint }) { - const modeling = useService("modeling"); - const debounce = useService("debounceInput"); - - const selectOptions = function () { - const arrValues = []; - jquery.ajax({ - url: wineryEndpoint + "/servicetemplates/?grouped", - method: "GET", - success: function (result) { - let checks = 0; - for (let i = 0; i < result.length; i++) { - if (result[i].text === QUANTME_NAMESPACE_PULL) { - result[i].children.forEach((element) => - arrValues.push({ - label: element.text, - value: concatenateCsarEndpoint( - "{{ wineryEndpoint }}", - result[i].id, - element.text - ), - }) - ); - checks++; - } - if (result[i].text === QUANTME_NAMESPACE_PUSH) { - result[i].children.forEach((element) => - arrValues.push({ - label: element.text, - value: concatenateCsarEndpoint( - "{{ wineryEndpoint }}", - result[i].id, - element.text - ), - }) - ); - checks++; - } - if (checks === 2) { - break; - } - } - }, - async: false, - }); - if (arrValues.length === 0) { - arrValues.push({ label: "No CSARs available", value: "" }); - } - return arrValues; - }; - - const get = function () { - return element.businessObject.get("quantme:deploymentModelUrl"); - }; - - const setValue = function (value) { - return modeling.updateProperties(element, { - deploymentModelUrl: value || "", - }); - }; - - const validate = function (values) { - return values === undefined || values === "" - ? translate("Must provide a CSAR") - : ""; - }; - - const hidden = function () { - const implType = getImplementationType(element); - console.log("getImplementationType returns " + implType); - return !(implType === "deploymentModel"); - }; - - return ( - <> - {!hidden() && ( - - )} - - ); -} - -function concatenateCsarEndpoint(wineryEndpoint, namespace, csarName) { - return ( - wineryEndpoint + - "/servicetemplates/" + - encodeURIComponent(namespace) + - "/" + - csarName + - "/?csar" - ); -} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/DmnImplementationProps.js b/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/DmnImplementationProps.js deleted file mode 100644 index 7641394c..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/DmnImplementationProps.js +++ /dev/null @@ -1,363 +0,0 @@ -import { getBusinessObject } from "bpmn-js/lib/util/ModelUtil"; - -import { - TextFieldEntry, - isTextFieldEntryEdited, - SelectEntry, - isSelectEntryEdited, -} from "@bpmn-io/properties-panel"; -import { getImplementationType } from "../../../utilities/ImplementationTypeHelperExtension"; -import { useService } from "bpmn-js-properties-panel"; - -export function DmnImplementationProps(props) { - const { element } = props; - - const entries = []; - - const implementationType = getImplementationType(element); - const bindingType = getDecisionRefBinding(element); - - if (implementationType !== "dmn") { - return entries; - } - - // (1) decisionRef - entries.push({ - id: "decisionRef", - component: DecisionRef, - isEdited: isTextFieldEntryEdited, - }); - - // (2) binding - entries.push({ - id: "decisionRefBinding", - component: Binding, - isEdited: isSelectEntryEdited, - }); - - // (3) version - if (bindingType === "version") { - entries.push({ - id: "decisionRefVersion", - component: Version, - isEdited: isTextFieldEntryEdited, - }); - } - - // (4) versionTag - if (bindingType === "versionTag") { - entries.push({ - id: "decisionRefVersionTag", - component: VersionTag, - isEdited: isTextFieldEntryEdited, - }); - } - - // (5) tenantId - entries.push({ - id: "decisionRefTenantId", - component: TenantId, - isEdited: isTextFieldEntryEdited, - }); - - // (6) resultVariable - entries.push({ - id: "decisionRefResultVariable", - component: ResultVariable, - isEdited: isTextFieldEntryEdited, - }); - - // (7) mapDecisionResult - if (getResultVariable(element)) { - entries.push({ - id: "mapDecisionResult", - component: MapDecisionResult, - isEdited: isSelectEntryEdited, - }); - } - - return entries; -} - -function DecisionRef(props) { - const { element } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - const debounce = useService("debounceInput"); - - const businessObject = getBusinessObject(element); - - const getValue = () => { - return businessObject.get("camunda:decisionRef"); - }; - - const setValue = (value) => { - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: businessObject, - properties: { - "camunda:decisionRef": value || "", - }, - }); - }; - - return TextFieldEntry({ - element, - id: "decisionRef", - label: translate("Decision reference"), - getValue, - setValue, - debounce, - }); -} - -function Binding(props) { - const { element } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - - const getValue = () => { - return getDecisionRefBinding(element); - }; - - const setValue = (value) => { - const businessObject = getBusinessObject(element); - - // reset version properties on binding type change - const updatedProperties = { - "camunda:decisionRefVersion": undefined, - "camunda:decisionRefVersionTag": undefined, - "camunda:decisionRefBinding": value, - }; - - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: businessObject, - properties: updatedProperties, - }); - }; - - // Note: default is "latest", - // cf. https://docs.camunda.org/manual/latest/reference/bpmn20/custom-extensions/extension-attributes/#decisionrefbinding - const getOptions = () => { - const options = [ - { value: "deployment", label: translate("deployment") }, - { value: "latest", label: translate("latest") }, - { value: "version", label: translate("version") }, - { value: "versionTag", label: translate("versionTag") }, - ]; - - return options; - }; - - return SelectEntry({ - element, - id: "decisionRefBinding", - label: translate("Binding"), - getValue, - setValue, - getOptions, - }); -} - -function Version(props) { - const { element } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - const debounce = useService("debounceInput"); - - const businessObject = getBusinessObject(element); - - const getValue = () => { - return businessObject.get("camunda:decisionRefVersion"); - }; - - const setValue = (value) => { - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: businessObject, - properties: { - "camunda:decisionRefVersion": value, - }, - }); - }; - - return TextFieldEntry({ - element, - id: "decisionRefVersion", - label: translate("Version"), - getValue, - setValue, - debounce, - }); -} - -function VersionTag(props) { - const { element } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - const debounce = useService("debounceInput"); - - const businessObject = getBusinessObject(element); - - const getValue = () => { - return businessObject.get("camunda:decisionRefVersionTag"); - }; - - const setValue = (value) => { - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: businessObject, - properties: { - "camunda:decisionRefVersionTag": value, - }, - }); - }; - - return TextFieldEntry({ - element, - id: "decisionRefVersionTag", - label: translate("Version tag"), - getValue, - setValue, - debounce, - }); -} - -function TenantId(props) { - const { element } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - const debounce = useService("debounceInput"); - - const businessObject = getBusinessObject(element); - - const getValue = () => { - return businessObject.get("camunda:decisionRefTenantId"); - }; - - const setValue = (value) => { - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: businessObject, - properties: { - "camunda:decisionRefTenantId": value, - }, - }); - }; - - return TextFieldEntry({ - element, - id: "decisionRefTenantId", - label: translate("Tenant ID"), - getValue, - setValue, - debounce, - }); -} - -function ResultVariable(props) { - const { element } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - const debounce = useService("debounceInput"); - - const businessObject = getBusinessObject(element); - - const getValue = () => { - return getResultVariable(businessObject); - }; - - // Note: camunda:mapDecisionResult got cleaned up in modeling behavior - // cf. https://github.com/camunda/camunda-bpmn-js/blob/main/lib/camunda-platform/features/modeling/behavior/UpdateResultVariableBehavior.js - const setValue = (value) => { - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: businessObject, - properties: { - "camunda:resultVariable": value, - }, - }); - }; - - return TextFieldEntry({ - element, - id: "decisionRefResultVariable", - label: translate("Result variable"), - getValue, - setValue, - debounce, - }); -} - -function MapDecisionResult(props) { - const { element } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - - const businessObject = getBusinessObject(element); - - const getValue = () => { - return businessObject.get("camunda:mapDecisionResult") || "resultList"; - }; - - const setValue = (value) => { - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: businessObject, - properties: { - "camunda:mapDecisionResult": value, - }, - }); - }; - - // Note: default is "resultList", - // cf. https://docs.camunda.org/manual/latest/reference/bpmn20/custom-extensions/extension-attributes/#mapdecisionresult - const getOptions = () => { - const options = [ - { - value: "collectEntries", - label: translate("collectEntries (List)"), - }, - { - value: "resultList", - label: translate("resultList (List>)"), - }, - { value: "singleEntry", label: translate("singleEntry (TypedValue)") }, - { - value: "singleResult", - label: translate("singleResult (Map)"), - }, - ]; - - return options; - }; - - return SelectEntry({ - element, - id: "mapDecisionResult", - label: translate("Map decision result"), - getValue, - setValue, - getOptions, - }); -} - -// helper //////////////////// - -function getDecisionRefBinding(element) { - const businessObject = getBusinessObject(element); - return businessObject.get("camunda:decisionRefBinding") || "latest"; -} - -function getResultVariable(element) { - const businessObject = getBusinessObject(element); - return businessObject.get("camunda:resultVariable"); -} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/ImplementationProps.js b/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/ImplementationProps.js deleted file mode 100644 index b2f8e499..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/ImplementationProps.js +++ /dev/null @@ -1,347 +0,0 @@ -import { - TextFieldEntry, - isTextFieldEntryEdited, -} from "@bpmn-io/properties-panel"; -import { DmnImplementationProps } from "./DmnImplementationProps"; -import { ImplementationTypeProps } from "./ImplementationTypeProps"; - -import { useService } from "bpmn-js-properties-panel"; -import { getImplementationType } from "../../../utilities/ImplementationTypeHelperExtension"; -import { getServiceTaskLikeBusinessObject } from "../../../../../editor/util/camunda-utils/ImplementationTypeUtils"; -import { getExtensionElementsList } from "../../../../../editor/util/camunda-utils/ExtensionElementsUtil"; -import { Deployment } from "./Deployment"; -import { Connector } from "./Connector"; -import { YamlUpload } from "./YamlUpload"; -const yaml = require("js-yaml"); -const QUANTME_NAMESPACE_PULL = "http://quantil.org/quantme/pull"; - -/** - * Properties group for service tasks. Extends the original implementation by adding a new selection option to the - * implementation entry: deployment. - * - * @param props - * @return {{component: function(*): preact.VNode, isEdited: function(*): *, id: string}[]|*[]} - * @constructor - */ -export function ImplementationProps(props) { - const { element, wineryEndpoint, translate } = props; - - if (!getServiceTaskLikeBusinessObject(element)) { - return []; - } - - const implementationType = getImplementationType(element); - - // (1) display implementation type select - const entries = [...ImplementationTypeProps({ element })]; - - // (2) display implementation properties based on type - if (implementationType === "class") { - entries.push({ - id: "javaClass", - component: JavaClass, - isEdited: isTextFieldEntryEdited, - }); - } else if (implementationType === "expression") { - entries.push( - { - id: "expression", - component: Expression, - isEdited: isTextFieldEntryEdited, - }, - { - id: "expressionResultVariable", - component: ResultVariable, - isEdited: isTextFieldEntryEdited, - } - ); - } else if (implementationType === "delegateExpression") { - entries.push({ - id: "delegateExpression", - component: DelegateExpression, - isEdited: isTextFieldEntryEdited, - }); - } else if (implementationType === "dmn") { - entries.push(...DmnImplementationProps({ element })); - } else if (implementationType === "external") { - entries.push({ - id: "externalTopic", - component: Topic, - isEdited: isTextFieldEntryEdited, - }); - } else if (implementationType === "connector") { - entries.push({ - id: "connectorId", - component: ConnectorId, - isEdited: isTextFieldEntryEdited, - }); - - // custom extension - } else if (implementationType === "deploymentModel") { - entries.push({ - id: "deployment", - element, - translate, - wineryEndpoint, - component: Deployment, - isEdited: isTextFieldEntryEdited, - }); - - entries.push({ - id: "yamlUpload", - component: YamlUpload, - isEdited: isTextFieldEntryEdited, - }); - console.log( - !element.businessObject.deploymentModelUrl.includes( - encodeURIComponent(encodeURIComponent(QUANTME_NAMESPACE_PULL)) - ) - ); - if ( - !element.businessObject.deploymentModelUrl.includes( - encodeURIComponent(encodeURIComponent(QUANTME_NAMESPACE_PULL)) - ) && - element.businessObject.yaml !== undefined - ) { - const urls = extractUrlsFromYaml(element.businessObject.yaml); - entries.push({ - id: "connector", - element, - translate, - urls, - component: Connector, - isEdited: isTextFieldEntryEdited, - }); - } - } - return entries; -} - -function extractUrlsFromYaml(content) { - const doc = yaml.load(content); - - // Extract URLs from paths - const paths = Object.keys(doc.paths); - const urls = paths.map((path) => { - return `${path}`; - }); - - return urls; -} - -export function JavaClass(props) { - const { - element, - businessObject = getServiceTaskLikeBusinessObject(element), - id = "javaClass", - } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - const debounce = useService("debounceInput"); - - const getValue = () => { - return businessObject.get("camunda:class"); - }; - - const setValue = (value) => { - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: businessObject, - properties: { - "camunda:class": value || "", - }, - }); - }; - - return TextFieldEntry({ - element, - id, - label: translate("Java class"), - getValue, - setValue, - debounce, - }); -} - -export function Expression(props) { - const { - element, - businessObject = getServiceTaskLikeBusinessObject(element), - id = "expression", - } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - const debounce = useService("debounceInput"); - - const getValue = () => { - return businessObject.get("camunda:expression"); - }; - - const setValue = (value) => { - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: businessObject, - properties: { - "camunda:expression": value || "", - }, - }); - }; - - return TextFieldEntry({ - element, - id, - label: translate("Expression"), - getValue, - setValue, - debounce, - }); -} - -function ResultVariable(props) { - const { element } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - const debounce = useService("debounceInput"); - - const businessObject = getServiceTaskLikeBusinessObject(element); - - const getValue = () => { - return businessObject.get("camunda:resultVariable"); - }; - - const setValue = (value) => { - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: businessObject, - properties: { - "camunda:resultVariable": value, - }, - }); - }; - - return TextFieldEntry({ - element, - id: "expressionResultVariable", - label: translate("Result variable"), - getValue, - setValue, - debounce, - }); -} - -export function DelegateExpression(props) { - const { - element, - businessObject = getServiceTaskLikeBusinessObject(element), - id = "delegateExpression", - } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - const debounce = useService("debounceInput"); - - const getValue = () => { - return businessObject.get("camunda:delegateExpression"); - }; - - const setValue = (value) => { - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: businessObject, - properties: { - "camunda:delegateExpression": value || "", - }, - }); - }; - - return TextFieldEntry({ - element, - id, - label: translate("Delegate expression"), - getValue, - setValue, - debounce, - }); -} - -function Topic(props) { - const { element } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - const debounce = useService("debounceInput"); - - const businessObject = getServiceTaskLikeBusinessObject(element); - - const getValue = () => { - return businessObject.get("camunda:topic"); - }; - - const setValue = (value) => { - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: businessObject, - properties: { - "camunda:topic": value, - }, - }); - }; - - return TextFieldEntry({ - element, - id: "externalTopic", - label: translate("Topic"), - getValue, - setValue, - debounce, - }); -} - -function ConnectorId(props) { - const { element } = props; - - const commandStack = useService("commandStack"); - const translate = useService("translate"); - const debounce = useService("debounceInput"); - - const connector = getConnector(element); - - const getValue = () => { - return connector.get("camunda:connectorId"); - }; - - const setValue = (value) => { - commandStack.execute("element.updateModdleProperties", { - element, - moddleElement: connector, - properties: { - "camunda:connectorId": value, - }, - }); - }; - - return TextFieldEntry({ - element, - id: "connectorId", - label: translate("Connector ID"), - getValue, - setValue, - debounce, - }); -} - -// helper ////////////////// - -function getConnectors(businessObject) { - return getExtensionElementsList(businessObject, "camunda:Connector"); -} - -function getConnector(element) { - const businessObject = getServiceTaskLikeBusinessObject(element); - const connectors = getConnectors(businessObject); - - return connectors[0]; -} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/ImplementationTypeProps.js b/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/ImplementationTypeProps.js deleted file mode 100644 index ace2e823..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/ImplementationTypeProps.js +++ /dev/null @@ -1,290 +0,0 @@ -import { sortBy, without } from "min-dash"; - -import { SelectEntry, isSelectEntryEdited } from "@bpmn-io/properties-panel"; -import { useService } from "bpmn-js-properties-panel"; -import { getImplementationType } from "../../../utilities/ImplementationTypeHelperExtension"; -import { createElement } from "../../../../../editor/util/camunda-utils/ElementUtil"; -import { - getServiceTaskLikeBusinessObject, - isDeploymentCapable, - isDmnCapable, - isExternalCapable, - isServiceTaskLike, -} from "../../../../../editor/util/camunda-utils/ImplementationTypeUtils"; -import { getExtensionElementsList } from "../../../../../editor/util/camunda-utils/ExtensionElementsUtil"; - -const DELEGATE_PROPS = { - "camunda:class": undefined, - "camunda:expression": undefined, - "camunda:delegateExpression": undefined, - "camunda:resultVariable": undefined, -}; - -const DMN_CAPABLE_PROPS = { - "camunda:decisionRef": undefined, - "camunda:decisionRefBinding": "latest", - "camunda:decisionRefVersion": undefined, - "camunda:mapDecisionResult": "resultList", - "camunda:decisionRefTenantId": undefined, -}; - -const EXTERNAL_CAPABLE_PROPS = { - "camunda:type": undefined, - "camunda:topic": undefined, -}; - -const IMPLEMENTATION_TYPE_NONE_LABEL = "", - IMPLEMENTATION_TYPE_JAVA_LABEL = "Java class", - IMPLEMENTATION_TYPE_EXPRESSION_LABEL = "Expression", - IMPLEMENTATION_TYPE_DELEGATE_LABEL = "Delegate expression", - IMPLEMENTATION_TYPE_DMN_LABEL = "DMN", - IMPLEMENTATION_TYPE_EXTERNAL_LABEL = "External", - IMPLEMENTATION_TYPE_CONNECTOR_LABEL = "Connector", - IMPLEMENTATION_TYPE_DEPLOYMENT_LABEL = "Deployment Model"; - -export function ImplementationTypeProps() { - return [ - { - id: "implementationType", - component: ImplementationType, - isEdited: isSelectEntryEdited, - }, - ]; -} - -function ImplementationType(props) { - const { element } = props; - - const bpmnFactory = useService("bpmnFactory"); - const commandStack = useService("commandStack"); - const translate = useService("translate"); - - const getValue = () => { - return getImplementationType(element) || ""; - }; - - const setValue = (value) => { - const oldType = getImplementationType(element); - const businessObject = getServiceTaskLikeBusinessObject(element); - const commands = []; - - let updatedProperties = DELEGATE_PROPS; - let extensionElements = businessObject.get("extensionElements"); - - // (1) class, expression, delegateExpression - if (isDelegateType(value)) { - updatedProperties = { - ...updatedProperties, - [value]: isDelegateType(oldType) - ? businessObject.get(`camunda:${oldType}`) - : "", - }; - } - - // (2) dmn - if (isDmnCapable(businessObject)) { - updatedProperties = { - ...updatedProperties, - ...DMN_CAPABLE_PROPS, - }; - - if (value === "dmn") { - updatedProperties = { - ...updatedProperties, - "camunda:decisionRef": "", - }; - } - } - - // (3) external - // Note: error event definition elements got cleaned up in modeling behavior - // cf. https://github.com/camunda/camunda-bpmn-js/blob/main/lib/camunda-platform/features/modeling/behavior/DeleteErrorEventDefinitionBehavior.js - if (isExternalCapable(businessObject)) { - updatedProperties = { - ...updatedProperties, - ...EXTERNAL_CAPABLE_PROPS, - }; - - if (value === "external") { - updatedProperties = { - ...updatedProperties, - "camunda:type": "external", - "camunda:topic": "", - }; - } - } - - // (4) connector - if (isServiceTaskLike(businessObject)) { - // (4.1) remove all connectors on type change - const connectors = getConnectors(businessObject); - - if (connectors.length) { - commands.push({ - cmd: "element.updateModdleProperties", - context: { - element, - moddleElement: extensionElements, - properties: { - values: without(extensionElements.get("values"), (value) => - connectors.includes(value) - ), - }, - }, - }); - } - - // (4.2) create connector - if (value === "connector") { - // ensure extension elements - if (!extensionElements) { - extensionElements = createElement( - "bpmn:ExtensionElements", - { values: [] }, - businessObject, - bpmnFactory - ); - - commands.push( - UpdateModdlePropertiesCommand(element, businessObject, { - extensionElements, - }) - ); - } - - const connector = createElement( - "camunda:Connector", - {}, - extensionElements, - bpmnFactory - ); - - commands.push({ - cmd: "element.updateModdleProperties", - context: { - element, - moddleElement: extensionElements, - properties: { - values: [...extensionElements.get("values"), connector], - }, - }, - }); - } - } - - // (5) deployment - if (isDeploymentCapable(businessObject)) { - updatedProperties = { - ...updatedProperties, - "quantme:deploymentModelUrl": undefined, - }; - - if (value === "deploymentModel") { - updatedProperties = { - ...updatedProperties, - "quantme:deploymentModelUrl": "", - }; - } - } - - // (5) collect all property updates - commands.push( - UpdateModdlePropertiesCommand(element, businessObject, updatedProperties) - ); - - // (6) commit all updates - commandStack.execute("properties-panel.multi-command-executor", commands); - }; - - const getOptions = () => { - const businessObject = getServiceTaskLikeBusinessObject(element); - - const options = [ - { value: "", label: translate(IMPLEMENTATION_TYPE_NONE_LABEL) }, - { value: "class", label: translate(IMPLEMENTATION_TYPE_JAVA_LABEL) }, - { - value: "expression", - label: translate(IMPLEMENTATION_TYPE_EXPRESSION_LABEL), - }, - { - value: "delegateExpression", - label: translate(IMPLEMENTATION_TYPE_DELEGATE_LABEL), - }, - ]; - - if (isDmnCapable(businessObject)) { - options.push({ - value: "dmn", - label: translate(IMPLEMENTATION_TYPE_DMN_LABEL), - }); - } - - if (isExternalCapable(businessObject)) { - options.push({ - value: "external", - label: translate(IMPLEMENTATION_TYPE_EXTERNAL_LABEL), - }); - } - - if (isServiceTaskLike(businessObject)) { - options.push({ - value: "connector", - label: translate(IMPLEMENTATION_TYPE_CONNECTOR_LABEL), - }); - } - - // add deployment - if (isDeploymentCapable(businessObject)) { - options.push({ - value: "deploymentModel", - label: translate(IMPLEMENTATION_TYPE_DEPLOYMENT_LABEL), - }); - } - - return sortByPriority(options); - }; - - return SelectEntry({ - element, - id: "implementationType", - label: translate("Type"), - getValue, - setValue, - getOptions, - }); -} - -// helper /////////////////////// - -function isDelegateType(type) { - return ["class", "expression", "delegateExpression"].includes(type); -} - -function getConnectors(businessObject) { - return getExtensionElementsList(businessObject, "camunda:Connector"); -} - -function UpdateModdlePropertiesCommand(element, businessObject, newProperties) { - return { - cmd: "element.updateModdleProperties", - context: { - element, - moddleElement: businessObject, - properties: newProperties, - }, - }; -} - -function sortByPriority(options) { - const priorities = { - [IMPLEMENTATION_TYPE_NONE_LABEL]: 0, - [IMPLEMENTATION_TYPE_JAVA_LABEL]: 3, - [IMPLEMENTATION_TYPE_EXPRESSION_LABEL]: 4, - [IMPLEMENTATION_TYPE_DELEGATE_LABEL]: 5, - [IMPLEMENTATION_TYPE_DMN_LABEL]: 1, - [IMPLEMENTATION_TYPE_EXTERNAL_LABEL]: 2, - [IMPLEMENTATION_TYPE_CONNECTOR_LABEL]: 6, - }; - - return sortBy(options, (o) => priorities[o.label]); -} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/YamlUpload.js b/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/YamlUpload.js deleted file mode 100644 index 2cca55b8..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/YamlUpload.js +++ /dev/null @@ -1,60 +0,0 @@ -import { HeaderButton } from "@bpmn-io/properties-panel"; -import { useService } from "bpmn-js-properties-panel"; -import React from "react"; -import YamlModal from "./YamlModal"; -import { createRoot } from "react-dom/client"; -import { useState } from "react"; -import "./yaml-modal.css"; - -/** - * Entry to display the button which opens the Yaml Model, a dialog which allows to upload yml files. - */ -export function YamlUpload(props) { - const { element } = props; - const translate = useService("translate"); - const commandStack = useService("commandStack"); - - const onClick = () => { - const yamlUploadDiv = document.querySelector("#yamlUploadDiv"); - - if (yamlUploadDiv) { - yamlUploadDiv.remove(); - } - const div = document.createElement("div"); - div.id = "yamlUploadDiv"; - document.getElementById("main-div").appendChild(div); - const root = createRoot(document.getElementById("yamlUploadDiv")); - root.render(); - }; - - return HeaderButton({ - element, - id: "upload-yaml-button", - text: translate("Upload YAML"), - description: "Upload YML", - className: "upload-yaml-button", - children: "Upload YAML", - onClick, - }); -} - -function YAMLModal(props) { - const [showModal, setShowModal] = useState(true); - const { element, commandStack } = props; - - function handleModalClosed() { - setShowModal(false); - } - - return ( -
- {showModal && ( - - )} -
- ); -} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/yaml-modal.css b/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/yaml-modal.css deleted file mode 100644 index 1656cc26..00000000 --- a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/service-task/yaml-modal.css +++ /dev/null @@ -1,53 +0,0 @@ -.tab { - flex: 1; - padding: 10px; - border-radius: 3px; - text-align: center; - cursor: pointer; - font-weight: bold; - font-stretch: normal; - font-style: normal; - line-height: normal; - letter-spacing: normal; - color: #fdfdfe; - margin: 1px; - box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.2); - border: solid 1px var(--blue-darken-62); - background-color: var(--blue-base-65); -} - -.tab.active { - background-color: var(--blue-darken-55); -} - -.tab:hover { - border: solid 1px var(--blue-darken-55); - background-color: var(--blue-darken-62); -} - -.tabButtonsContainer { - display: flex; - flex-direction: row; - align-items: center; - overflow: auto; - min-width: 120px; - max-height: 263px; - direction: ltr; -} - -.upload-yaml-button { - outline: none; - background-color: var(--color-grey-225-10-97); - border: 1px solid var(--color-grey-225-10-75); - border-radius: 4px; - margin: 2px 32px 6px 12px; - padding: 2px 6px 2px 6px; - fill: var(--add-entry-fill-color); -} - -.upload-yaml-button:hover { - color: white; - border: 1px solid white; - background-color: var(--color-blue-205-100-50); - fill: var(--add-entry-hover-fill-color); -} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/resources/quantum4bpmn.json b/components/bpmn-q/modeler-component/extensions/quantme/resources/quantum4bpmn.json index 2c6eaee9..a702b9b1 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/resources/quantum4bpmn.json +++ b/components/bpmn-q/modeler-component/extensions/quantme/resources/quantum4bpmn.json @@ -8,7 +8,7 @@ "types": [ { "name": "QuantMEProcess", - "extends": ["bpmn:Process"], + "extends": [ "bpmn:Process" ], "properties": [ { "name": "flowElements", @@ -20,7 +20,7 @@ }, { "name": "QuantumHardwareSelectionSubprocess", - "superClass": ["bpmn:SubProcess"], + "superClass": [ "bpmn:SubProcess" ], "properties": [ { "name": "providers", @@ -41,7 +41,7 @@ }, { "name": "CircuitCuttingSubprocess", - "superClass": ["bpmn:SubProcess"], + "superClass": [ "bpmn:SubProcess" ], "properties": [ { "name": "cuttingMethod", @@ -67,7 +67,7 @@ }, { "name": "QuantumComputationTask", - "superClass": ["bpmn:Task"], + "superClass": [ "bpmn:Task" ], "properties": [ { "name": "algorithm", @@ -83,7 +83,7 @@ }, { "name": "QuantumCircuitLoadingTask", - "superClass": ["bpmn:Task"], + "superClass": [ "bpmn:Task" ], "properties": [ { "name": "quantumCircuit", @@ -98,7 +98,7 @@ }, { "name": "DataPreparationTask", - "superClass": ["bpmn:Task"], + "superClass": [ "bpmn:Task" ], "properties": [ { "name": "encodingSchema", @@ -114,7 +114,7 @@ }, { "name": "OracleExpansionTask", - "superClass": ["bpmn:Task"], + "superClass": [ "bpmn:Task" ], "properties": [ { "name": "oracleId", @@ -139,7 +139,7 @@ }, { "name": "QuantumCircuitExecutionTask", - "superClass": ["bpmn:Task"], + "superClass": [ "bpmn:Task" ], "properties": [ { "name": "provider", @@ -165,7 +165,9 @@ }, { "name": "ParameterOptimizationTask", - "superClass": ["bpmn:Task"], + "superClass": [ + "bpmn:Task" + ], "properties": [ { "name": "optimizer", @@ -191,7 +193,9 @@ }, { "name": "VariationalQuantumAlgorithmTask", - "superClass": ["bpmn:Task"], + "superClass": [ + "bpmn:Task" + ], "properties": [ { "name": "quantumAlgorithm", @@ -242,7 +246,9 @@ }, { "name": "ResultEvaluationTask", - "superClass": ["bpmn:Task"], + "superClass": [ + "bpmn:Task" + ], "properties": [ { "name": "objectiveFunction", @@ -268,7 +274,9 @@ }, { "name": "WarmStartingTask", - "superClass": ["bpmn:Task"], + "superClass": [ + "bpmn:Task" + ], "properties": [ { "name": "warmStartingMethod", @@ -299,7 +307,7 @@ }, { "name": "ReadoutErrorMitigationTask", - "superClass": ["bpmn:Task"], + "superClass": [ "bpmn:Task" ], "properties": [ { "name": "provider", @@ -368,25 +376,9 @@ } ] }, - { - "name": "ServiceTask", - "extends": ["bpmn:ServiceTask"], - "properties": [ - { - "name": "deploymentModelUrl", - "isAttr": true, - "type": "String" - }, - { - "name": "connectorUrl", - "isAttr": true, - "type": "String" - } - ] - }, { "name": "Task", - "extends": ["bpmn:Task"], + "extends": [ "bpmn:Task" ], "properties": [ { "name": "hybridRuntimeExecution", @@ -407,7 +399,7 @@ }, { "name": "CircuitCuttingSubprocess", - "superClass": ["bpmn:SubProcess"], + "superClass": [ "bpmn:SubProcess" ], "properties": [ { "name": "cuttingMethod", @@ -433,7 +425,9 @@ }, { "name": "ParameterOptimizationTask", - "superClass": ["bpmn:Task"], + "superClass": [ + "bpmn:Task" + ], "properties": [ { "name": "optimizer", @@ -459,7 +453,9 @@ }, { "name": "VariationalQuantumAlgorithmTask", - "superClass": ["bpmn:Task"], + "superClass": [ + "bpmn:Task" + ], "properties": [ { "name": "quantumAlgorithm", @@ -510,7 +506,9 @@ }, { "name": "ResultEvaluationTask", - "superClass": ["bpmn:Task"], + "superClass": [ + "bpmn:Task" + ], "properties": [ { "name": "objectiveFunction", @@ -536,7 +534,9 @@ }, { "name": "WarmStartingTask", - "superClass": ["bpmn:Task"], + "superClass": [ + "bpmn:Task" + ], "properties": [ { "name": "warmStartingMethod", @@ -567,7 +567,7 @@ }, { "name": "CircuitCuttingTask", - "superClass": ["bpmn:Task"], + "superClass": [ "bpmn:Task" ], "properties": [ { "name": "cuttingMethod", @@ -593,7 +593,7 @@ }, { "name": "CuttingResultCombinationTask", - "superClass": ["bpmn:Task"], + "superClass": [ "bpmn:Task" ], "properties": [ { "name": "cuttingMethod", @@ -605,4 +605,4 @@ ], "enumerations": [], "associations": [] -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/styling/quantme.css b/components/bpmn-q/modeler-component/extensions/quantme/styling/quantme.css index d2baed8f..46f8c4cf 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/styling/quantme.css +++ b/components/bpmn-q/modeler-component/extensions/quantme/styling/quantme.css @@ -1,4 +1,4 @@ -@import url("~bpmn-font/dist/css/bpmn-embedded.css"); +@import url('~bpmn-font/dist/css/bpmn-embedded.css'); .qwm .quantme-logo:before { content: ""; @@ -38,8 +38,7 @@ width: 15px; height: 15px; background-size: contain; - background: url("../resources/hybrid-loop-adapation-icon.png") no-repeat - center center; + background: url("../resources/hybrid-loop-adapation-icon.png") no-repeat center center; float: left; } @@ -49,8 +48,7 @@ width: 15px; height: 15px; background-size: contain; - background: url("../resources/worklow-transformation-icon.png") no-repeat - center center; + background: url("../resources/worklow-transformation-icon.png") no-repeat center center; float: left; } @@ -60,8 +58,7 @@ width: 15px; height: 15px; background-size: contain; - background: url("../resources/workflow-deployment-icon.png") no-repeat center - center; + background: url("../resources/workflow-deployment-icon.png") no-repeat center center; float: left; } @@ -75,17 +72,6 @@ float: left; } -.qwm .app-icon-service-deployment:before { - content: ""; - width: 15px; - height: 15px; - background-size: contain; - background-image: url("../resources/service-deployment-icon.png"); - background-repeat: no-repeat; - display: inline-block; - float: left; -} - .qwm .indent { margin-left: 5px; } @@ -107,14 +93,13 @@ } .qwm .djs-label { - font-family: "Arial", sans-serif; + font-family: 'Arial', sans-serif; } .qwm .bpmn-icon-hardware-selection-subprocess:before { content: ""; display: block; - background: url("../resources/menu-icon-hardware-selection-subprocess.png") - no-repeat; + background: url("../resources/menu-icon-hardware-selection-subprocess.png") no-repeat; width: 20px; height: 20px; float: left; @@ -124,8 +109,7 @@ .qwm .bpmn-icon-task-quantum-computation:before { content: ""; display: block; - background: url("../resources/menu-icon-quantum-computation-task.png") - no-repeat; + background: url("../resources/menu-icon-quantum-computation-task.png") no-repeat; width: 20px; height: 20px; float: left; @@ -135,8 +119,7 @@ .qwm .bpmn-icon-circuit-loading:before { content: ""; display: block; - background: url("../resources/menu-icon-quantum-circuit-loading-task.png") - no-repeat; + background: url("../resources/menu-icon-quantum-circuit-loading-task.png") no-repeat; width: 20px; height: 20px; float: left; @@ -186,8 +169,7 @@ .qwm .bpmn-icon-parameter-optimization:before { content: ""; display: block; - background: url("../resources/menu-icon-parameter-optimization-task.png") - no-repeat; + background: url("../resources/menu-icon-parameter-optimization-task.png") no-repeat; width: 20px; height: 20px; float: left; @@ -207,8 +189,7 @@ .qwm .bpmn-icon-variational-quantum-algorithm:before { content: ""; display: block; - background: url("../resources/menu-icon-variational-quantum-algorithm-task.png") - no-repeat; + background: url("../resources/menu-icon-variational-quantum-algorithm-task.png") no-repeat; width: 20px; height: 20px; float: left; @@ -273,16 +254,17 @@ padding-left: 20px; } -.qwm .rewrite-failed-button:disabled { +.qwm .rewrite-failed-button:disabled{ background-color: #f44336; color: #000000; } -.qwm .rewrite-successful-button:disabled { +.qwm .rewrite-successful-button:disabled{ background-color: #008000; color: #000000; } + .qwm .quantme-tasks-icon-cutting:before { content: ""; width: 25px; @@ -293,6 +275,7 @@ display: inline-block; } + .qwm .quantme-tasks-icon-variational:before { content: ""; width: 20px; @@ -467,16 +450,10 @@ margin-top: 5px; } -.djs-popup-results - .djs-popup-group - .entry[data-id="replace-with-hardware-selection-subprocess"] - .djs-popup-label { +.djs-popup-results .djs-popup-group .entry[data-id="replace-with-hardware-selection-subprocess"] .djs-popup-label { margin-left: 10px; /* Adjust the value as per your requirements */ } -.djs-popup-results - .djs-popup-group - .entry[data-id="replace-by-more-options"] - .djs-popup-label { +.djs-popup-results .djs-popup-group .entry[data-id="replace-by-more-options"] .djs-popup-label { margin-top: -1px; /* Adjust the value as per your requirements */ -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/ui/QuantMEPluginButton.js b/components/bpmn-q/modeler-component/extensions/quantme/ui/QuantMEPluginButton.js index eca49b86..42b15ce5 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/ui/QuantMEPluginButton.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/ui/QuantMEPluginButton.js @@ -1,45 +1,34 @@ -import React from "react"; +import React from 'react'; import AdaptationPlugin from "./adaptation/AdaptationPlugin"; import QuantMEController from "./control/QuantMEController"; import UpdateDataObjectConfigurationsButton from "../configurations/UpdateDataObjectConfigurationsButton"; -import DeploymentPlugin from "./deployment/services/DeploymentPlugin"; import ExtensibleButton from "../../../editor/ui/ExtensibleButton"; import NotificationHandler from "../../../editor/ui/notifications/NotificationHandler"; -import { updateQRMs } from "../qrm-manager"; +import {updateQRMs} from "../qrm-manager"; export default function QuantMEPluginButton() { + // trigger initial QRM update - updateQRMs() - .then((response) => { - console.log("Update of QRMs completed: ", response); - NotificationHandler.getInstance().displayNotification({ - type: "info", - title: "Successfully updated QRMs", - content: - "Loaded " + response.length + " QRMs from configured repository!", - duration: 4000, - }); - }) - .catch((e) => { - NotificationHandler.getInstance().displayNotification({ - type: "warning", - title: "Unable to load QRMs", - content: e.toString(), - duration: 20000, - }); + updateQRMs().then(response => { + console.log('Update of QRMs completed: ', response); + NotificationHandler.getInstance().displayNotification({ + type: 'info', + title: 'Successfully updated QRMs', + content: 'Loaded ' + response.length + ' QRMs from configured repository!', + duration: 4000 + }); + }).catch(e => { + NotificationHandler.getInstance().displayNotification({ + type: 'warning', + title: 'Unable to load QRMs', + content: e.toString(), + duration: 20000 }); + }); - return ( - , - , - , - , - ]} - title="QuantME" - styleClass="quantme-logo" - description="Show buttons of the QuantME plugin" - /> - ); -} + return , , ]} + title="QuantME" + styleClass="quantme-logo" + description="Show buttons of the QuantME plugin"/>; +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/ui/control/QuantMEController.js b/components/bpmn-q/modeler-component/extensions/quantme/ui/control/QuantMEController.js index c0dfeacd..0ee97ec5 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/ui/control/QuantMEController.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/ui/control/QuantMEController.js @@ -10,296 +10,86 @@ */ /* eslint-disable no-unused-vars*/ -import React, { PureComponent } from "react"; +import React, {PureComponent} from 'react'; -import { startQuantmeReplacementProcess } from "../../replacement/QuantMETransformator"; -import { configureBasedOnHardwareSelection } from "../../replacement/hardware-selection/QuantMEHardwareSelectionHandler"; -import { getServiceTasksToDeploy } from "../../deployment/DeploymentUtils"; -import { - createServiceInstance, - uploadCSARToContainer, -} from "../../deployment/OpenTOSCAUtils"; -import { bindUsingPull, bindUsingPush } from "../../deployment/BindingUtils"; -import { - createTempModelerFromXml, - getModeler, -} from "../../../../editor/ModelerHandler"; +import {startQuantmeReplacementProcess} from '../../replacement/QuantMETransformator'; +import {getModeler} from "../../../../editor/ModelerHandler"; import NotificationHandler from "../../../../editor/ui/notifications/NotificationHandler"; -import { getQRMs, updateQRMs } from "../../qrm-manager"; -import { getXml } from "../../../../editor/util/IoUtilities"; +import {getQRMs, updateQRMs} from "../../qrm-manager"; import config from "../../framework-config/config"; -import { getRootProcess } from "../../../../editor/util/ModellingUtilities"; /** * React component which contains a button which updates the QRMs by reloading them from the sepcified GitHub repository. */ export default class QuantMEController extends PureComponent { + constructor(props) { super(props); } componentDidMount() { + this.modeler = getModeler(); const self = this; // register actions to enable invocation over the menu and the API - this.editorActions = this.modeler.get("editorActions"); + this.editorActions = this.modeler.get('editorActions'); if (!this.modeler.config) { this.modeler.config = config; } - if (!this.editorActions._actions.hasOwnProperty("transformWorkflow")) { + if (!this.editorActions._actions.hasOwnProperty('transformWorkflow')) { // transform the workflow passed through the API to a native workflow this.editorActions.register({ transformWorkflow: async function (params) { - console.log("Transforming workflow posted through API!"); + console.log('Transforming workflow posted through API!'); let currentQRMs = getQRMs(); - let result = await startQuantmeReplacementProcess( - params.xml, - currentQRMs, - { - nisqAnalyzerEndpoint: self.modeler.config.nisqAnalyzerEndpoint, - transformationFrameworkEndpoint: - self.modeler.config.transformationFrameworkEndpoint, - camundaEndpoint: self.modeler.config.camundaEndpoint, - } - ); - - // return result to API - self.api.sendResult(params.returnPath, params.id, { - status: result.status, - xml: result.xml, - }); - }, - }); - } - - if ( - !this.editorActions._actions.hasOwnProperty("transformAndDeployWorkflow") - ) { - // transform and deploy the workflow for the dynamic hardware selection - this.editorActions.register({ - transformAndDeployWorkflow: async function (params) { - console.log( - "Transforming and deploying workflow for hardware selection!" - ); - let currentQRMs = getQRMs(); - - // configure the workflow fragment with the given parameters - console.log( - 'Configuring workflow to transform using provider "%s", QPU "%s", and circuit language "%s"!', - params.provider, - params.qpu, - params.circuitLanguage - ); - let configurationResult = await configureBasedOnHardwareSelection( - params.xml, - params.provider, - params.qpu, - params.circuitLanguage - ); - - // forward error to API if configuration fails - if (configurationResult.status === "failed") { - console.log( - "Configuration of given workflow fragment and parameters failed!" - ); - self.api.sendResult(params.returnPath, params.id, { - status: configurationResult.status, - xml: configurationResult.xml, - }); - return; - } - - // transform to native BPMN - let result = await startQuantmeReplacementProcess( - configurationResult.xml, - currentQRMs, + let result = await startQuantmeReplacementProcess(params.xml, currentQRMs, { nisqAnalyzerEndpoint: self.modeler.config.nisqAnalyzerEndpoint, - transformationFrameworkEndpoint: - self.modeler.config.transformationFrameworkEndpoint, - camundaEndpoint: self.modeler.config.camundaEndpoint, - } - ); - if (result.status === "failed") { - console.log( - "Transformation process failed with cause: ", - result.cause - ); - self.api.sendResult(params.returnPath, params.id, { - status: "failed", + transformationFrameworkEndpoint: self.modeler.config.transformationFrameworkEndpoint, + camundaEndpoint: self.modeler.config.camundaEndpoint }); - return; - } - - // get all ServiceTasks that require a service deployment - let modeler = await createTempModelerFromXml(result.xml); - let csarsToDeploy = getServiceTasksToDeploy( - getRootProcess(modeler.getDefinitions()) - ); - console.log( - "Found %i CSARs associated with ServiceTasks: ", - csarsToDeploy.length, - csarsToDeploy - ); - - // upload the CSARs to the OpenTOSCA Container - for (let i = 0; i < csarsToDeploy.length; i++) { - let csar = csarsToDeploy[i]; - let uploadResult = await uploadCSARToContainer( - config.opentoscaEndpoint, - csar.csarName, - csar.url, - config.wineryEndpoint - ); - console.log( - "Uploaded CSAR '%s' to OpenTOSCA container with result: ", - csar.csarName, - uploadResult - ); - - // abort if upload is not successful - if (uploadResult.success === false) { - self.api.sendResult(params.returnPath, params.id, { - status: "failed", - }); - return; - } - csar.buildPlanUrl = uploadResult.url; - csar.inputParameters = uploadResult.inputParameters; - - // create a service instance of the CSAR - console.log( - "Successfully uploaded CSAR to OpenTOSCA Container. Creating service instance..." - ); - let instanceCreationResponse = await createServiceInstance( - csar, - config.camundaEndpoint - ); - console.log( - "Creation of service instance of CSAR '%s' returned result: ", - csar.csarName, - instanceCreationResponse - ); - - // bind the service instance using the specified binding pattern - let serviceTaskIds = csar.serviceTaskIds; - for (let j = 0; j < serviceTaskIds.length; j++) { - let bindingResponse = undefined; - if (csar.type === "pull") { - bindingResponse = bindUsingPull( - instanceCreationResponse.topicName, - serviceTaskIds[j], - modeler.get("elementRegistry"), - modeler.get("modeling") - ); - } else if (csar.type === "push") { - bindingResponse = bindUsingPush( - csar, - serviceTaskIds[j], - modeler.get("elementRegistry") - ); - } - if ( - bindingResponse === undefined || - bindingResponse.success === false - ) { - console.error( - "Failed to bind service instance to ServiceTask with Id: ", - serviceTaskIds[j] - ); - self.api.sendResult(params.returnPath, params.id, { - status: "failed", - }); - return; - } - } - } - console.log( - "Successfully deployed and bound all required service instances!" - ); - - // deploy the transformed and bound workflow to the Camunda engine - const rootElement = getRootProcess(modeler.getDefinitions()); - let boundWorkflowXml = await getXml(modeler); - let workflowDeploymentResult = await self.backend.send( - "deployment:deploy-workflow", - rootElement.id, - boundWorkflowXml, - {} - ); - if ( - workflowDeploymentResult === undefined || - workflowDeploymentResult.status !== "deployed" - ) { - console.error( - "Failed to deploy workflow: ", - workflowDeploymentResult - ); - self.api.sendResult(params.returnPath, params.id, { - status: "failed", - }); - return; - } - - // return result to the API - console.log("Workflow deployment successfully. Returning to API..."); - self.api.sendResult(params.returnPath, params.id, { - status: workflowDeploymentResult.status, - deployedProcessDefinition: - workflowDeploymentResult.deployedProcessDefinition, - xml: boundWorkflowXml, - }); - }, + // return result to API + self.api.sendResult(params.returnPath, params.id, {status: result.status, xml: result.xml}); + } }); } } updateQRMs() { NotificationHandler.getInstance().displayNotification({ - type: "info", - title: "QRMs update triggered", - content: "Triggered QRM update from configured repository!", - duration: 4000, + type: 'info', + title: 'QRMs update triggered', + content: 'Triggered QRM update from configured repository!', + duration: 4000 }); - updateQRMs() - .then((response) => { - console.log("Update of QRMs completed: ", response); - NotificationHandler.getInstance().displayNotification({ - type: "info", - title: "Successfully updated QRMs", - content: - "Loaded " + response.length + " QRMs from configured repository!", - duration: 4000, - }); - }) - .catch((e) => { - NotificationHandler.getInstance().displayNotification({ - type: "warning", - title: "Unable to load QRMs", - content: e.toString(), - duration: 20000, - }); + updateQRMs().then(response => { + console.log('Update of QRMs completed: ', response); + NotificationHandler.getInstance().displayNotification({ + type: 'info', + title: 'Successfully updated QRMs', + content: 'Loaded ' + response.length + ' QRMs from configured repository!', + duration: 4000 + }); + }).catch(e => { + NotificationHandler.getInstance().displayNotification({ + type: 'warning', + title: 'Unable to load QRMs', + content: e.toString(), + duration: 20000 }); + }); } render() { - return ( -
- -
- ); + return
+ +
; } -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/extensions/quantme/utilities/ImplementationTypeHelperExtension.js b/components/bpmn-q/modeler-component/extensions/quantme/utilities/ImplementationTypeHelperExtension.js index 2a0176f0..38e2e82b 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/utilities/ImplementationTypeHelperExtension.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/utilities/ImplementationTypeHelperExtension.js @@ -14,65 +14,64 @@ import { getServiceTaskLikeBusinessObject, isDmnCapable, isExternalCapable, - isServiceTaskLike, + isServiceTaskLike } from "../../../editor/util/camunda-utils/ImplementationTypeUtils"; -import { getExtensionElementsList } from "../../../editor/util/camunda-utils/ExtensionElementsUtil"; +import {getExtensionElementsList} from "../../../editor/util/camunda-utils/ExtensionElementsUtil"; export function getImplementationType(element) { - const businessObject = + + const businessObject = ( getListenerBusinessObject(element) || - getServiceTaskLikeBusinessObject(element); + getServiceTaskLikeBusinessObject(element) + ); if (!businessObject) { return; } if (isDmnCapable(businessObject)) { - const decisionRef = businessObject.get("camunda:decisionRef"); - if (typeof decisionRef !== "undefined") { - return "dmn"; + const decisionRef = businessObject.get('camunda:decisionRef'); + if (typeof decisionRef !== 'undefined') { + return 'dmn'; } } if (isServiceTaskLike(businessObject)) { - const connectors = getExtensionElementsList( - businessObject, - "camunda:Connector" - ); + const connectors = getExtensionElementsList(businessObject, 'camunda:Connector'); if (connectors.length) { - return "connector"; + return 'connector'; } } if (isExternalCapable(businessObject)) { - const type = businessObject.get("camunda:type"); - if (type === "external") { - return "external"; + const type = businessObject.get('camunda:type'); + if (type === 'external') { + return 'external'; } } - const cls = businessObject.get("camunda:class"); - if (typeof cls !== "undefined") { - return "class"; + const cls = businessObject.get('camunda:class'); + if (typeof cls !== 'undefined') { + return 'class'; } - const expression = businessObject.get("camunda:expression"); - if (typeof expression !== "undefined") { - return "expression"; + const expression = businessObject.get('camunda:expression'); + if (typeof expression !== 'undefined') { + return 'expression'; } - const delegateExpression = businessObject.get("camunda:delegateExpression"); - if (typeof delegateExpression !== "undefined") { - return "delegateExpression"; + const delegateExpression = businessObject.get('camunda:delegateExpression'); + if (typeof delegateExpression !== 'undefined') { + return 'delegateExpression'; } - const deploymentModelUrl = businessObject.get("quantme:deploymentModelUrl"); - if (typeof deploymentModelUrl !== "undefined") { - return "deploymentModel"; + const deploymentModelUrl = businessObject.get('opentosca:deploymentModelUrl'); + if (typeof deploymentModelUrl !== 'undefined') { + return 'deploymentModel'; } - const script = businessObject.get("script"); - if (typeof script !== "undefined") { - return "script"; + const script = businessObject.get('script'); + if (typeof script !== 'undefined') { + return 'script'; } -} +} \ No newline at end of file diff --git a/components/bpmn-q/modeler-component/modeler.css b/components/bpmn-q/modeler-component/modeler.css index b80f188a..08231910 100644 --- a/components/bpmn-q/modeler-component/modeler.css +++ b/components/bpmn-q/modeler-component/modeler.css @@ -1,16 +1,9 @@ -.resize { +.qwm .resize { position: absolute; bottom: 45px; right: 0; } -.xml-viewer-button { - position: absolute; - bottom: 45px; - left: 20px; - z-index: 2; -} - .ace_editor.ace-tm { width: 99%; top: 0%; @@ -31,15 +24,15 @@ cursor: w-resize; } -body { - overflow-y: hidden; -} - #editor_wrap { position: relative; border-bottom: 1px solid #222222; } +.qwm-body { + overflow-y: hidden; +} + #editor_dragbar { position: absolute; top: 200; @@ -51,4 +44,4 @@ body { cursor: row-resize; opacity: 1; z-index: 3; -} +} \ No newline at end of file diff --git a/components/bpmn-q/package-lock.json b/components/bpmn-q/package-lock.json index 3c0f78cf..15ff63e6 100644 --- a/components/bpmn-q/package-lock.json +++ b/components/bpmn-q/package-lock.json @@ -65,7 +65,6 @@ "browserify-css": "^0.15.0", "canvg-browser": "^1.0.0", "chai": "^4.3.7", - "chai-xml": "^0.4.0", "clean-webpack-plugin": "^4.0.0", "client": "file:client", "copy-webpack-plugin": "^4.6.0", @@ -4721,43 +4720,6 @@ "node": ">=4" } }, - "node_modules/chai-xml": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chai-xml/-/chai-xml-0.4.0.tgz", - "integrity": "sha512-VjFPW64Hcp9CuuZbAC26cBWi+DPhyWOW8yxNpfQX3W+jQLPJxN/sm5FAaW+FOKTzsNeIFQpt5yhGbZA5s/pEyg==", - "dev": true, - "dependencies": { - "xml2js": "^0.4.23" - }, - "engines": { - "node": ">= 0.8.0" - }, - "peerDependencies": { - "chai": ">=1.10.0 " - } - }, - "node_modules/chai-xml/node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/chai-xml/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -19900,33 +19862,6 @@ "type-detect": "^4.0.5" } }, - "chai-xml": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chai-xml/-/chai-xml-0.4.0.tgz", - "integrity": "sha512-VjFPW64Hcp9CuuZbAC26cBWi+DPhyWOW8yxNpfQX3W+jQLPJxN/sm5FAaW+FOKTzsNeIFQpt5yhGbZA5s/pEyg==", - "dev": true, - "requires": { - "xml2js": "^0.4.23" - }, - "dependencies": { - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - } - }, - "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true - } - } - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", diff --git a/components/bpmn-q/package.json b/components/bpmn-q/package.json index 00e0fc06..1341bdae 100644 --- a/components/bpmn-q/package.json +++ b/components/bpmn-q/package.json @@ -81,7 +81,6 @@ "browserify-css": "^0.15.0", "canvg-browser": "^1.0.0", "chai": "^4.3.7", - "chai-xml": "^0.4.0", "clean-webpack-plugin": "^4.0.0", "client": "file:client", "copy-webpack-plugin": "^4.6.0", diff --git a/components/bpmn-q/public/index.html b/components/bpmn-q/public/index.html index f6b5840a..93057766 100644 --- a/components/bpmn-q/public/index.html +++ b/components/bpmn-q/public/index.html @@ -1,221 +1,211 @@ - - - - - Workflow Modeler - + + + + + Workflow Modeler + - -
- -
- - - - - - + + + + - - + modelerComponent.pluginConfigs = pluginConfigs; + + + \ No newline at end of file diff --git a/components/bpmn-q/test/tests/editor/plugin.spec.js b/components/bpmn-q/test/tests/editor/plugin.spec.js index 2c002865..059d46e3 100644 --- a/components/bpmn-q/test/tests/editor/plugin.spec.js +++ b/components/bpmn-q/test/tests/editor/plugin.spec.js @@ -1,53 +1,46 @@ -import { createTempModeler } from "../../../modeler-component/editor/ModelerHandler"; -import { expect } from "chai"; +import {createTempModeler} from '../../../modeler-component/editor/ModelerHandler'; +import {expect} from 'chai'; import { getActivePlugins, - getAdditionalModules, - getConfigTabs, - getModdleExtension, - getPluginButtons, - getStyles, - getTransformationButtons, -} from "../../../modeler-component/editor/plugin/PluginHandler"; + getAdditionalModules, getConfigTabs, getModdleExtension, + getPluginButtons, getStyles, getTransformationButtons +} from '../../../modeler-component/editor/plugin/PluginHandler'; import { getAllConfigs, getPluginConfig, - setPluginConfig, -} from "../../../modeler-component/editor/plugin/PluginConfigHandler"; + setPluginConfig +} from '../../../modeler-component/editor/plugin/PluginConfigHandler'; -describe("Test plugins", function () { - describe("Test PluginHandler", function () { - describe("Test getActivePlugins()", function () { - it("Should find no active plugin", function () { +describe('Test plugins', function () { + + describe('Test PluginHandler', function () { + + describe('Test getActivePlugins()', function () { + + it('Should find no active plugin', function () { setPluginConfig([]); createTempModeler(); expect(getActivePlugins().length).to.equal(0); }); - it("Should find 3 active plugins", function () { - setPluginConfig([ - { name: "dataflow" }, - { name: "quantme" }, - { name: "planqk" }, - ]); + it('Should find 4 active plugins', function () { + setPluginConfig([{name: 'dataflow'}, {name: 'quantme'}, {name: 'opentosca'}, {name: 'planqk'}]); const plugins = getActivePlugins(); - expect(plugins.length).to.equal(3); - expect(plugins[0].name).to.equal("dataflow"); - expect(plugins[1].name).to.equal("quantme"); - expect(plugins[2].name).to.equal("planqk"); + expect(plugins.length).to.equal(4); + expect(plugins[0].name).to.equal('dataflow'); + expect(plugins[1].name).to.equal('quantme'); + expect(plugins[2].name).to.equal('opentosca'); + expect(plugins[3].name).to.equal('planqk'); }); }); - describe("Test getter for plugin attributes", function () { - it("Should get correct plugin entries for active plugins", function () { - setPluginConfig([ - { name: "dataflow" }, - { name: "quantme" }, - { name: "planqk" }, - ]); + describe('Test getter for plugin attributes', function () { + + it('Should get correct plugin entries for active plugins', function () { + setPluginConfig([{name: 'dataflow'}, {name: 'quantme'}, {name: 'opentosca'}, {name: 'planqk'}]); const modules = getAdditionalModules(); const extensions = getModdleExtension(); @@ -56,59 +49,58 @@ describe("Test plugins", function () { const tabs = getConfigTabs(); const styles = getStyles(); - expect(modules.length).to.equal(3); - expect(extensions["dataflow"]).to.not.be.undefined; - expect(extensions["quantme"]).to.not.be.undefined; - expect(extensions["planqk"]).to.not.be.undefined; - expect(transfButtons.length).to.equal(3); - expect(buttons.length).to.equal(2); - expect(tabs.length).to.equal(4); - expect(styles.length).to.equal(3); + expect(modules.length).to.equal(4); + expect(extensions['dataflow']).to.not.be.undefined; + expect(extensions['quantme']).to.not.be.undefined; + expect(extensions['opentosca']).to.not.be.undefined; + expect(extensions['planqk']).to.not.be.undefined; }); }); }); - describe("Test PluginConfigHandler", function () { + describe('Test PluginConfigHandler', function () { const examplePluginConfig = [ { - name: "plugin1", + name: 'plugin1', config: { - config1: "alpha", - config2: "beta", + config1: 'alpha', + config2: 'beta', configArray: [1, 2, 3], }, }, { - name: "plugin2", + name: 'plugin2', config: { - endpointURL: "http://example-domain.com/api", - }, + endpointURL: 'http://example-domain.com/api', + } }, { - name: "notConfiguredPlugin", - }, + name: 'notConfiguredPlugin', + } ]; - describe("Test setPluginConfig()", function () { - it("Should set all three plugins", function () { + describe('Test setPluginConfig()', function () { + + it('Should set all three plugins', function () { setPluginConfig(examplePluginConfig); const pluginList = getAllConfigs(); expect(pluginList.length).to.equal(3); - expect(pluginList[0].name).to.equal("plugin1"); - expect(pluginList[1].name).to.equal("plugin2"); - expect(pluginList[2].name).to.equal("notConfiguredPlugin"); + expect(pluginList[0].name).to.equal('plugin1'); + expect(pluginList[1].name).to.equal('plugin2'); + expect(pluginList[2].name).to.equal('notConfiguredPlugin'); }); - it("Should set empty pluginConfig", function () { + it('Should set empty pluginConfig', function () { setPluginConfig([]); const pluginList = getAllConfigs(); expect(pluginList.length).to.equal(0); }); - it("Should set empty pluginConfig for undefined config", function () { + + it('Should set empty pluginConfig for undefined config', function () { setPluginConfig(undefined); const pluginList = getAllConfigs(); @@ -116,38 +108,39 @@ describe("Test plugins", function () { }); }); - describe("Test getPluginConfig()", function () { - it("Should set plugin config for plugin1 and plugin2", function () { + describe('Test getPluginConfig()', function () { + it('Should set plugin config for plugin1 and plugin2', function () { setPluginConfig(examplePluginConfig); - const plugin1Config = getPluginConfig("plugin1"); + const plugin1Config = getPluginConfig('plugin1'); - expect(plugin1Config.config1).to.equal("alpha"); - expect(plugin1Config.config2).to.equal("beta"); + expect(plugin1Config.config1).to.equal('alpha'); + expect(plugin1Config.config2).to.equal('beta'); expect(plugin1Config.configArray).to.deep.equal([1, 2, 3]); - const plugin2Config = getPluginConfig("plugin2"); - expect(plugin2Config.endpointURL).to.equal( - examplePluginConfig[1].config.endpointURL - ); + const plugin2Config = getPluginConfig('plugin2'); + + expect(plugin2Config.endpointURL).to.equal(examplePluginConfig[1].config.endpointURL); }); - it("Should return empty object for unknown plugin", function () { + it('Should return empty object for unknown plugin', function () { + setPluginConfig(examplePluginConfig); - const emptyConfig = getPluginConfig("unknownPlugin"); + const emptyConfig = getPluginConfig('unknownPlugin'); expect(emptyConfig).to.deep.equal({}); }); - it("Should return empty object for defined but not configured plugin", function () { + it('Should return empty object for defined but not configured plugin', function () { + setPluginConfig(examplePluginConfig); - const emptyConfig = getPluginConfig("notConfiguredPlugin"); + const emptyConfig = getPluginConfig('notConfiguredPlugin'); expect(emptyConfig).to.deep.equal({}); }); }); }); -}); +}); \ No newline at end of file diff --git a/components/bpmn-q/test/tests/helpers/DiagramHelper.js b/components/bpmn-q/test/tests/helpers/DiagramHelper.js index 3af80c0f..aead3542 100644 --- a/components/bpmn-q/test/tests/helpers/DiagramHelper.js +++ b/components/bpmn-q/test/tests/helpers/DiagramHelper.js @@ -1,79 +1,77 @@ -const validPlanqkDiagram = - '\n' + +const validPlanqkDiagram = '\n' + '\n' + ' \n' + ' \n' + - " Flow_11l0uo0\n" + - " \n" + + ' Flow_11l0uo0\n' + + ' \n' + ' \n' + ' \n' + - " Flow_11l0uo0\n" + - " Flow_0k7wb56\n" + + ' Flow_11l0uo0\n' + + ' Flow_0k7wb56\n' + ' \n' + ' \n' + - " DataPool_049grpp\n" + - " Property_1y4jr3x\n" + - " \n" + - " \n" + + ' DataPool_049grpp\n' + + ' Property_1y4jr3x\n' + + ' \n' + + ' \n' + ' \n' + - " Flow_0k7wb56\n" + - " \n" + + ' Flow_0k7wb56\n' + + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + - " \n" + - " \n" + + ' \n' + + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + - " \n" + - " \n" + + ' \n' + + ' \n' + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + - " \n" + - " \n" + - " \n" + - "\n"; + ' \n' + + ' \n' + + ' \n' + + '\n'; -const transformedValidPlanqkDiagram = - '\n' + +const transformedValidPlanqkDiagram = '\n' + '\n' + ' \n' + - " \n" + + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + - " Flow_11l0uo0\n" + - " \n" + + ' Flow_11l0uo0\n' + + ' \n' + ' \n' + ' \n' + - " Flow_0k7wb56\n" + - " \n" + + ' Flow_0k7wb56\n' + + ' \n' + ' \n' + ' \n' + - " \n" + - " \n" + + ' \n' + + ' \n' + ' {}\n' + ' {}\n' + ' \n' + @@ -83,119 +81,119 @@ const transformedValidPlanqkDiagram = ' app1ConsumerSecret\n' + ' app1ConsumerKey\n' + ' ${result}\n' + - " \n" + - " \n" + - " Flow_11l0uo0\n" + - " Flow_0k7wb56\n" + + ' \n' + + ' \n' + + ' Flow_11l0uo0\n' + + ' Flow_0k7wb56\n' + ' \n' + ' \n' + - " DataPool_049grpp\n" + - " Property_1q2avq2\n" + - " \n" + + ' DataPool_049grpp\n' + + ' Property_1q2avq2\n' + + ' \n' + ' \n' + - " Flow_1dm9t5d\n" + - " \n" + + ' Flow_1dm9t5d\n' + + ' \n' + ' \n' + - " Flow_1382lgm\n" + - " Flow_0482sfu\n" + - " Flow_0nfkrrj\n" + + ' Flow_1382lgm\n' + + ' Flow_0482sfu\n' + + ' Flow_0nfkrrj\n' + ' \n' + ' R/PT5S\n' + - " \n" + - " \n" + + ' \n' + + ' \n' + ' \n' + - " Flow_1s3f52r\n" + - " Flow_04vby0l\n" + - " Flow_0482sfu\n" + - " Flow_1skraj1\n" + - " \n" + + ' Flow_1s3f52r\n' + + ' Flow_04vby0l\n' + + ' Flow_0482sfu\n' + + ' Flow_1skraj1\n' + + ' \n' + ' \n' + - " Flow_04vby0l\n" + - " Flow_1gu42un\n" + - " \n" + + ' Flow_04vby0l\n' + + ' Flow_1gu42un\n' + + ' \n' + ' \n' + - " Flow_1dm9t5d\n" + - " Flow_1382lgm\n" + - " \n" + + ' Flow_1dm9t5d\n' + + ' Flow_1382lgm\n' + + ' \n' + ' \n' + - " Flow_0nfkrrj\n" + - " Flow_1s3f52r\n" + - " \n" + + ' Flow_0nfkrrj\n' + + ' Flow_1s3f52r\n' + + ' \n' + ' \n' + - " Flow_1gu42un\n" + - " \n" + + ' Flow_1gu42un\n' + + ' \n' + ' \n' + - " Flow_1skraj1\n" + + ' Flow_1skraj1\n' + ' \n' + - " \n" + + ' \n' + ' \n' + - " ${executionState=='SUCCEEDED'}\n" + - " \n" + + ' ${executionState==\'SUCCEEDED\'}\n' + + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + - " ${executionState=='PENDING'}\n" + - " \n" + + ' ${executionState==\'PENDING\'}\n' + + ' \n' + ' \n' + ' \n' + ' \n' + - " ${executionState=='FAILED' || executionState=='UNKNOWN'}\n" + - " \n" + - " \n" + + ' ${executionState==\'FAILED\' || executionState==\'UNKNOWN\'}\n' + + ' \n' + + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + - " \n" + - " \n" + + ' \n' + + ' \n' + ' \n' + ' \n' + - " \n" + - " \n" + + ' \n' + + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + - " \n" + - " \n" + + ' \n' + + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + - " \n" + - " \n" + + ' \n' + + ' \n' + ' \n' + ' \n' + - " \n" + - " \n" + + ' \n' + + ' \n' + ' \n' + ' \n' + - " \n" + - " \n" + + ' \n' + + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + - " \n" + - " \n" + + ' \n' + + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + @@ -203,7 +201,7 @@ const transformedValidPlanqkDiagram = ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + @@ -211,7 +209,7 @@ const transformedValidPlanqkDiagram = ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + @@ -219,7 +217,7 @@ const transformedValidPlanqkDiagram = ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + @@ -227,7 +225,7 @@ const transformedValidPlanqkDiagram = ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + @@ -235,7 +233,7 @@ const transformedValidPlanqkDiagram = ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + @@ -243,7 +241,7 @@ const transformedValidPlanqkDiagram = ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + @@ -251,7 +249,7 @@ const transformedValidPlanqkDiagram = ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + @@ -259,7 +257,7 @@ const transformedValidPlanqkDiagram = ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + @@ -267,29 +265,26 @@ const transformedValidPlanqkDiagram = ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + - " \n" + + ' \n' + ' \n' + ' \n' + ' \n' + - " \n" + - " \n" + - " \n" + + ' \n' + + ' \n' + + ' \n' + ' \n' + ' \n' + - " \n" + - "\n"; + ' \n' + + '\n'; -export const validQuantMEDiagram = - ' SequenceFlow_0kum1kc SequenceFlow_0kum1kc SequenceFlow_0gw15u7 SequenceFlow_0gw15u7 SequenceFlow_00gjpgx SequenceFlow_0s6m835 SequenceFlow_0fxi83k SequenceFlow_16yvlag SequenceFlow_0591a3g ${clusteringConverged == \'false\'} ${clusteringConverged == \'true\'} SequenceFlow_1wsvjv1 SequenceFlow_0ncbyt5 SequenceFlow_08ni26o SequenceFlow_09l09is SequenceFlow_0ncbyt5 SequenceFlow_0vmb89t ${classificationConverged == \'false\'} ${classificationConverged == \'true\'} SequenceFlow_03zrxe7 SequenceFlow_1csno8e SequenceFlow_1csno8e SequenceFlow_15qw95r SequenceFlow_0fxi83k SequenceFlow_16yvlag SequenceFlow_00gjpgx SequenceFlow_0591a3g SequenceFlow_1wsvjv1 SequenceFlow_08ed0ea SequenceFlow_09l09is SequenceFlow_0vmb89t SequenceFlow_03zrxe7 SequenceFlow_08ni26o SequenceFlow_08ed0ea SequenceFlow_0s6m835 SequenceFlow_15qw95r '; +export const validQuantMEDiagram = ' SequenceFlow_0kum1kc SequenceFlow_0kum1kc SequenceFlow_0gw15u7 SequenceFlow_0gw15u7 SequenceFlow_00gjpgx SequenceFlow_0s6m835 SequenceFlow_0fxi83k SequenceFlow_16yvlag SequenceFlow_0591a3g ${clusteringConverged == \'false\'} ${clusteringConverged == \'true\'} SequenceFlow_1wsvjv1 SequenceFlow_0ncbyt5 SequenceFlow_08ni26o SequenceFlow_09l09is SequenceFlow_0ncbyt5 SequenceFlow_0vmb89t ${classificationConverged == \'false\'} ${classificationConverged == \'true\'} SequenceFlow_03zrxe7 SequenceFlow_1csno8e SequenceFlow_1csno8e SequenceFlow_15qw95r SequenceFlow_0fxi83k SequenceFlow_16yvlag SequenceFlow_00gjpgx SequenceFlow_0591a3g SequenceFlow_1wsvjv1 SequenceFlow_08ed0ea SequenceFlow_09l09is SequenceFlow_0vmb89t SequenceFlow_03zrxe7 SequenceFlow_08ni26o SequenceFlow_08ed0ea SequenceFlow_0s6m835 SequenceFlow_15qw95r '; -export const validDataFlowDiagram = - ' Flow_1wgvxmm Flow_1wr8t0y Flow_1wgvxmm Flow_1wr8t0y DataStoreMap_0louwgh Property_0ohays4 DataMapObject_19we4h2 '; +export const validDataFlowDiagram = ' Flow_1wgvxmm Flow_1wr8t0y Flow_1wgvxmm Flow_1wr8t0y DataStoreMap_0louwgh Property_0ohays4 DataMapObject_19we4h2 '; -export const validQuantMESubprocessDiagram = - ' Flow_036tksd Flow_19sigyl Flow_036tksdFlow_0k45o2vdef packagesUrl = execution.getVariable("packagesUrl"); def packageString = new URL (packagesUrl).getText(); //def packageString = new URL ("https://raw.githubusercontent.com/UST-QuAntiL/QuantME-UseCases/icwe/2023-icwe/data/packages.txt").getText(); def packages = [] def destinations = [] packageString.split("\\n").each { p -> def packageValues = [:] def values = p.split(",") packageValues.put("destination", values[0]) packageValues.put("size", values[1].toInteger()) packageValues.put("deliveryDate", values[2]) packages.add(packageValues) if (!destinations.contains(values[0])){destinations.add(values[0]) } } println(packages); println(destinations) execution.setVariable("destinations", destinations); execution.setVariable("packages", packages); execution.setVariable("nextDestinations", [destinations.getClass().newInstance(destinations)]); Flow_0k45o2vFlow_1o0job9 def trucksUrl = execution.getVariable("trucksUrl"); def destinations = execution.getVariable("destinations"); def trucksString = new URL (trucksUrl).getText(); def trucks = [] trucksString.split("\\n").each { p -> def truckValues = [:] def values = p.split(",") truckValues.put("driver", values[0]) truckValues.put("capacity", values[1].toInteger()) truckValues.put("location", values[2]) truckValues.put("email", values[3]) trucks.add(truckValues) if (!destinations.contains(values[2])){destinations.add(values[2]) } } execution.setVariable("trucks", trucks); execution.setVariable("allCities", destinations); execution.setVariable("unassignedTrucks", trucks); POST application/jsonapplication/json http://distance-matrix:8101/useCaseDistanceMatrix import groovy.json.JsonBuilderdef allCities = execution.getVariable("allCities"); def aws_token = execution.getVariable("awsToken"); def request = [:]; request.put("towns", allCities); request.put("token", aws_token); requeststring = new JsonBuilder(request).toPrettyString() return requeststring; def resp = connector.getVariable("response");resp = new groovy.json.JsonSlurper().parseText(resp)distanceMatrix= resp.get("distanceMatrix")println(distanceMatrix);return distanceMatrix; def resp = connector.getVariable("response");resp = new groovy.json.JsonSlurper().parseText(resp)durationMatrix= resp.get("durationMatrix")println(durationMatrix);return durationMatrix; http-connectorFlow_1o0job9Flow_0pqisjm Flow_164r496Flow_1uk996u def nextDestinations = execution.getVariable("nextDestinations"); execution.setVariable("currentDestinations", nextDestinations[0].getClass().newInstance(nextDestinations[0])); Flow_1lgu2v0Flow_19sigyl Flow_1uk996uFlow_0ey4t8g def unassignedTrucks = execution.getVariable("unassignedTrucks"); def packages = execution.getVariable("packages"); def currentDestinations = execution.getVariable("currentDestinations"); def maxCapacity = 0; unassignedTrucks.each { truck -> if (truck.get("capacity") > maxCapacity) {maxCapacity = truck.get("capacity"); } } def totalSize = 0; packages.each { p -> if( currentDestinations.contains(p.get("destination"))) {totalSize += p.get("size"); } } execution.setVariable("allFitsInTruck", totalSize < maxCapacity); Flow_08svt2nFlow_1lgu2v0Flow_1mglfkn def unassignedTrucks = execution.getVariable("unassignedTrucks"); def nextDestinations= execution.getVariable("nextDestinations"); return !(unassignedTrucks.size() > 0 && nextDestinations.size() > 0) Flow_1mglfknFlow_0kiekyoFlow_03ixpie def unassignedTrucks = execution.getVariable("unassignedTrucks"); def nextDestinations= execution.getVariable("nextDestinations"); return (unassignedTrucks.size() > 0 && nextDestinations.size() > 0) Flow_0pqisjmFlow_03ixpieFlow_164r496 Flow_0ey4t8gFlow_0wg476aFlow_1d4o5uw ${ execution.getVariable("allFitsInTruck")!= null && execution.getVariable("allFitsInTruck") == "true"} Flow_0wg476aFlow_1k013oeFlow_1m1hkm4 Flow_1d4o5uwFlow_0awxqtpFlow_1k013oe ${ execution.getVariable("allFitsInTruck")== null || execution.getVariable("allFitsInTruck") == "false"} def unassignedTrucks = execution.getVariable("unassignedTrucks"); def currentDestinations = execution.getVariable("currentDestinations"); return unassignedTrucks.size() > 1 && currentDestinations.size() > 1 def unassignedTrucks = execution.getVariable("unassignedTrucks"); def currentDestinations = execution.getVariable("currentDestinations"); return !(unassignedTrucks.size() > 1 && currentDestinations.size() > 1) Flow_0t1933oFlow_08svt2ndef unassignedTrucks = execution.getVariable("unassignedTrucks");def nextDestinations = execution.getVariable("nextDestinations");def currentRoute= execution.getVariable("currentRoute");def allRoutes= execution.getVariable("allRoutes");println(unassignedTrucks[0].get("driver")+" on route" + currentRoute.inspect());if(allRoutes == null){ allRoutes = [];}this_route =[:]this_route.put("route", currentRoute.getClass().newInstance(currentRoute));this_route.put("driver", unassignedTrucks[0].getClass().newInstance(unassignedTrucks[0]));allRoutes.push(this_route);nextDestinations.removeAt(0);unassignedTrucks.removeAt(0);execution.setVariable("allRoutes", allRoutes);execution.setVariable("nextDestinations", nextDestinations);execution.setVariable("unassignedTrucks", unassignedTrucks); Flow_1gu7ma8Flow_0kiekyo def currentDestinations = execution.getVariable("currentDestinations"); def nextDestinations = execution.getVariable("nextDestinations"); def evaluatedCosts = execution.getVariable("evaluatedCosts");println(nextDestinations); evaluatedCosts = evaluatedCosts[0].get("bitstring");println(evaluatedCosts); def cities_with_zero =[]; def cities_with_one =[]; evaluatedCosts.toCharArray().eachWithIndex { c, index -> (c == "0") ? cities_with_zero.push(currentDestinations[index]) : cities_with_one.push(currentDestinations[index]); }if (cities_with_zero.size()==0 || cities_with_one.size()==0) { nextDestinations.push(currentDestinations[0..((int)(currentDestinations.size()/2))-1]); nextDestinations.push(currentDestinations[((int)(currentDestinations.size()/2))..currentDestinations.size()-1]);} else { nextDestinations.push(cities_with_zero); nextDestinations.push(cities_with_one);}println(nextDestinations); nextDestinations.removeAt(0); println(nextDestinations); execution.setVariable("nextDestinations", nextDestinations); Flow_1m1hkm4Flow_0t1933o [1] [1] YOUR_TOKENFlow_12pjp7kFlow_1gu7ma8 Flow_0awxqtpFlow_12pjp7k def allCities = execution.getVariable("allCities"); def currentDestinations = execution.getVariable("currentDestinations"); def distanceMatrix = execution.getVariable("distanceMatrix"); def durationMatrix = execution.getVariable("durationMatrix"); def requiredIndizes = [] for (def i in 0..allCities.size()-1) { if (currentDestinations.contains(allCities[i])){requiredIndizes.push(i) } } def submatrixOfDistanceMatrix = new Integer [requiredIndizes.size()] [requiredIndizes.size()]; def submatrixOfDurationMatrix = new Float [requiredIndizes.size()] [requiredIndizes.size()]; for (def i in 0..requiredIndizes.size()-1) { submatrixOfDistanceMatrix [i][i] = 0; submatrixOfDurationMatrix [i][i] = 0.0; for (def j in i+1..requiredIndizes.size()-1) {if (j < requiredIndizes.size()){submatrixOfDistanceMatrix [i][j] = distanceMatrix[requiredIndizes[i]][requiredIndizes[j]];submatrixOfDistanceMatrix [j][i] = distanceMatrix[requiredIndizes[j]][requiredIndizes[i]];submatrixOfDurationMatrix [j][i] = durationMatrix[requiredIndizes[j]][requiredIndizes[i]];submatrixOfDurationMatrix [j][i] = durationMatrix[requiredIndizes[j]][requiredIndizes[i]];} } }println(submatrixOfDistanceMatrix); // switch between distance and duration depending on reqs execution.setVariable("adjMatrix", submatrixOfDistanceMatrix); '; -export { validPlanqkDiagram, transformedValidPlanqkDiagram }; +export const validQuantMESubprocessDiagram = ' Flow_036tksd Flow_19sigyl Flow_036tksdFlow_0k45o2vdef packagesUrl = execution.getVariable("packagesUrl"); def packageString = new URL (packagesUrl).getText(); //def packageString = new URL ("https://raw.githubusercontent.com/UST-QuAntiL/QuantME-UseCases/icwe/2023-icwe/data/packages.txt").getText(); def packages = [] def destinations = [] packageString.split("\\n").each { p -> def packageValues = [:] def values = p.split(",") packageValues.put("destination", values[0]) packageValues.put("size", values[1].toInteger()) packageValues.put("deliveryDate", values[2]) packages.add(packageValues) if (!destinations.contains(values[0])){destinations.add(values[0]) } } println(packages); println(destinations) execution.setVariable("destinations", destinations); execution.setVariable("packages", packages); execution.setVariable("nextDestinations", [destinations.getClass().newInstance(destinations)]); Flow_0k45o2vFlow_1o0job9 def trucksUrl = execution.getVariable("trucksUrl"); def destinations = execution.getVariable("destinations"); def trucksString = new URL (trucksUrl).getText(); def trucks = [] trucksString.split("\\n").each { p -> def truckValues = [:] def values = p.split(",") truckValues.put("driver", values[0]) truckValues.put("capacity", values[1].toInteger()) truckValues.put("location", values[2]) truckValues.put("email", values[3]) trucks.add(truckValues) if (!destinations.contains(values[2])){destinations.add(values[2]) } } execution.setVariable("trucks", trucks); execution.setVariable("allCities", destinations); execution.setVariable("unassignedTrucks", trucks); POST application/jsonapplication/json http://distance-matrix:8101/useCaseDistanceMatrix import groovy.json.JsonBuilderdef allCities = execution.getVariable("allCities"); def aws_token = execution.getVariable("awsToken"); def request = [:]; request.put("towns", allCities); request.put("token", aws_token); requeststring = new JsonBuilder(request).toPrettyString() return requeststring; def resp = connector.getVariable("response");resp = new groovy.json.JsonSlurper().parseText(resp)distanceMatrix= resp.get("distanceMatrix")println(distanceMatrix);return distanceMatrix; def resp = connector.getVariable("response");resp = new groovy.json.JsonSlurper().parseText(resp)durationMatrix= resp.get("durationMatrix")println(durationMatrix);return durationMatrix; http-connectorFlow_1o0job9Flow_0pqisjm Flow_164r496Flow_1uk996u def nextDestinations = execution.getVariable("nextDestinations"); execution.setVariable("currentDestinations", nextDestinations[0].getClass().newInstance(nextDestinations[0])); Flow_1lgu2v0Flow_19sigyl Flow_1uk996uFlow_0ey4t8g def unassignedTrucks = execution.getVariable("unassignedTrucks"); def packages = execution.getVariable("packages"); def currentDestinations = execution.getVariable("currentDestinations"); def maxCapacity = 0; unassignedTrucks.each { truck -> if (truck.get("capacity") > maxCapacity) {maxCapacity = truck.get("capacity"); } } def totalSize = 0; packages.each { p -> if( currentDestinations.contains(p.get("destination"))) {totalSize += p.get("size"); } } execution.setVariable("allFitsInTruck", totalSize < maxCapacity); Flow_08svt2nFlow_1lgu2v0Flow_1mglfkn def unassignedTrucks = execution.getVariable("unassignedTrucks"); def nextDestinations= execution.getVariable("nextDestinations"); return !(unassignedTrucks.size() > 0 && nextDestinations.size() > 0) Flow_1mglfknFlow_0kiekyoFlow_03ixpie def unassignedTrucks = execution.getVariable("unassignedTrucks"); def nextDestinations= execution.getVariable("nextDestinations"); return (unassignedTrucks.size() > 0 && nextDestinations.size() > 0) Flow_0pqisjmFlow_03ixpieFlow_164r496 Flow_0ey4t8gFlow_0wg476aFlow_1d4o5uw ${ execution.getVariable("allFitsInTruck")!= null && execution.getVariable("allFitsInTruck") == "true"} Flow_0wg476aFlow_1k013oeFlow_1m1hkm4 Flow_1d4o5uwFlow_0awxqtpFlow_1k013oe ${ execution.getVariable("allFitsInTruck")== null || execution.getVariable("allFitsInTruck") == "false"} def unassignedTrucks = execution.getVariable("unassignedTrucks"); def currentDestinations = execution.getVariable("currentDestinations"); return unassignedTrucks.size() > 1 && currentDestinations.size() > 1 def unassignedTrucks = execution.getVariable("unassignedTrucks"); def currentDestinations = execution.getVariable("currentDestinations"); return !(unassignedTrucks.size() > 1 && currentDestinations.size() > 1) Flow_0t1933oFlow_08svt2ndef unassignedTrucks = execution.getVariable("unassignedTrucks");def nextDestinations = execution.getVariable("nextDestinations");def currentRoute= execution.getVariable("currentRoute");def allRoutes= execution.getVariable("allRoutes");println(unassignedTrucks[0].get("driver")+" on route" + currentRoute.inspect());if(allRoutes == null){ allRoutes = [];}this_route =[:]this_route.put("route", currentRoute.getClass().newInstance(currentRoute));this_route.put("driver", unassignedTrucks[0].getClass().newInstance(unassignedTrucks[0]));allRoutes.push(this_route);nextDestinations.removeAt(0);unassignedTrucks.removeAt(0);execution.setVariable("allRoutes", allRoutes);execution.setVariable("nextDestinations", nextDestinations);execution.setVariable("unassignedTrucks", unassignedTrucks); Flow_1gu7ma8Flow_0kiekyo def currentDestinations = execution.getVariable("currentDestinations"); def nextDestinations = execution.getVariable("nextDestinations"); def evaluatedCosts = execution.getVariable("evaluatedCosts");println(nextDestinations); evaluatedCosts = evaluatedCosts[0].get("bitstring");println(evaluatedCosts); def cities_with_zero =[]; def cities_with_one =[]; evaluatedCosts.toCharArray().eachWithIndex { c, index -> (c == "0") ? cities_with_zero.push(currentDestinations[index]) : cities_with_one.push(currentDestinations[index]); }if (cities_with_zero.size()==0 || cities_with_one.size()==0) { nextDestinations.push(currentDestinations[0..((int)(currentDestinations.size()/2))-1]); nextDestinations.push(currentDestinations[((int)(currentDestinations.size()/2))..currentDestinations.size()-1]);} else { nextDestinations.push(cities_with_zero); nextDestinations.push(cities_with_one);}println(nextDestinations); nextDestinations.removeAt(0); println(nextDestinations); execution.setVariable("nextDestinations", nextDestinations); Flow_1m1hkm4Flow_0t1933o [1] [1] YOUR_TOKENFlow_12pjp7kFlow_1gu7ma8 Flow_0awxqtpFlow_12pjp7k def allCities = execution.getVariable("allCities"); def currentDestinations = execution.getVariable("currentDestinations"); def distanceMatrix = execution.getVariable("distanceMatrix"); def durationMatrix = execution.getVariable("durationMatrix"); def requiredIndizes = [] for (def i in 0..allCities.size()-1) { if (currentDestinations.contains(allCities[i])){requiredIndizes.push(i) } } def submatrixOfDistanceMatrix = new Integer [requiredIndizes.size()] [requiredIndizes.size()]; def submatrixOfDurationMatrix = new Float [requiredIndizes.size()] [requiredIndizes.size()]; for (def i in 0..requiredIndizes.size()-1) { submatrixOfDistanceMatrix [i][i] = 0; submatrixOfDurationMatrix [i][i] = 0.0; for (def j in i+1..requiredIndizes.size()-1) {if (j < requiredIndizes.size()){submatrixOfDistanceMatrix [i][j] = distanceMatrix[requiredIndizes[i]][requiredIndizes[j]];submatrixOfDistanceMatrix [j][i] = distanceMatrix[requiredIndizes[j]][requiredIndizes[i]];submatrixOfDurationMatrix [j][i] = durationMatrix[requiredIndizes[j]][requiredIndizes[i]];submatrixOfDurationMatrix [j][i] = durationMatrix[requiredIndizes[j]][requiredIndizes[i]];} } }println(submatrixOfDistanceMatrix); // switch between distance and duration depending on reqs execution.setVariable("adjMatrix", submatrixOfDistanceMatrix); ' +export {validPlanqkDiagram, transformedValidPlanqkDiagram}; \ No newline at end of file diff --git a/components/bpmn-q/test/tests/opentosca/deployment-model-renderer.spec.js b/components/bpmn-q/test/tests/opentosca/deployment-model-renderer.spec.js new file mode 100644 index 00000000..e458bba0 --- /dev/null +++ b/components/bpmn-q/test/tests/opentosca/deployment-model-renderer.spec.js @@ -0,0 +1,29 @@ +import {createTempModeler} from "../../../modeler-component/editor/ModelerHandler"; +import {setPluginConfig} from "../../../modeler-component/editor/plugin/PluginConfigHandler"; +import {create as svgCreate} from "tiny-svg"; +import {expect} from 'chai'; + +const deploymentModelTopology = { "topNode": { "id": "RealWorld-Application-Backend_Java11-Spring-w1_0", "documentation": [], "any": [], "otherAttributes": { "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "390", "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "112" }, "properties": { "propertyType": "KV", "namespace": "http://www.example.org", "elementName": "Properties", "kvproperties": { "AppName": "", "Port": "", "context_root": "" } }, "type": "{http://opentosca.org/example/applications/nodetypes}RealWorld-Application-Backend_Java11-Spring-w1", "name": "RealWorld-Application-Backend", "minInstances": 1, "maxInstances": "1", "x": "390", "y": "112" }, "nodeTemplates": [ { "id": "MariaDB_10-w1_0", "documentation": [], "any": [], "otherAttributes": { "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "657", "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "450" }, "properties": { "propertyType": "KV", "namespace": "http://opentosca.org/nodetypes/propertiesdefinition/winery", "elementName": "properties", "kvproperties": { "DBName": "", "DBUser": "", "DBPassword": "" } }, "type": "{http://opentosca.org/nodetypes}MariaDB_10-w1", "name": "MariaDB", "minInstances": 1, "maxInstances": "1", "x": "657", "y": "450" }, { "id": "Java_11-w1_0", "documentation": [], "any": [], "otherAttributes": { "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "352", "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "281" }, "properties": { "propertyType": "KV", "namespace": "http://www.example.org", "elementName": "Properties", "kvproperties": { "component_version": "", "admin_credential": "" } }, "type": "{http://opentosca.org/nodetypes}Java_11-w1", "name": "Java", "minInstances": 1, "maxInstances": "1", "x": "352", "y": "281" }, { "id": "Ubuntu-VM_18.04-w1_0", "documentation": [], "any": [], "otherAttributes": { "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "390", "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "619" }, "properties": { "propertyType": "KV", "namespace": "http://opentosca.org/nodetypes", "elementName": "VirtualMachineProperties", "kvproperties": { "instanceRef": "", "VMIP": "", "VMInstanceID": "", "VMType": "", "VMUserName": "", "VMUserPassword": "", "VMPrivateKey": "", "VMPublicKey": "", "VMKeyPairName": "" } }, "type": "{http://opentosca.org/nodetypes}Ubuntu-VM_18.04-w1", "name": "Ubuntu-VM", "minInstances": 1, "maxInstances": "1", "x": "390", "y": "619" }, { "id": "DockerContainer_w1_0", "documentation": [], "any": [], "otherAttributes": { "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "352", "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "450" }, "properties": { "propertyType": "KV", "namespace": "http://opentosca.org/nodetypes/properties", "elementName": "properties", "kvproperties": { "Port": "", "ContainerPort": "", "ContainerID": "", "ContainerIP": "", "ImageID": "", "ContainerMountPath": "", "HostMountFiles": "", "PrivilegedMode": "" } }, "type": "{http://opentosca.org/nodetypes}DockerContainer_w1", "name": "DockerContainer_w1", "minInstances": 1, "maxInstances": "1", "x": "352", "y": "450" }, { "id": "RealWorld-Application-Backend_Java11-Spring-w1_0", "documentation": [], "any": [], "otherAttributes": { "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "390", "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "112" }, "properties": { "propertyType": "KV", "namespace": "http://www.example.org", "elementName": "Properties", "kvproperties": { "AppName": "", "Port": "", "context_root": "" } }, "type": "{http://opentosca.org/example/applications/nodetypes}RealWorld-Application-Backend_Java11-Spring-w1", "name": "RealWorld-Application-Backend", "minInstances": 1, "maxInstances": "1", "x": "390", "y": "112" }, { "id": "AWS_w1_0", "documentation": [], "any": [], "otherAttributes": { "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}x": "390", "{http://www.opentosca.org/winery/extensions/tosca/2013/02/12}y": "788" }, "properties": { "propertyType": "KV", "namespace": "http://opentosca.org/nodetypes", "elementName": "AWS_w1", "kvproperties": { "AWS_ACCESS_KEY_ID": "", "AWS_SECRET_ACCESS_KEY": "", "AWS_REGION": "" } }, "type": "{http://opentosca.org/nodetypes}AWS_w1", "name": "AWS_w1", "minInstances": 1, "maxInstances": "1", "x": "390", "y": "788" } ], "relationshipTemplates": [ { "id": "con_AttachesTo_0", "documentation": [], "any": [], "otherAttributes": {}, "type": "{http://docs.oasis-open.org/tosca/ToscaNormativeTypes/relationshiptypes}AttachesTo", "sourceElement": { "ref": "MariaDB_10-w1_0" }, "targetElement": { "ref": "Ubuntu-VM_18.04-w1_0" }, "name": "AttachesTo" }, { "id": "con_HostedOn_0", "documentation": [], "any": [], "otherAttributes": {}, "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}HostedOn", "sourceElement": { "ref": "DockerContainer_w1_0" }, "targetElement": { "ref": "Ubuntu-VM_18.04-w1_0" }, "name": "HostedOn" }, { "id": "con_HostedOn_1", "documentation": [], "any": [], "otherAttributes": {}, "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}HostedOn", "sourceElement": { "ref": "Java_11-w1_0" }, "targetElement": { "ref": "DockerContainer_w1_0" }, "name": "HostedOn" }, { "id": "con_HostedOn_2", "documentation": [], "any": [], "otherAttributes": {}, "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}HostedOn", "sourceElement": { "ref": "RealWorld-Application-Backend_Java11-Spring-w1_0" }, "targetElement": { "ref": "Java_11-w1_0" }, "name": "HostedOn" }, { "id": "con_HostedOn_3", "documentation": [], "any": [], "otherAttributes": {}, "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}HostedOn", "sourceElement": { "ref": "Ubuntu-VM_18.04-w1_0" }, "targetElement": { "ref": "AWS_w1_0" }, "name": "HostedOn" }, { "id": "con_ConnectsTo_0", "documentation": [], "any": [], "otherAttributes": {}, "properties": { "propertyType": "KV", "namespace": "http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes/propertiesdefinition/winery", "elementName": "properties", "kvproperties": { "ChannelType": "" } }, "type": "{http://docs.oasis-open.org/tosca/ns/2011/12/ToscaBaseTypes}ConnectsTo", "sourceElement": { "ref": "Java_11-w1_0" }, "targetElement": { "ref": "MariaDB_10-w1_0" }, "name": "ConnectsTo" } ]}; + +describe('Test the OpenTosca renderer', function () { + it("Should render deployment model", () => { + setPluginConfig([{ + name: 'opentosca', + config: { + test: 'test', + } + }]); + const modeler = createTempModeler(); + const renderer = modeler.get("openToscaRenderer"); + const loadedDeploymentModelUrl = "moz://a"; + + const parentGfx = svgCreate("g"); + + renderer.showDeploymentModel(parentGfx, { + loadedDeploymentModelUrl, + deploymentModelTopology + }, loadedDeploymentModelUrl); + + expect(parentGfx.innerHTML).to.contain(""); + }); +}); \ No newline at end of file diff --git a/components/bpmn-q/test/tests/opentosca/deployment-utils.spec.js b/components/bpmn-q/test/tests/opentosca/deployment-utils.spec.js new file mode 100644 index 00000000..0ae1cc0a --- /dev/null +++ b/components/bpmn-q/test/tests/opentosca/deployment-utils.spec.js @@ -0,0 +1,32 @@ +import {validQuantMEDiagram} from "../helpers/DiagramHelper"; +import {createTempModelerFromXml} from "../../../modeler-component/editor/ModelerHandler"; +import {getServiceTasksToDeploy} from "../../../modeler-component/extensions/opentosca/deployment/DeploymentUtils"; +import {getRootProcess} from "../../../modeler-component/editor/util/ModellingUtilities"; +import {setPluginConfig} from "../../../modeler-component/editor/plugin/PluginConfigHandler"; +import {expect} from 'chai'; + +describe('Test the CSAR extraction.', function () { + + + it('should get service tasks to deploy from model', async function () { + setPluginConfig([{ + name: 'dataflow', + config: {} + }, { + name: 'quantme', + config: { + test: 'test', + } + }, { + name: 'opentosca', + config: { + test: 'test', + } + }]); + const modeler = await createTempModelerFromXml(validQuantMEDiagram); + let csarsToDeploy = getServiceTasksToDeploy(getRootProcess(modeler.getDefinitions())); + expect(csarsToDeploy.length).to.equal(1); + expect(csarsToDeploy[0].url).to.equal('{{ wineryEndpoint }}/servicetemplates/http%253A%252F%252Fquantil.org%252Fquantme%252Fpull/KMeansInitializerService/?csar'); + expect(csarsToDeploy[0].csarName).to.equal('KMeansInitializerService.csar'); + }); +}); \ No newline at end of file diff --git a/components/bpmn-q/test/tests/opentosca/opentosca-config.spec.js b/components/bpmn-q/test/tests/opentosca/opentosca-config.spec.js new file mode 100644 index 00000000..13133bfb --- /dev/null +++ b/components/bpmn-q/test/tests/opentosca/opentosca-config.spec.js @@ -0,0 +1,32 @@ +import {setPluginConfig} from '../../../modeler-component/editor/plugin/PluginConfigHandler'; +import {expect} from 'chai'; +import * as opentoscaConfig from '../../../modeler-component/extensions/opentosca/framework-config/config-manager'; + +describe('Test OpenTOSCA ConfigManager', function () { + + describe('Test OpenTOSCA endpoint', function () { + + before('Reset OpenTOSCA configuration', function () { + opentoscaConfig.resetConfig(); + }); + + afterEach('Reset OpenTOSCA configuration', function () { + opentoscaConfig.resetConfig(); + }); + + it('Should configure OpenTOSCA endpoints', function () { + setPluginConfig([ + { + name: 'opentosca', + config: { + opentoscaEndpoint: 'http://test:1337/csars', + wineryEndpoint: 'http://test:8093/winery', + } + }] + ); + + expect(opentoscaConfig.getOpenTOSCAEndpoint()).to.equal('http://test:1337/csars'); + expect(opentoscaConfig.getWineryEndpoint()).to.equal('http://test:8093/winery'); + }); + }); +}); diff --git a/components/bpmn-q/test/tests/quantme/quantme-config.spec.js b/components/bpmn-q/test/tests/quantme/quantme-config.spec.js index b5d36817..cbdc4f46 100644 --- a/components/bpmn-q/test/tests/quantme/quantme-config.spec.js +++ b/components/bpmn-q/test/tests/quantme/quantme-config.spec.js @@ -1,68 +1,50 @@ -import { setPluginConfig } from "../../../modeler-component/editor/plugin/PluginConfigHandler"; -import { expect } from "chai"; -import * as quantmeConfig from "../../../modeler-component/extensions/quantme/framework-config/config-manager"; +import {setPluginConfig} from '../../../modeler-component/editor/plugin/PluginConfigHandler'; +import {expect} from 'chai'; +import * as quantmeConfig from '../../../modeler-component/extensions/quantme/framework-config/config-manager'; -describe("Test QuantME ConfigManager", function () { - describe("Test QuantME endpoint", function () { - before("Reset QuantME configuration", function () { +describe('Test QuantME ConfigManager', function () { + + describe('Test QuantME endpoint', function () { + + before('Reset QuantME configuration', function () { quantmeConfig.resetConfig(); }); - afterEach("Reset QuantME configuration", function () { + afterEach('Reset QuantME configuration', function () { quantmeConfig.resetConfig(); }); - it("Should configure QuantME endpoints", function () { + it('Should configure QuantME endpoints', function () { setPluginConfig([ { - name: "quantme", + name: 'quantme', config: { - quantmeDataConfigurationsEndpoint: "http://test:8100/data-objects", - opentoscaEndpoint: "http://test:1337/csars", - wineryEndpoint: "http://test:8093/winery", - nisqAnalyzerEndpoint: "http://test:8098/nisq-analyzer", - transformationFrameworkEndpoint: "http://test:8888", - qiskitRuntimeHandlerEndpoint: "http://test:8889", - awsRuntimeHandlerEndpoint: "http://test:8890", - scriptSplitterEndpoint: "http://test:8891", + quantmeDataConfigurationsEndpoint: 'http://test:8100/data-objects', + nisqAnalyzerEndpoint: 'http://test:8098/nisq-analyzer', + transformationFrameworkEndpoint: 'http://test:8888', + qiskitRuntimeHandlerEndpoint: 'http://test:8889', + awsRuntimeHandlerEndpoint: 'http://test:8890', + scriptSplitterEndpoint: 'http://test:8891', scriptSplitterThreshold: 7, - githubRepositoryName: "Example-Repo", - githubUsername: "userName", - githubRepositoryPath: "path/to/repo", - hybridRuntimeProvenance: true, - }, - }, - ]); - - expect(quantmeConfig.getQuantMEDataConfigurationsEndpoint()).to.equal( - "http://test:8100/data-objects" - ); - expect(quantmeConfig.getOpenTOSCAEndpoint()).to.equal( - "http://test:1337/csars" - ); - expect(quantmeConfig.getWineryEndpoint()).to.equal( - "http://test:8093/winery" - ); - expect(quantmeConfig.getNisqAnalyzerEndpoint()).to.equal( - "http://test:8098/nisq-analyzer" - ); - expect(quantmeConfig.getTransformationFrameworkEndpoint()).to.equal( - "http://test:8888" - ); - expect(quantmeConfig.getQiskitRuntimeHandlerEndpoint()).to.equal( - "http://test:8889" - ); - expect(quantmeConfig.getAWSRuntimeHandlerEndpoint()).to.equal( - "http://test:8890" - ); - expect(quantmeConfig.getScriptSplitterEndpoint()).to.equal( - "http://test:8891" + githubRepositoryName: 'Example-Repo', + githubUsername: 'userName', + githubRepositoryPath: 'path/to/repo', + hybridRuntimeProvenance: true + } + }] ); + + expect(quantmeConfig.getQuantMEDataConfigurationsEndpoint()).to.equal('http://test:8100/data-objects'); + expect(quantmeConfig.getNisqAnalyzerEndpoint()).to.equal('http://test:8098/nisq-analyzer'); + expect(quantmeConfig.getTransformationFrameworkEndpoint()).to.equal('http://test:8888'); + expect(quantmeConfig.getQiskitRuntimeHandlerEndpoint()).to.equal('http://test:8889'); + expect(quantmeConfig.getAWSRuntimeHandlerEndpoint()).to.equal('http://test:8890'); + expect(quantmeConfig.getScriptSplitterEndpoint()).to.equal('http://test:8891'); expect(quantmeConfig.getScriptSplitterThreshold()).to.equal(7); - expect(quantmeConfig.getQRMRepositoryName()).to.equal("Example-Repo"); - expect(quantmeConfig.getQRMRepositoryUserName()).to.equal("userName"); - expect(quantmeConfig.getQRMRepositoryPath()).to.equal("path/to/repo"); + expect(quantmeConfig.getQRMRepositoryName()).to.equal('Example-Repo'); + expect(quantmeConfig.getQRMRepositoryUserName()).to.equal('userName'); + expect(quantmeConfig.getQRMRepositoryPath()).to.equal('path/to/repo'); expect(quantmeConfig.getHybridRuntimeProvenance()).to.equal(true); }); }); -}); +}); \ No newline at end of file diff --git a/components/bpmn-q/webpack.config.js b/components/bpmn-q/webpack.config.js index 3f5fae24..0968c389 100644 --- a/components/bpmn-q/webpack.config.js +++ b/components/bpmn-q/webpack.config.js @@ -1,33 +1,40 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin"); -const path = require("path"); -const webpack = require("webpack"); +const path = require('path'); +const webpack = require('webpack'); module.exports = { entry: { - bundle: ["./modeler-component/QuantumWorkflowModeler.js"], + bundle: ['./modeler-component/QuantumWorkflowModeler.js'] }, output: { - filename: "index.js", - path: path.resolve(__dirname, "public"), + filename: 'index.js', + path: path.resolve(__dirname, 'public'), + }, + devServer: { + allowedHosts: "all" }, - module: { rules: [ { test: /\.(png|svg|jpg|jpeg|gif)$/i, - type: "asset/inline", + resourceQuery: { not: [/raw/] }, + type: 'asset/inline', }, { test: /\.(woff|woff2|eot|ttf|otf)$/i, - type: "asset/inline", + type: 'asset/inline', }, { test: /\.(less|css)$/i, - use: ["style-loader", "css-loader", "less-loader"], + use: [ + "style-loader", + "css-loader", + "less-loader", + ], }, { test: /\.bpmnlintrc$/i, - use: "bpmnlint-loader", + use: 'bpmnlint-loader', }, { test: /\.jsx?$/, @@ -38,58 +45,61 @@ module.exports = { options: { cacheDirectory: true, cacheCompression: false, - }, + } }, // 'css-loader', - ], + ] }, { test: /\.bpmn$/, type: "asset/source", }, + { + resourceQuery: /raw/, + type: 'asset/source', + } ], }, resolve: { - extensions: [".jsx", ".js"], + extensions: ['.jsx', '.js'] }, plugins: [ new MiniCssExtractPlugin({ - filename: "modeler-styles.css", + filename: 'modeler-styles.css' }), // 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: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", - ENABLE_PLANQK_PLUGIN: "true", - ENABLE_QHANA_PLUGIN: "true", - ENABLE_QUANTME_PLUGIN: "true", - 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: "", - SERVICE_DATA_CONFIG: "http://localhost:8000/service-task", - SCRIPT_SPLITTER_ENDPOINT: "http://localhost:8891", + AWS_RUNTIME_HANDLER_ENDPOINT: 'http://localhost:8890', + 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, + ENABLE_PLANQK_PLUGIN: true, + ENABLE_QHANA_PLUGIN: true, + ENABLE_QUANTME_PLUGIN: true, + ENABLE_OPENTOSCA_PLUGIN: true, + 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: '', + SERVICE_DATA_CONFIG: 'http://localhost:8000/service-task', + SCRIPT_SPLITTER_ENDPOINT: 'http://localhost:8891', SCRIPT_SPLITTER_THRESHOLD: 5, - TRANSFORMATION_FRAMEWORK_ENDPOINT: "http://localhost:8888", - UPLOAD_BRANCH_NAME: "", - UPLOAD_FILE_NAME: "workflow", - UPLOAD_GITHUB_REPO: "", - UPLOAD_GITHUB_USER: "", - WINERY_ENDPOINT: "http://localhost:8081/winery", - }), + TRANSFORMATION_FRAMEWORK_ENDPOINT: 'http://localhost:8888', + UPLOAD_BRANCH_NAME: '', + UPLOAD_FILE_NAME: 'workflow', + UPLOAD_GITHUB_REPO: '', + UPLOAD_GITHUB_USER: '', + WINERY_ENDPOINT: 'http://localhost:8080/winery' + }) ], - mode: "development", - devtool: "source-map", -}; + mode: 'development', + devtool: 'source-map' +}; \ No newline at end of file diff --git a/doc/quantum-workflow-modeler/extensions/opentosca-plugin/OpenToscaUI.jpg b/doc/quantum-workflow-modeler/extensions/opentosca-plugin/OpenToscaUI.jpg new file mode 100644 index 00000000..7fcbc95f Binary files /dev/null and b/doc/quantum-workflow-modeler/extensions/opentosca-plugin/OpenToscaUI.jpg differ diff --git a/doc/quantum-workflow-modeler/extensions/opentosca-plugin/opentosca-plugin.md b/doc/quantum-workflow-modeler/extensions/opentosca-plugin/opentosca-plugin.md new file mode 100644 index 00000000..a950ce95 --- /dev/null +++ b/doc/quantum-workflow-modeler/extensions/opentosca-plugin/opentosca-plugin.md @@ -0,0 +1,25 @@ +# OpenTOSCA Plugin +Plugin which integrates OpenTOSCA as a plugin. It allows attached deployment model to be deployed to OpenTOSCA. + +## Attach deployment model to Service +In the Properties panel in the `Implementation` section set `Type` to `Deployment`, as shown in the red box. +Afterwards the desired CSAR can be selected. Alternativly a Deployment model could be created on the fly by clicking the `Upload Artifact` button. +In the shown modal a Docker image reference or a local file can be specified. A click on the `Create` button creates the deployment model an attaches it to the service task. + +## Visualize deployment models +On service tasks with an attached deployment model a button is shown which toggles the visualisation of the deployment model. +Alternatively a click on OpenTOSCA in the toolbar. Shows two buttons that when clicked show or hide all deployment visualisations at a single click. +Both options are shown in the green and orange boxes respectively. + +The deployment model is only shown if a top level node is found. The top level node is detected in the following names +* by a reference as in the TOSCA Tags. key: `top-node` value: `NODE_XYZ`. +* node without any incoming hosted-on. Only possible if just node with this condition is found. + +![UI](OpenToscaUI.jpg) + +## Structure +- [OpenTOSCA Plugin Object](../../../../components/bpmn-q/modeler-component/extensions/opentosca/OpenToscaPlugin.js) +- [OpenTOSCA Config](../../../../components/bpmn-q/modeler-component/extensions/opentosca/framework-config) +- [bpmn-js Extension Module](../../../../components/bpmn-q/modeler-component/extensions/opentosca/modeling) +- [Service Deployment to OpenTOSCA](../../../../components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment) +- [Deployment Model Renderer](../../../../components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCARenderer.js) \ No newline at end of file diff --git a/doc/quantum-workflow-modeler/extensions/quantme-plugin/quantme-plugin.md b/doc/quantum-workflow-modeler/extensions/quantme-plugin/quantme-plugin.md index 588c2427..06075c46 100644 --- a/doc/quantum-workflow-modeler/extensions/quantme-plugin/quantme-plugin.md +++ b/doc/quantum-workflow-modeler/extensions/quantme-plugin/quantme-plugin.md @@ -10,5 +10,4 @@ because the Data Object Configurations are applied to DataMapObjects. - [QuantME Transformation Function](../../../../components/bpmn-q/modeler-component/extensions/quantme/replacement/QuantMETransformator.js) - [QRM Manager](../../../../components/bpmn-q/modeler-component/extensions/quantme/qrm-manager/qrm-handler.js) - [Hybrid Loop Detection and Rewrite](../../../../components/bpmn-q/modeler-component/extensions/quantme/ui/adaptation) -- [Service Deployment to OpenTOSCA](../../../../components/bpmn-q/modeler-component/extensions/quantme/ui/deployment) - [Utilities](../../../../components/bpmn-q/modeler-component/extensions/quantme/utilities) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..26e0a275 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "workflow-modeler", + "lockfileVersion": 2, + "requires": true, + "packages": {} +}