From 3758f64b1b49362d933b510bb3e31caea8043d5c Mon Sep 17 00:00:00 2001 From: LaviniaStiliadou Date: Mon, 20 Nov 2023 15:07:59 +0100 Subject: [PATCH] Add policies (#123) * add policies * add deployment policy * add deploymentStrategy to task * fix props for deployment policy * remove paths * checkbox, change icon * add palette * change base shape * change onDemand of task * add constants * move policies to opentosca * remove privacy policy split into location, dedicatedHosting, cloudDeploymentMode * fix display of properties * add transformation into service deployment * remove on-demand modal from deploy workflow * update service task ids * adjust height & position * change icons of policies * adjust blue box * sync between task & policy * fix lint * add icons for palette & replace menu * change quantme to opentosca * update palette * add replace rule * Add missing header * remove entries from contextpad * restrict number of policies * update connect rule * add header * Update docs * update rule of reconnect * remove checkbox from ondemandpolicy, fix icons * remove checkbox for dedicated hosting policy * fix flag * remove on-demand policy when on-demand task is transformed --------- Co-authored-by: Benjamin Weder --- .../editor/ui/DeploymentButton.js | 42 +- .../editor/ui/OnDemandDeploymentModal.js | 56 -- .../rules/DataFlowRulesProvider.js | 6 +- .../extensions/opentosca/Constants.js | 29 + .../modeling/OpenTOSCAContextPadProvider.js | 45 ++ .../modeling/OpenTOSCAPaletteProvider.js | 65 +++ .../opentosca/modeling/OpenTOSCARenderer.js | 92 ++- .../modeling/OpenTOSCAReplaceMenuProvider.js | 100 ++++ .../modeling/OpenTOSCAReplaceOptions.js | 39 ++ .../opentosca/modeling/OpenTOSCARules.js | 167 ++++++ .../opentosca/modeling/OpenTOSCASVGMap.js | 41 ++ .../extensions/opentosca/modeling/index.js | 20 +- .../DeploymentModelProps.js | 65 ++- .../OpenTOSCAPropertyEntries.js | 125 +++++ .../OpenTOSCATaskProperties.js | 28 + .../ServiceTaskPropertiesProvider.js | 63 ++- .../replacement/OnDemandTransformator.js | 208 +++---- .../extensions/opentosca/resources/Policy.svg | 120 ++++ .../clouddeploymentmodel_palette.svg | 101 ++++ .../dedicatedHosting_policy_palette.svg | 109 ++++ .../resources/deployment_policy_palette.svg | 159 ++++++ .../resources/location_policy_palette.svg | 105 ++++ .../opentosca/resources/opentosca4bpmn.json | 54 ++ .../opentosca/styling/opentosca.css | 55 ++ .../deployment/services/DeploymentPlugin.js | 44 +- .../services/ServiceDeploymentBindingModal.js | 2 +- .../services/ServiceDeploymentInputModal.js | 2 +- .../ServiceDeploymentOverviewModal.js | 20 +- .../ServiceOnDemandDeploymentOverviewModal.js | 110 ++++ .../modeling/QuantMEReplaceMenuProvider.js | 1 - .../QuantMETaskProperties.js | 3 +- .../resources/DeploymentPolicy_Icon.svg | 531 ++++++++++++++++++ .../quantme/resources/PrivacyPolicy_Icon.svg | 166 ++++++ .../extensions/quantme/resources/policy.svg | 120 ++++ 34 files changed, 2677 insertions(+), 216 deletions(-) delete mode 100644 components/bpmn-q/modeler-component/editor/ui/OnDemandDeploymentModal.js create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/Constants.js create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAContextPadProvider.js create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAPaletteProvider.js create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAReplaceMenuProvider.js create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAReplaceOptions.js create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCARules.js create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCASVGMap.js create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/OpenTOSCAPropertyEntries.js create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/OpenTOSCATaskProperties.js create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/resources/Policy.svg create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/resources/clouddeploymentmodel_palette.svg create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/resources/dedicatedHosting_policy_palette.svg create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/resources/deployment_policy_palette.svg create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/resources/location_policy_palette.svg create mode 100644 components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceOnDemandDeploymentOverviewModal.js create mode 100644 components/bpmn-q/modeler-component/extensions/quantme/resources/DeploymentPolicy_Icon.svg create mode 100644 components/bpmn-q/modeler-component/extensions/quantme/resources/PrivacyPolicy_Icon.svg create mode 100644 components/bpmn-q/modeler-component/extensions/quantme/resources/policy.svg diff --git a/components/bpmn-q/modeler-component/editor/ui/DeploymentButton.js b/components/bpmn-q/modeler-component/editor/ui/DeploymentButton.js index 3ecb888c..c1f416a1 100644 --- a/components/bpmn-q/modeler-component/editor/ui/DeploymentButton.js +++ b/components/bpmn-q/modeler-component/editor/ui/DeploymentButton.js @@ -1,12 +1,9 @@ -import React, { Fragment, useState } from "react"; +import React, { Fragment } 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 { createTempModelerFromXml, getModeler } from "../ModelerHandler"; -import OnDemandDeploymentModal from "./OnDemandDeploymentModal"; -import { startOnDemandReplacementProcess } from "../../extensions/opentosca/replacement/OnDemandTransformator"; +import { createTempModelerFromXml } from "../ModelerHandler"; // eslint-disable-next-line no-unused-vars const defaultState = { @@ -21,31 +18,8 @@ const defaultState = { * @constructor */ export default function DeploymentButton(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 */ @@ -116,14 +90,7 @@ export default function DeploymentButton(props) { } async function onClick() { - let csarsToDeploy = getServiceTasksToDeploy( - getRootProcess(getModeler().getDefinitions()) - ); - if (csarsToDeploy.length > 0) { - setWindowOpenOnDemandDeployment(true); - } else { - deploy((await modeler.saveXML({ format: true })).xml); - } + deploy((await modeler.saveXML({ format: true })).xml); } return ( @@ -138,9 +105,6 @@ export default function DeploymentButton(props) { Deploy Workflow - {windowOpenOnDemandDeployment && ( - handleOnDemandDeployment(e)} /> - )} ); } diff --git a/components/bpmn-q/modeler-component/editor/ui/OnDemandDeploymentModal.js b/components/bpmn-q/modeler-component/editor/ui/OnDemandDeploymentModal.js deleted file mode 100644 index aee49242..00000000 --- a/components/bpmn-q/modeler-component/editor/ui/OnDemandDeploymentModal.js +++ /dev/null @@ -1,56 +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 - */ - -/* 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/extensions/data-extension/rules/DataFlowRulesProvider.js b/components/bpmn-q/modeler-component/extensions/data-extension/rules/DataFlowRulesProvider.js index ff426592..db0c255a 100644 --- a/components/bpmn-q/modeler-component/extensions/data-extension/rules/DataFlowRulesProvider.js +++ b/components/bpmn-q/modeler-component/extensions/data-extension/rules/DataFlowRulesProvider.js @@ -33,7 +33,7 @@ export default class CustomRulesProvider extends BpmnRules { /** * Fired during creation of a new connection (while you selected the target of a connection) */ - this.addRule("connection.create", 200000, function (context) { + this.addRule("connection.create", 200, function (context) { const source = context.source, target = context.target; @@ -43,7 +43,7 @@ export default class CustomRulesProvider extends BpmnRules { /** * Fired when a connection between two elements is drawn again, e.g. after dragging an element */ - this.addRule("connection.reconnect", 200000000000, function (context) { + this.addRule("connection.reconnect", 200, function (context) { const source = context.source, target = context.target; @@ -57,7 +57,7 @@ export default class CustomRulesProvider extends BpmnRules { /** * Fired when a new shape for an element is created */ - this.addRule("shape.create", 200000000000, function (context) { + this.addRule("shape.create", 200, function (context) { return canCreate( context.shape, context.target, diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/Constants.js b/components/bpmn-q/modeler-component/extensions/opentosca/Constants.js new file mode 100644 index 00000000..799656a5 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/Constants.js @@ -0,0 +1,29 @@ +/** + * 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 + */ +export const POLICY = "opentosca:Policy"; +export const CLOUD_DEPLOYMENT_MODEL_POLICY = + "opentosca:CloudDeploymentModelPolicy"; +export const ON_DEMAND_POLICY = "opentosca:OnDemandPolicy"; +export const LOCATION_POLICY = "opentosca:LocationPolicy"; +export const DEDICATED_HOSTING_POLICY = "opentosca:DedicatedHostingPolicy"; + +export const ON_DEMAND = "onDemand"; +export const DEDICATED_HOSTING = "dedicatedHosting"; +export const CLOUD_TYPE = "cloudType"; +export const LOCATION = "location"; + +export const POLICIES = [ + POLICY, + CLOUD_DEPLOYMENT_MODEL_POLICY, + DEDICATED_HOSTING_POLICY, + LOCATION_POLICY, + ON_DEMAND_POLICY, +]; diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAContextPadProvider.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAContextPadProvider.js new file mode 100644 index 00000000..dc158fb1 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAContextPadProvider.js @@ -0,0 +1,45 @@ +/** + * 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 inherits from "inherits"; + +import ContextPadProvider from "bpmn-js/lib/features/context-pad/ContextPadProvider"; + +import { bind } from "min-dash"; + +import * as consts from "../Constants"; + +export default function OpenTOSCAContextPadProvider(injector) { + injector.invoke(ContextPadProvider, this); + bind(this.getContextPadEntries, this); + + const _getContextPadEntries = + ContextPadProvider.prototype.getContextPadEntries; + ContextPadProvider.prototype.getContextPadEntries = function (element) { + const entries = _getContextPadEntries.apply(this, [element]); + if (consts.POLICIES.includes(element.type)) { + delete entries["append.end-event"]; + delete entries["append.intermediate-event"]; + delete entries["append.gateway"]; + delete entries["append.append-task"]; + delete entries["append.text-annotation"]; + delete entries["connect"]; + if (element.type === consts.ON_DEMAND_POLICY) { + delete entries["delete"]; + delete entries["replace"]; + } + } + return entries; + }; +} + +inherits(OpenTOSCAContextPadProvider, ContextPadProvider); + +OpenTOSCAContextPadProvider.$inject = ["injector", "connect", "translate"]; diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAPaletteProvider.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAPaletteProvider.js new file mode 100644 index 00000000..c39e3f86 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAPaletteProvider.js @@ -0,0 +1,65 @@ +/** + * 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 consts from "../Constants"; + +export default class OpenTOSCAPaletteProvider { + constructor(bpmnFactory, create, elementFactory, palette, translate) { + this.bpmnFactory = bpmnFactory; + this.create = create; + this.elementFactory = elementFactory; + this.translate = translate; + + palette.registerProvider(this); + } + + getPaletteEntries() { + return this.createPolicyEntry(); + } + + createPolicyEntry() { + const { bpmnFactory, create, elementFactory, translate } = this; + + function createPolicy(event) { + const businessObject = bpmnFactory.create(consts.POLICY); + let shape = elementFactory.createShape({ + type: consts.POLICY, + businessObject: businessObject, + }); + create.start(event, shape); + } + + return { + // add separator line to delimit the new group + "opentosca-separator": { + group: "opentosca", + separator: true, + }, + "create.opentosca-policy": { + group: "opentosca", + className: "qwm opentosca-icon-policy-palette", + title: translate("Creates a policy"), + action: { + click: createPolicy, + dragstart: createPolicy, + }, + }, + }; + } +} + +OpenTOSCAPaletteProvider.$inject = [ + "bpmnFactory", + "create", + "elementFactory", + "palette", + "translate", +]; diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCARenderer.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCARenderer.js index 962095ba..f3d13341 100644 --- a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCARenderer.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCARenderer.js @@ -23,11 +23,14 @@ import { append as svgAppend, attr as svgAttr, create as svgCreate, - select, + innerSVG, + select as svgSelect, prepend as svgPrepend, } from "tiny-svg"; import { query as domQuery } from "min-dom"; import { loadTopology } from "../deployment/WineryUtils"; +import * as consts from "../Constants"; +import { getOpenTOSCASVG } from "./OpenTOSCASVGMap"; const HIGH_PRIORITY = 14001; const SERVICE_TASK_TYPE = "bpmn:ServiceTask"; @@ -72,6 +75,22 @@ export default class OpenTOSCARenderer extends BpmnRenderer { this.styles = styles; this.textRenderer = textRenderer; + function drawTaskSVG(parentGfx, iconID) { + var importsvg = getOpenTOSCASVG(iconID); + var innerSVGstring = importsvg.svg; + var transformDef = importsvg.transform; + + const groupDef = svgCreate("g"); + svgAttr(groupDef, { transform: transformDef }); + innerSVG(groupDef, innerSVGstring); + + // set task box opacity to 0 such that icon can be in the background + svgAttr(svgSelect(parentGfx, "rect"), { "fill-opacity": 0 }); + + // draw svg in the background + parentGfx.prepend(groupDef); + } + // snap boundary events eventBus.on( ["create.move", "create.end", "shape.move.move", "shape.move.end"], @@ -85,13 +104,14 @@ export default class OpenTOSCARenderer extends BpmnRenderer { canExecute && (canExecute === "attach" || canExecute.attach); const isRelevantEvent = context.shape.type === "bpmn:IntermediateThrowEvent" || - context.shape.type === "bpmn:BoundaryEvent"; + context.shape.type === "bpmn:BoundaryEvent" || + context.shape.type.includes("Policy"); if ( canAttach && isRelevantEvent && context.target.businessObject.get("opentosca:deploymentModelUrl") && - getOrientation(event, target, -30) === "bottom-right" + getOrientation(event, target, -50) === "bottom-right" ) { // Prevent snapping on deployment visualisation toggle button event.stopPropagation(); @@ -105,6 +125,70 @@ export default class OpenTOSCARenderer extends BpmnRenderer { self.showDeploymentModelButton(parentGfx, element); return task; }, + [consts.POLICY]: function (self, parentGfx, element) { + var attrs = { + fill: "white", + stroke: "none", + }; + element.width = 42; + element.height = 44; + var task = self.renderer("bpmn:Activity")(parentGfx, element, attrs); + setTimeout(function () {}, 10000); + drawTaskSVG(parentGfx, "POLICY"); + return task; + }, + [consts.CLOUD_DEPLOYMENT_MODEL_POLICY]: function ( + self, + parentGfx, + element + ) { + var attrs = { + fill: "white", + stroke: "none", + }; + element.width = 42; + element.height = 44; + var task = self.renderer("bpmn:Activity")(parentGfx, element, attrs); + setTimeout(function () {}, 10000); + drawTaskSVG(parentGfx, "CLOUD_DEPLOYMENT_MODEL_POLICY"); + return task; + }, + [consts.DEDICATED_HOSTING_POLICY]: function (self, parentGfx, element) { + var attrs = { + fill: "white", + stroke: "none", + }; + element.width = 42; + element.height = 44; + var task = self.renderer("bpmn:Activity")(parentGfx, element, attrs); + setTimeout(function () {}, 10000); + drawTaskSVG(parentGfx, "DEDICATED_HOSTING_POLICY"); + return task; + }, + [consts.ON_DEMAND_POLICY]: function (self, parentGfx, element) { + var attrs = { + fill: "white", + stroke: "none", + }; + element.width = 42; + element.height = 44; + var task = self.renderer("bpmn:Activity")(parentGfx, element, attrs); + setTimeout(function () {}, 10000); + drawTaskSVG(parentGfx, "ON_DEMAND_POLICY"); + return task; + }, + [consts.LOCATION_POLICY]: function (self, parentGfx, element) { + var attrs = { + fill: "white", + stroke: "none", + }; + element.width = 42; + element.height = 44; + var task = self.renderer("bpmn:Activity")(parentGfx, element, attrs); + setTimeout(function () {}, 10000); + drawTaskSVG(parentGfx, "LOCATION_POLICY"); + return task; + }, }; this.addMarkerDefinition(canvas); this.registerShowDeploymentModelHandler(); @@ -615,7 +699,7 @@ export default class OpenTOSCARenderer extends BpmnRenderer { removeDeploymentModel(parentGfx, element) { this.currentlyShownDeploymentsModels.delete(element.id); - const group = select(parentGfx, "#" + DEPLOYMENT_GROUP_ID); + const group = svgSelect(parentGfx, "#" + DEPLOYMENT_GROUP_ID); if (group) { group.remove(); } diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAReplaceMenuProvider.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAReplaceMenuProvider.js new file mode 100644 index 00000000..f9b970d5 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAReplaceMenuProvider.js @@ -0,0 +1,100 @@ +/** + * 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 opentoscaReplaceOptions from "./OpenTOSCAReplaceOptions"; +import { is } from "bpmn-js/lib/util/ModelUtil"; +import { createMenuEntries } from "../../../editor/util/PopupMenuUtilities"; +import { filter } from "min-dash"; +import { isDifferentType } from "bpmn-js/lib/features/popup-menu/util/TypeUtil"; + +/** + * This class extends the default ReplaceMenuProvider with the newly introduced policies + */ +export default class OpenTOSCAReplaceMenuProvider { + constructor( + bpmnFactory, + popupMenu, + modeling, + moddle, + bpmnReplace, + rules, + translate, + commandStack + ) { + this.popupMenu = popupMenu; + this.translate = translate; + this.bpmnReplace = bpmnReplace; + this.replaceElement = bpmnReplace.replaceElement; + this.bpmnFactory = bpmnFactory; + this.modeling = modeling; + this.commandStack = commandStack; + + popupMenu.registerProvider("bpmn-replace", this); + } + + getPopupMenuEntries(element) { + const self = this; + return function (entries) { + // add additional elements to replace policies + if (is(element, "bpmn:Event")) { + if (element.host !== undefined) { + if (element.host.type === "bpmn:ServiceTask") { + let attachers = element.host.attachers; + let attacherTypes = []; + for (let i = 0; i < attachers.length; i++) { + let attacher = attachers[i]; + let attacherType = attacher.type; + + // Add the attacher type to the array if it's not already there + if ( + !attacherTypes.includes(attacherType) && + attacherType !== element.type + ) { + attacherTypes.push(attacherType); + } + } + + const filteredOptions = filter( + opentoscaReplaceOptions.POLICY, + isDifferentType(element) + ); + + const filteredOptionsBasedOnAttachers = filteredOptions.filter( + (option) => { + return !attacherTypes.includes(option.target.type); + } + ); + const policyEntries = createMenuEntries( + element, + filteredOptionsBasedOnAttachers, + self.translate, + self.bpmnReplace.replaceElement + ); + return Object.assign(policyEntries, entries); + } + } + } + + return entries; + }; + } +} + +OpenTOSCAReplaceMenuProvider.$inject = [ + "bpmnFactory", + "popupMenu", + "modeling", + "moddle", + "bpmnReplace", + "rules", + "translate", + "commandStack", +]; diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAReplaceOptions.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAReplaceOptions.js new file mode 100644 index 00000000..66d9b982 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCAReplaceOptions.js @@ -0,0 +1,39 @@ +/** + * 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 consts from "../Constants"; + +export var POLICY = [ + { + label: "Cloud Deployment Model Policy", + actionName: "replace-with-cloud-deployment-model-policy", + className: "qwm bpmn-icon-cloud-deployment-model-policy", + target: { + type: consts.CLOUD_DEPLOYMENT_MODEL_POLICY, + }, + }, + { + label: "Dedicated Hosting Policy", + actionName: "replace-with-dedicated-hosting-policy", + className: "qwm bpmn-icon-dedicated-hosting-policy", + target: { + type: consts.DEDICATED_HOSTING_POLICY, + }, + }, + { + label: "Location Policy", + actionName: "replace-with-location-policy", + className: "qwm bpmn-icon-location-policy", + target: { + type: consts.LOCATION_POLICY, + }, + }, +]; diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCARules.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCARules.js new file mode 100644 index 00000000..9da72e5c --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCARules.js @@ -0,0 +1,167 @@ +/** + * 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 RuleProvider from "diagram-js/lib/features/rules/RuleProvider"; +import * as consts from "../Constants"; +import { getModeler } from "../../../editor/ModelerHandler"; + +export default class OpenTOSCARules extends RuleProvider { + constructor(eventBus, modeling) { + super(eventBus); + this.modeling = modeling; + + this.addRule("shape.create", 10000, function (context) { + var shape = context.shape, + target = context.target; + + if (shape.type.includes("Policy") && target.type !== "bpmn:ServiceTask") { + return false; + } + }); + + function canMove(context) { + var target = context.target; + + if (target != undefined) { + if (context.shapes[0].type.includes("Policy")) { + return false; + } + } + } + + this.addRule("elements.move", 4000, function (context) { + return canMove(context); + }); + + this.addRule("shape.replace", function (context) { + if (context.element.type.includes("Policy")) { + if (context.element.type === consts.DEDICATED_HOSTING_POLICY) { + getModeler().get("modeling").updateProperties(context.element, { + "opentosca:dedicatedHosting": "true", + }); + } + return false; + } + }); + this.addRule("shape.append", function (context) { + if (context.element.type.includes("Policy")) { + return false; + } + }); + + this.addRule("connection.create", 2000, function (context) { + if (context.target.type.includes("Policy")) { + return false; + } + if (context.source.type.includes("Policy")) { + return false; + } + }); + + this.addRule("connection.reconnect", 2000, function (context) { + const source = context.source, + target = context.target; + + if (source.type.includes("Policy") || target.type.includes("Policy")) { + return false; + } + }); + + this.addRule("shape.attach", 4000, function (context) { + let shapeToAttach = context.shape; + let target = context.target; + + if ( + shapeToAttach.type.includes("Policy") && + target.type !== "bpmn:ServiceTask" + ) { + return false; + } + + if ( + shapeToAttach.type.includes("Policy") && + target.type === "bpmn:ServiceTask" + ) { + let specificPolicies = consts.POLICIES; + specificPolicies = specificPolicies.filter( + (policy) => policy !== consts.POLICY + ); + specificPolicies = specificPolicies.filter( + (policy) => policy !== consts.ON_DEMAND_POLICY + ); + let attachedElementTypesWithPolicy = 0; + for (let i = 0; i < target.attachers.length; i++) { + if ( + target.attachers[i].type.includes("Policy") && + target.attachers[i].type !== consts.ON_DEMAND_POLICY + ) { + attachedElementTypesWithPolicy++; + } + if (specificPolicies.includes(target.attachers[i].type)) { + specificPolicies = specificPolicies.filter( + (policy) => policy !== target.attachers[i].type + ); + } + } + + for (let i = 0; i < target.attachers.length; i++) { + if (specificPolicies.includes(target.attachers[i].type)) { + specificPolicies = specificPolicies.filter( + (policy) => policy !== target.attachers[i].type + ); + } + } + if (attachedElementTypesWithPolicy === consts.POLICIES.length - 2) { + return false; + } + + // If the specific policies are included, prevent attaching another policy + if (specificPolicies.length === 0) { + return false; + } + for (let i = 0; i < target.attachers.length; i++) { + let boundaryElement = target.attachers[i]; + + if ( + boundaryElement.type === consts.DEDICATED_HOSTING_POLICY && + shapeToAttach.type === consts.DEDICATED_HOSTING_POLICY + ) { + return false; + } + + if ( + boundaryElement.type === consts.ON_DEMAND_POLICY && + shapeToAttach.type === consts.ON_DEMAND_POLICY + ) { + return false; + } + + if ( + boundaryElement.type === consts.LOCATION_POLICY && + shapeToAttach.type === consts.LOCATION_POLICY + ) { + return false; + } + + if ( + boundaryElement.type === consts.CLOUD_DEPLOYMENT_MODEL_POLICY && + shapeToAttach.type === consts.CLOUD_DEPLOYMENT_MODEL_POLICY + ) { + return false; + } + } + return true; + } + }); + } +} + +OpenTOSCARules.$inject = ["eventBus", "modeling"]; diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCASVGMap.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCASVGMap.js new file mode 100644 index 00000000..067c0b5a --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/OpenTOSCASVGMap.js @@ -0,0 +1,41 @@ +/** + * 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 + */ + +export function getOpenTOSCASVG(svgId) { + // to insert svgs easily just open them in your browser, copy the outer html and insert it using ctrl + alt + shift + v in intellij to avoid formatting,escaping etc. + // matrix( Scalingfactor, 0, 0, Scalingfactor, shift X, shift y) + // IMPORTANT: ensure that definition Ids for new SVGs are UNIQUE + // viewbox is not required + const openTOSCAMap = { + CLOUD_DEPLOYMENT_MODEL_POLICY: { + transform: "matrix(0.32, 0, 0, 0.32, -3.5, -2)", + svg: ' ', + }, + DEDICATED_HOSTING_POLICY: { + transform: "matrix(0.32, 0, 0, 0.32, -3.5, -2)", + svg: ' ', + }, + ON_DEMAND_POLICY: { + transform: "matrix(0.32, 0, 0, 0.32, -3.5, -2)", + svg: ' ', + }, + LOCATION_POLICY: { + transform: "matrix(0.32, 0, 0, 0.32, -3.5, -2)", + svg: ' ', + }, + POLICY: { + transform: "matrix(0.32, 0, 0, 0.32, -3.5, -2)", + svg: ' ', + }, + }; + + return openTOSCAMap[svgId]; +} diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/index.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/index.js index bc61a866..ffe367f1 100644 --- a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/index.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/index.js @@ -10,9 +10,23 @@ */ import ServiceTaskPropertiesProvider from "./properties-provider/ServiceTaskPropertiesProvider"; import OpenTOSCARenderer from "./OpenTOSCARenderer"; - +import OpenTOSCAPaletteProvider from "./OpenTOSCAPaletteProvider"; +import OpenTOSCARules from "./OpenTOSCARules"; +import OpenTOSCAReplaceMenuProvider from "./OpenTOSCAReplaceMenuProvider"; +import OpenTOSCAContextPadProvider from "./OpenTOSCAContextPadProvider"; export default { - __init__: ["openToscaRenderer", "serviceTaskPropertyProvider"], + __init__: [ + "openToscaRenderer", + "openToscaPropertiesProvider", + "openToscaRules", + "openToscaPalette", + "openToscaReplaceMenuProvider", + "openToscaContextPadProvider", + ], openToscaRenderer: ["type", OpenTOSCARenderer], - serviceTaskPropertyProvider: ["type", ServiceTaskPropertiesProvider], + openToscaPropertiesProvider: ["type", ServiceTaskPropertiesProvider], + openToscaRules: ["type", OpenTOSCARules], + openToscaPalette: ["type", OpenTOSCAPaletteProvider], + openToscaReplaceMenuProvider: ["type", OpenTOSCAReplaceMenuProvider], + openToscaContextPadProvider: ["type", OpenTOSCAContextPadProvider], }; diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/DeploymentModelProps.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/DeploymentModelProps.js index b623fd2c..25f977fe 100644 --- a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/DeploymentModelProps.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/DeploymentModelProps.js @@ -8,11 +8,16 @@ * * SPDX-License-Identifier: Apache-2.0 */ - +import React from "@bpmn-io/properties-panel/preact/compat"; import { getServiceTaskLikeBusinessObject } from "../../../../editor/util/camunda-utils/ImplementationTypeUtils"; import { ArtifactUpload } from "./ArtifactUpload"; -import { isTextFieldEntryEdited } from "@bpmn-io/properties-panel"; +import { + CheckboxEntry, + isTextFieldEntryEdited, +} from "@bpmn-io/properties-panel"; import { Deployment } from "./Deployment"; +import { useService } from "bpmn-js-properties-panel"; +import * as consts from "../../Constants"; /** * Properties group for service tasks. Extends the original implementation by adding a new selection option to the @@ -32,6 +37,12 @@ export function DeploymentModelProps(props) { // list of configuration options const entries = []; + entries.push({ + id: consts.ON_DEMAND, + element, + component: OnDemandEntry, + isEdited: isTextFieldEntryEdited, + }); // field to define deployment models entries.push({ @@ -53,3 +64,53 @@ export function DeploymentModelProps(props) { return entries; } + +export function OnDemandEntry({ element }) { + const modeling = useService("modeling"); + const elementRegistry = useService("elementRegistry"); + const translate = + useService("translate") || + function (str) { + return str; + }; + const debounce = useService("debounceInput"); + + const getValue = function () { + return element.businessObject.onDemand; + }; + + const setValue = function (newValue) { + let attachers = element.attachers; + if (newValue) { + modeling.createShape( + { type: consts.ON_DEMAND_POLICY }, + { x: element.x + 85, y: element.y }, + element, + { attach: true } + ); + return modeling.updateProperties(element, { + onDemand: newValue, + }); + } else { + for (let i = 0; i < attachers.length; i++) { + if (attachers[i].type === consts.ON_DEMAND_POLICY) { + attachers[i].businessObject.onDemand = newValue; + modeling.removeShape(elementRegistry.get(attachers[i].id)); + } + } + return modeling.updateProperties(element, { + onDemand: newValue, + }); + } + }; + + return ( + + ); +} diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/OpenTOSCAPropertyEntries.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/OpenTOSCAPropertyEntries.js new file mode 100644 index 00000000..75155c1f --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/OpenTOSCAPropertyEntries.js @@ -0,0 +1,125 @@ +/** + * 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 React from "@bpmn-io/properties-panel/preact/compat"; + +import { + SelectEntry, + CheckboxEntry, + TextFieldEntry, +} from "@bpmn-io/properties-panel"; +import * as consts from "../../Constants"; +import { useService } from "bpmn-js-properties-panel"; + +/** + * All entries needed to display the different properties introduced through the OpenTOSCA modeling constructs. One entry represents one + * property. + */ +export function CloudTypeEntry({ element }) { + const modeling = useService("modeling"); + const translate = + useService("translate") || + function (str) { + return str; + }; + const debounce = useService("debounceInput"); + + const getValue = function () { + return element.businessObject.cloudType; + }; + + const setValue = function (newValue) { + return modeling.updateProperties(element, { + cloudType: newValue, + }); + }; + + const selectOptions = [ + { value: "public", label: "Public" }, + { value: "private", label: "Private" }, + ]; + + const getOptions = function () { + return selectOptions; + }; + + return ( + + ); +} + +export function DedicatedHostingEntry({ element }) { + const modeling = useService("modeling"); + const translate = + useService("translate") || + function (str) { + return str; + }; + const debounce = useService("debounceInput"); + + const getValue = function () { + return element.businessObject.dedicatedHosting; + }; + + const setValue = function (newValue) { + return modeling.updateProperties(element, { + dedicatedHosting: newValue, + }); + }; + + return ( + + ); +} + +export function LocationEntry({ element }) { + const modeling = useService("modeling"); + const translate = + useService("translate") || + function (str) { + return str; + }; + const debounce = useService("debounceInput"); + + const getValue = function () { + return element.businessObject.location; + }; + + const setValue = function (newValue) { + return modeling.updateProperties(element, { + location: newValue, + }); + }; + + return ( + + ); +} diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/OpenTOSCATaskProperties.js b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/OpenTOSCATaskProperties.js new file mode 100644 index 00000000..30e1f8cb --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/modeling/properties-provider/OpenTOSCATaskProperties.js @@ -0,0 +1,28 @@ +import * as consts from "../../Constants"; +import { isTextFieldEntryEdited } from "@bpmn-io/properties-panel"; +import { CloudTypeEntry, LocationEntry } from "./OpenTOSCAPropertyEntries.js"; + +/** + * This file contains all properties of the OpenTOSCA modeling constructs and the entries they define. + */ +export function CloudDeploymentModelPolicyEntries(element) { + return [ + { + id: consts.CLOUD_TYPE, + element, + component: CloudTypeEntry, + isEdited: isTextFieldEntryEdited, + }, + ]; +} + +export function LocationPolicyEntries(element) { + return [ + { + id: consts.LOCATION, + element, + component: LocationEntry, + isEdited: isTextFieldEntryEdited, + }, + ]; +} 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 index a6bdbb47..60da7f6f 100644 --- 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 @@ -13,6 +13,11 @@ import { ImplementationProps } from "./ImplementationProps"; import { Group } from "@bpmn-io/properties-panel"; import { getWineryEndpoint } from "../../framework-config/config-manager"; import { DeploymentModelProps } from "./DeploymentModelProps"; +import { + CloudDeploymentModelPolicyEntries, + LocationPolicyEntries, +} from "./OpenTOSCATaskProperties"; +import * as consts from "../../Constants"; const LOW_PRIORITY = 500; @@ -24,7 +29,9 @@ const LOW_PRIORITY = 500; */ export default function ServiceTaskPropertiesProvider( propertiesPanel, - injector + injector, + translate, + modeling ) { /** * Return the groups provided for the given element. @@ -49,9 +56,21 @@ export default function ServiceTaskPropertiesProvider( groups[3] = DeploymentModelGroup( element, injector, - getWineryEndpoint() + getWineryEndpoint(), + modeling ); } + + // add properties of policies to panel + if ( + element.type && + element.type.startsWith("opentosca:") && + element.type !== consts.POLICY && + element.type !== consts.ON_DEMAND_POLICY && + element.type !== consts.DEDICATED_HOSTING_POLICY + ) { + groups.unshift(createOpenTOSCAGroup(element, translate)); + } return groups; }; }; @@ -63,7 +82,7 @@ ServiceTaskPropertiesProvider.$inject = [ "propertiesPanel", "injector", "translate", - "eventBus", + "modeling", ]; /** @@ -100,14 +119,16 @@ function ImplementationGroup(element, injector) { * @return {null|{component: ((function(*): preact.VNode)|*), entries: *[], label, id: string}} * @constructor */ -function DeploymentModelGroup(element, injector, wineryEndpoint) { +function DeploymentModelGroup(element, injector, wineryEndpoint, modeling) { const translate = injector.get("translate"); const group = { label: translate("Deployment Models"), id: "CamundaPlatform__DeploymentModels", component: Group, - entries: [...DeploymentModelProps({ element, wineryEndpoint, translate })], + entries: [ + ...DeploymentModelProps({ element, wineryEndpoint, translate, modeling }), + ], }; if (group.entries.length) { @@ -116,3 +137,35 @@ function DeploymentModelGroup(element, injector, wineryEndpoint) { return null; } + +/** + * Create properties group to display custom properties in the properties panel. The entries of this group + * depend on the actual type of the given element and are determined in OpenTOSCAProps. + * + * @param element The given element + * @param translate The translate function of the bpmn-js modeler. + */ +function createOpenTOSCAGroup(element, translate) { + // add required properties to general tab + return { + id: "opentoscaServiceDetails", + label: translate("Details"), + entries: OpenTOSCAProps(element), + }; +} + +/** + * Add the property entries for the attributes to the given group based on the type of the OpenTOSCA element + * + * @param element the OpenTOSCA element + */ +function OpenTOSCAProps(element) { + switch (element.type) { + case consts.CLOUD_DEPLOYMENT_MODEL_POLICY: + return CloudDeploymentModelPolicyEntries(element); + case consts.LOCATION_POLICY: + return LocationPolicyEntries(element); + default: + console.log("Unsupported OpenTOSCA element of type: ", element.type); + } +} diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/replacement/OnDemandTransformator.js b/components/bpmn-q/modeler-component/extensions/opentosca/replacement/OnDemandTransformator.js index 7957a14b..aaab10a3 100644 --- a/components/bpmn-q/modeler-component/extensions/opentosca/replacement/OnDemandTransformator.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/replacement/OnDemandTransformator.js @@ -16,6 +16,8 @@ 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 { getRootProcess } from "../../../editor/util/ModellingUtilities"; +import * as consts from "../Constants"; const fetchMethod = ` function fetch(method, url, body) { @@ -135,123 +137,135 @@ export async function startOnDemandReplacementProcess(xml) { const bpmnAutoResizeProvider = modeler.get("bpmnAutoResizeProvider"); const bpmnFactory = modeler.get("bpmnFactory"); bpmnAutoResizeProvider.canResize = () => false; + const definitions = modeler.getDefinitions(); + const rootElement = getRootProcess(definitions); const serviceTasks = elementRegistry.filter(({ businessObject }) => isDeployableServiceTask(businessObject) ); + let onDemandPolicies = []; + for (const flowElement of rootElement.flowElements) { + if (flowElement.$type === consts.ON_DEMAND_POLICY) { + onDemandPolicies.push(elementRegistry.get(flowElement.id)); + } + } + modeling.removeElements(onDemandPolicies); for (const serviceTask of serviceTasks) { - let deploymentModelUrl = serviceTask.businessObject.get( - "opentosca:deploymentModelUrl" - ); - if (deploymentModelUrl.startsWith("{{ wineryEndpoint }}")) { - deploymentModelUrl = deploymentModelUrl.replace( - "{{ wineryEndpoint }}", - config.getWineryEndpoint() + let onDemand = serviceTask.businessObject.get("onDemand"); + if (onDemand) { + let deploymentModelUrl = serviceTask.businessObject.get( + "opentosca:deploymentModelUrl" ); - } + if (deploymentModelUrl.startsWith("{{ wineryEndpoint }}")) { + deploymentModelUrl = deploymentModelUrl.replace( + "{{ wineryEndpoint }}", + config.getWineryEndpoint() + ); + } - const extensionElements = serviceTask.businessObject.extensionElements; + const extensionElements = serviceTask.businessObject.extensionElements; - let subProcess = bpmnReplace.replaceElement(serviceTask, { - type: "bpmn:SubProcess", - }); + let subProcess = bpmnReplace.replaceElement(serviceTask, { + type: "bpmn:SubProcess", + }); - subProcess.businessObject.set("opentosca:onDemandDeployment", true); - subProcess.businessObject.set( - "opentosca:deploymentModelUrl", - deploymentModelUrl - ); + subProcess.businessObject.set("opentosca:onDemandDeployment", true); + subProcess.businessObject.set( + "opentosca:deploymentModelUrl", + deploymentModelUrl + ); - const startEvent = modeling.createShape( - { - type: "bpmn:StartEvent", - }, - { x: 200, y: 200 }, - subProcess - ); + 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"); + 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 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 } - ); + 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; + 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 newExtensionElements = createElement( + "bpmn:ExtensionElements", + { values }, + serviceTask2.businessObject, + bpmnFactory + ); + subProcess.businessObject.set("extensionElements", undefined); + serviceTask3.businessObject.set( + "extensionElements", + newExtensionElements + ); + } + modeling.appendShape( + serviceTask3, + { + type: "bpmn:EndEvent", + }, + { x: 1000, y: 200 }, + subProcess ); } - modeling.appendShape( - serviceTask3, - { - type: "bpmn:EndEvent", - }, - { x: 1000, y: 200 }, - subProcess - ); } // layout diagram after successful transformation diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/Policy.svg b/components/bpmn-q/modeler-component/extensions/opentosca/resources/Policy.svg new file mode 100644 index 00000000..ea754d5e --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/resources/Policy.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/clouddeploymentmodel_palette.svg b/components/bpmn-q/modeler-component/extensions/opentosca/resources/clouddeploymentmodel_palette.svg new file mode 100644 index 00000000..389eefa9 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/resources/clouddeploymentmodel_palette.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/dedicatedHosting_policy_palette.svg b/components/bpmn-q/modeler-component/extensions/opentosca/resources/dedicatedHosting_policy_palette.svg new file mode 100644 index 00000000..4f52b3a7 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/resources/dedicatedHosting_policy_palette.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/deployment_policy_palette.svg b/components/bpmn-q/modeler-component/extensions/opentosca/resources/deployment_policy_palette.svg new file mode 100644 index 00000000..4251bf0b --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/resources/deployment_policy_palette.svg @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/location_policy_palette.svg b/components/bpmn-q/modeler-component/extensions/opentosca/resources/location_policy_palette.svg new file mode 100644 index 00000000..361296e1 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/resources/location_policy_palette.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/resources/opentosca4bpmn.json b/components/bpmn-q/modeler-component/extensions/opentosca/resources/opentosca4bpmn.json index dd216089..20c32939 100644 --- a/components/bpmn-q/modeler-component/extensions/opentosca/resources/opentosca4bpmn.json +++ b/components/bpmn-q/modeler-component/extensions/opentosca/resources/opentosca4bpmn.json @@ -15,12 +15,66 @@ "isAttr": true, "type": "String" }, + { + "name": "onDemand", + "isAttr": true, + "type": "String" + }, { "name": "connectorUrl", "isAttr": true, "type": "String" } ] + }, + { + "name": "CloudDeploymentModelPolicy", + "superClass": ["bpmn:Event"], + "properties": [ + { + "name": "cloudType", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "OnDemandPolicy", + "superClass": ["bpmn:Event"], + "properties": [ + { + "name": "onDemand", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "DedicatedHostingPolicy", + "superClass": ["bpmn:Event"], + "properties": [ + { + "name": "dedicatedHosting", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "LocationPolicy", + "superClass": ["bpmn:Event"], + "properties": [ + { + "name": "location", + "isAttr": true, + "type": "String" + } + ] + }, + { + "name": "Policy", + "superClass": ["bpmn:Event"], + "properties": [] } ], "enumerations": [], diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/styling/opentosca.css b/components/bpmn-q/modeler-component/extensions/opentosca/styling/opentosca.css index b01ed139..4a040371 100644 --- a/components/bpmn-q/modeler-component/extensions/opentosca/styling/opentosca.css +++ b/components/bpmn-q/modeler-component/extensions/opentosca/styling/opentosca.css @@ -123,3 +123,58 @@ background-size: contain; float: left; } + +.qwm .opentosca-icon-policy-palette:before { + content: ""; + width: 30px; + height: 30px; + margin-top: 5px; + background-size: contain; + background-image: url("../resources/Policy.svg"); + background-repeat: no-repeat; + display: inline-block; +} + +.qwm .opentosca-icon-policy-palette:hover { + filter: invert(0.5) sepia(1) hue-rotate(165deg) saturate(4) brightness(1); +} + +.qwm .bpmn-icon-cloud-deployment-model-policy:before { + content: ""; + width: 25px; + height: 15px; + background-size: contain; + background-image: url("../resources/clouddeploymentmodel_palette.svg"); + background-repeat: no-repeat; + display: inline-block; +} + +.qwm .bpmn-icon-location-policy:before { + content: ""; + width: 25px; + height: 15px; + background-size: contain; + background-image: url("../resources/location_policy_palette.svg"); + background-repeat: no-repeat; + display: inline-block; +} + +.qwm .bpmn-icon-dedicated-hosting-policy:before { + content: ""; + width: 25px; + height: 15px; + background-size: contain; + background-image: url("../resources/dedicatedHosting_policy_palette.svg"); + background-repeat: no-repeat; + display: inline-block; +} + +.qwm .bpmn-icon-deployment-policy:before { + content: ""; + width: 25px; + height: 15px; + background-size: contain; + background-image: url("../resources/deployment_policy_palette.svg"); + background-repeat: no-repeat; + display: inline-block; +} diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/DeploymentPlugin.js b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/DeploymentPlugin.js index 57639568..68905be8 100644 --- a/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/DeploymentPlugin.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/DeploymentPlugin.js @@ -15,6 +15,7 @@ import React, { Fragment, PureComponent } from "react"; import ServiceDeploymentOverviewModal from "./ServiceDeploymentOverviewModal"; import ServiceDeploymentInputModal from "./ServiceDeploymentInputModal"; import ServiceDeploymentBindingModal from "./ServiceDeploymentBindingModal"; +import ServiceOnDemandDeploymentOverviewModal from "./ServiceOnDemandDeploymentOverviewModal"; import { createServiceInstance, @@ -30,8 +31,11 @@ import { getModeler } from "../../../../../editor/ModelerHandler"; import NotificationHandler from "../../../../../editor/ui/notifications/NotificationHandler"; import { getRootProcess } from "../../../../../editor/util/ModellingUtilities"; import ExtensibleButton from "../../../../../editor/ui/ExtensibleButton"; +import { loadDiagram } from "../../../../../editor/util/IoUtilities"; +import { startOnDemandReplacementProcess } from "../../../replacement/OnDemandTransformator"; const defaultState = { + windowOpenOnDemandDeploymentOverview: false, windowOpenDeploymentOverview: false, windowOpenDeploymentInput: false, windowOpenDeploymentBinding: false, @@ -49,6 +53,8 @@ export default class DeploymentPlugin extends PureComponent { this.handleDeploymentInputClosed.bind(this); this.handleDeploymentBindingClosed = this.handleDeploymentBindingClosed.bind(this); + this.handleOnDemandDeploymentClosed = + this.handleOnDemandDeploymentClosed.bind(this); } componentDidMount() { @@ -75,6 +81,34 @@ export default class DeploymentPlugin extends PureComponent { } } + /** + * Handle result of the on demand deployment dialog + * + * @param result the result from the dialog + */ + async handleOnDemandDeploymentClosed(result) { + if (result && result.hasOwnProperty("onDemand")) { + let xml = (await this.modeler.saveXML({ format: true })).xml; + if (result.onDemand) { + xml = await startOnDemandReplacementProcess(xml); + loadDiagram(xml, this.modeler); + this.setState({ + windowOpenOnDemandDeploymentOverview: false, + windowOpenDeploymentOverview: true, + windowOpenDeploymentInput: false, + windowOpenDeploymentBinding: false, + }); + } + } else { + this.setState({ + windowOpenOnDemandDeploymentOverview: false, + windowOpenDeploymentOverview: false, + windowOpenDeploymentInput: false, + windowOpenDeploymentBinding: false, + }); + } + } + /** * Handle the result of a close operation on the deployment overview modal * @@ -425,7 +459,7 @@ export default class DeploymentPlugin extends PureComponent { className="qwm-toolbar-btn" title="Open service deployment menu" onClick={() => - this.setState({ windowOpenDeploymentOverview: true }) + this.setState({ windowOpenOnDemandDeploymentOverview: true }) } > @@ -434,10 +468,18 @@ export default class DeploymentPlugin extends PureComponent { , ]} /> + {this.state.windowOpenOnDemandDeploymentOverview && ( + + )} {this.state.windowOpenDeploymentOverview && ( )} {this.state.windowOpenDeploymentInput && ( diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentBindingModal.js b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentBindingModal.js index 6ddc57ab..e3619c53 100644 --- a/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentBindingModal.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentBindingModal.js @@ -59,7 +59,7 @@ export default function ServiceDeploymentBindingModal({ onClose, initValues }) { return ( - Service Deployment (3/3) + Service Deployment (4/4)

diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentInputModal.js b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentInputModal.js index e0b59702..6be2841c 100644 --- a/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentInputModal.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentInputModal.js @@ -220,7 +220,7 @@ export default function ServiceDeploymentInputModal({ onClose, initValues }) { return ( - Service Deployment (2/3) + Service Deployment (3/4)

diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentOverviewModal.js b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentOverviewModal.js index 52ce237d..e3e45e58 100644 --- a/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentOverviewModal.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceDeploymentOverviewModal.js @@ -22,6 +22,7 @@ const Footer = Modal.Footer || (({ children }) =>
{children}
); export default function ServiceDeploymentOverviewModal({ onClose, initValues, + elementRegistry, }) { // close if no deployment required if (!initValues || initValues.length === 0) { @@ -43,7 +44,22 @@ export default function ServiceDeploymentOverviewModal({ }, }); - const listItems = initValues.map((CSAR) => ( + const filteredInitValues = initValues.filter((CSAR) => + CSAR.serviceTaskIds.some((taskId) => { + const taskData = elementRegistry.get(taskId); + return taskData && !taskData.businessObject.onDemand; + }) + ); + // Modify the filteredInitValues to update the serviceTaskIds + const updatedInitValues = filteredInitValues.map((CSAR) => { + const updatedServiceTaskIds = CSAR.serviceTaskIds.filter((taskId) => { + const taskData = elementRegistry.get(taskId); + return taskData && !taskData.businessObject.onDemand; + }); + return { ...CSAR, serviceTaskIds: updatedServiceTaskIds }; + }); + + const listItems = updatedInitValues.map((CSAR) => ( {CSAR.csarName} {CSAR.serviceTaskIds.join(",")} @@ -54,7 +70,7 @@ export default function ServiceDeploymentOverviewModal({ return ( - Service Deployment (1/3) + Service Deployment (2/4)

diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceOnDemandDeploymentOverviewModal.js b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceOnDemandDeploymentOverviewModal.js new file mode 100644 index 00000000..8f521772 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/opentosca/ui/deployment/services/ServiceOnDemandDeploymentOverviewModal.js @@ -0,0 +1,110 @@ +/** + * 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 "../../../../../editor/ui/modal/Modal"; + +const Title = Modal.Title || (({ children }) =>

{children}

); +const Body = Modal.Body || (({ children }) =>
{children}
); +const Footer = Modal.Footer || (({ children }) =>
{children}
); + +export default function ServiceOnDemandDeploymentOverviewModal({ + onClose, + initValues, + elementRegistry, +}) { + // close if no deployment required + if (!initValues || initValues.length === 0) { + onClose(); + } + + let footerRef = React.createRef(); + + const onDemand = (value) => + onClose({ + onDemand: value, + }); + const filteredInitValues = initValues.filter((CSAR) => + CSAR.serviceTaskIds.some((taskId) => { + const taskData = elementRegistry.get(taskId); + return taskData && taskData.businessObject.onDemand; + }) + ); + // Modify the filteredInitValues to update the serviceTaskIds + const updatedInitValues = filteredInitValues.map((CSAR) => { + const updatedServiceTaskIds = CSAR.serviceTaskIds.filter((taskId) => { + const taskData = elementRegistry.get(taskId); + return taskData && taskData.businessObject.onDemand; + }); + return { ...CSAR, serviceTaskIds: updatedServiceTaskIds }; + }); + + const listItems = updatedInitValues.map((CSAR) => ( + + {CSAR.csarName} + {CSAR.serviceTaskIds.join(",")} + {CSAR.type} + + )); + + return ( + + Service Deployment (1/4) + + + {listItems.length > 0 ? ( + <> +

+ The following service-tasks contain on-demand deployment models: +

+ + + + + + + + {listItems} + +
CSAR NameRelated ServiceTask IDsType (Push/Pull)
+ + ) : ( +

+ No service tasks with on-demand deployment models are contained in + the workflow. +

+ )} + + +
+
+ + +
+
+
+ ); +} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/QuantMEReplaceMenuProvider.js b/components/bpmn-q/modeler-component/extensions/quantme/modeling/QuantMEReplaceMenuProvider.js index 2b730990..3e281825 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/modeling/QuantMEReplaceMenuProvider.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/modeling/QuantMEReplaceMenuProvider.js @@ -87,7 +87,6 @@ export default class QuantMEReplaceMenuProvider { ); return Object.assign(subprocessEntries, entries); } - return entries; }; } diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/QuantMETaskProperties.js b/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/QuantMETaskProperties.js index b21e1c99..f2e8309b 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/QuantMETaskProperties.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/modeling/properties-provider/QuantMETaskProperties.js @@ -3,8 +3,9 @@ import { isTextFieldEntryEdited, isSelectEntryEdited, } from "@bpmn-io/properties-panel"; -import { AlgorithmEntry, AlphaEntry } from "./QuantMEPropertyEntries.js"; import { + AlgorithmEntry, + AlphaEntry, CalibrationMethodEntry, DNNHiddenLayersEntry, EncodingSchemaEntry, diff --git a/components/bpmn-q/modeler-component/extensions/quantme/resources/DeploymentPolicy_Icon.svg b/components/bpmn-q/modeler-component/extensions/quantme/resources/DeploymentPolicy_Icon.svg new file mode 100644 index 00000000..68157c3c --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/quantme/resources/DeploymentPolicy_Icon.svg @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/bpmn-q/modeler-component/extensions/quantme/resources/PrivacyPolicy_Icon.svg b/components/bpmn-q/modeler-component/extensions/quantme/resources/PrivacyPolicy_Icon.svg new file mode 100644 index 00000000..21dac3f6 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/quantme/resources/PrivacyPolicy_Icon.svg @@ -0,0 +1,166 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/bpmn-q/modeler-component/extensions/quantme/resources/policy.svg b/components/bpmn-q/modeler-component/extensions/quantme/resources/policy.svg new file mode 100644 index 00000000..ea754d5e --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/quantme/resources/policy.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +