diff --git a/components/bpmn-q/modeler-component/extensions/opentosca/deployment/WineryUtils.js b/components/bpmn-q/modeler-component/extensions/opentosca/deployment/WineryUtils.js index dc1a6bf81..cb5e84d4b 100644 --- a/components/bpmn-q/modeler-component/extensions/opentosca/deployment/WineryUtils.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/deployment/WineryUtils.js @@ -208,7 +208,9 @@ export async function addNodeWithArtifactToServiceTemplateByName( name, artifactTemplateQName, artifactName, - artifactTypeQName + artifactTypeQName, + requirementTypes, + kvProperties ) { const serviceTemplateAddress = encodeURIComponent(encodeURIComponent(QUANTME_NAMESPACE_PUSH)) + @@ -221,7 +223,9 @@ export async function addNodeWithArtifactToServiceTemplateByName( name, artifactTemplateQName, artifactName, - artifactTypeQName + artifactTypeQName, + requirementTypes, + kvProperties ); return serviceTemplateAddress; } @@ -288,7 +292,9 @@ export async function addNodeWithArtifactToServiceTemplate( name, artifactTemplateQName, artifactName, - artifactTypeQName + artifactTypeQName, + requirementTypes, + kvProperties ) { const nodeTemplate = { documentation: [], @@ -305,10 +311,7 @@ export async function addNodeWithArtifactToServiceTemplate( }, properties: { propertyType: "KV", - kvproperties: { - Port: "", - Name: "", - }, + kvproperties: kvProperties, elementName: "properties", namespace: "http://opentosca.org/nodetypes/propertiesdefinition/winery", @@ -321,7 +324,7 @@ export async function addNodeWithArtifactToServiceTemplate( x: 1245, y: 350, capabilities: [], - requirements: [], + requirements: requirementTypes, deploymentArtifacts: [ { documentation: [], @@ -391,16 +394,23 @@ export async function createServiceTemplateWithNodeAndArtifact( nodeName, artifactTemplateQName, artifactName, - artifactTypeQName + artifactTypeQName, + requirementTypes, + kvProperties ) { - const serviceTemplateAddress = await createServiceTemplate(name); + const serviceTemplateAddress = await createServiceTemplate( + name, + "http://quantil.org/quantme/push" + ); await addNodeWithArtifactToServiceTemplate( serviceTemplateAddress, nodeTypeQName, nodeName, artifactTemplateQName, artifactName, - artifactTypeQName + artifactTypeQName, + requirementTypes, + kvProperties ); return serviceTemplateAddress; } @@ -436,10 +446,41 @@ const nodeTypeQNameMapping = new Map([ "{http://opentosca.org/nodetypes}PythonApp_3-w1", ], ]); + +const artifactTypeKVMapping = new Map([ + [ + "WAR", + { + Port: "", + Name: "", + }, + ], + [ + "Python", + { + Port: "", + Name: "", + }, + ], + [ + "Flask", + { + StartupCommand: + "export FLASK_APP=app.py && export FLASK_ENV=development && export FLASK_DEBUG=0 && python3 -m flask run --host=0.0.0.0", + Name: "app", + }, + ], +]); + export function getNodeTypeQName(artifactTypeQName) { return nodeTypeQNameMapping.get(artifactTypeQName); } +export function getArtifactTypeKVMapping(artifactType) { + console.log("retrieving kv mapping for type", artifactType); + return artifactTypeKVMapping.get(artifactType); +} + export async function loadTopology(deploymentModelUrl) { if (deploymentModelUrl.startsWith("{{ wineryEndpoint }}")) { deploymentModelUrl = deploymentModelUrl.replace( 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 index 501c20159..4e0e48c8c 100644 --- 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 @@ -18,6 +18,7 @@ import { createArtifactTemplateWithFile, createServiceTemplateWithNodeAndArtifact, getNodeTypeQName, + getArtifactTypeKVMapping, getArtifactTemplateInfo, insertTopNodeTag, serviceTemplateExists, @@ -54,6 +55,9 @@ export default function ArtifactUploadModal({ const [selectedOption, setSelectedOption] = useState(""); const [selectedOptionName, setSelectedOptionName] = useState(""); const [artifactTypes, setArtifactTypes] = useState([]); + const [requirementTypes, setRequirementTypes] = useState([]); + const [kvProperties, setKvProperties] = useState({}); + const [isFlask, setIsFlask] = useState(false); const [acceptTypes, setAcceptTypes] = useState(""); async function updateArtifactSelect() { @@ -101,9 +105,22 @@ export default function ArtifactUploadModal({ artifactTemplateAddress ); const artifactTemplateQName = + "{" + + artifactTemplateInfo.targetNamespace + + "}" + artifactTemplateInfo - .serviceTemplateOrNodeTypeOrNodeTypeImplementation[0].type; - const nodeTypeQName = getNodeTypeQName(selectedOption); + .serviceTemplateOrNodeTypeOrNodeTypeImplementation[0].id; + // artifactTemplateInfo.serviceTemplateOrNodeTypeOrNodeTypeImplementation[0].type; + let nodeTypeQName = getNodeTypeQName(selectedOption); + if ( + isFlask && + nodeTypeQName === "{http://opentosca.org/nodetypes}PythonApp_3-w1" + ) { + nodeTypeQName = + "{https://ust-quantil.github.io/nodetypes}QuokkaPythonApp_latest-w1-wip1"; + } + console.log("nodetypeqname", nodeTypeQName); + console.log("kvproperties", kvProperties); const serviceTemplateName = `${namePrefix}ServiceTemplate-${element.id}`; const doesExist = await serviceTemplateExists(serviceTemplateName); console.log("doesExist", doesExist); @@ -115,7 +132,9 @@ export default function ArtifactUploadModal({ `${namePrefix}Node-${element.id}`, artifactTemplateQName, `${namePrefix}Artifact-${element.id}`, - selectedOption + selectedOption, + requirementTypes, + kvProperties ); await deleteTopNodeTag(serviceTemplateAddress); } else { @@ -125,7 +144,9 @@ export default function ArtifactUploadModal({ `${namePrefix}Node-${element.id}`, artifactTemplateQName, `${namePrefix}Artifact-${element.id}`, - selectedOption + selectedOption, + requirementTypes, + kvProperties ); } await insertTopNodeTag(serviceTemplateAddress, nodeTypeQName); @@ -192,12 +213,36 @@ export default function ArtifactUploadModal({ setSelectedOptionName(artifactTypes.find((x) => x.qName === value).name); if (value.includes("WAR")) { setAcceptTypes(allowedFileTypes.war); + setRequirementTypes([]); + setKvProperties(getArtifactTypeKVMapping("WAR")); } else if (value.includes("PythonArchive")) { setAcceptTypes(allowedFileTypes.zip); + const pythonReq = { + name: "canHostPythonApp", + id: "req1", + type: "{https://ust-quantil.github.io/requirementtypes}ReqCanInstallQiskit", + }; + setRequirementTypes([pythonReq]); + setKvProperties(getArtifactTypeKVMapping("Python")); } + console.log("handle dropdown change"); }; const isOptionSelected = selectedOption !== ""; + const isPythonArchive = selectedOption.includes("PythonArchive"); + + const handleIsFlaskCheckboxChange = () => { + setIsFlask(!isFlask); + let artifactType = ""; + if (isFlask) { + // For some reason this works the other way round as expected + artifactType = "Python"; + } else { + artifactType = "Flask"; + } + console.log("artifacttype", artifactType); + setKvProperties(getArtifactTypeKVMapping(artifactType)); + }; if (artifactTypes.length === 0) { updateArtifactSelect(); @@ -252,6 +297,20 @@ export default function ArtifactUploadModal({ ))} + + {isOptionSelected && isPythonArchive && ( +
+
+ + +
+
+ )} {isOptionSelected && (
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 index 6c2416415..a488e732c 100644 --- 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 @@ -93,15 +93,21 @@ export function Connector({ element, translate, filteredUrls, methodUrlList }) { const setMethodValue = 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 acceptEntry = moddle.create("camunda:Entry", { + key: "Accept", + value: "application/json", + }); + const contentEntry = moddle.create("camunda:Entry", { + key: "Content-Type", + value: "application/json", + }); - const map = moddle.create("camunda:Map", { entries: [entry] }); + const map = moddle.create("camunda:Map", { + entries: [acceptEntry, contentEntry], + }); - entry.$parent = map; + acceptEntry.$parent = map; + contentEntry.$parent = map; const headersInputParameter = moddle.create("camunda:InputParameter", { definition: map, @@ -110,7 +116,7 @@ export function Connector({ element, translate, filteredUrls, methodUrlList }) { const methodInputParameter = moddle.create("camunda:InputParameter", { name: "method", - value: value, + value: value.toUpperCase(), }); const urlInputParameter = moddle.create("camunda:InputParameter", { name: "url", @@ -126,7 +132,6 @@ export function Connector({ element, translate, filteredUrls, methodUrlList }) { const script = moddle.create("camunda:Script", { scriptFormat: "Groovy", value: scriptValue, - resource: "Inline", }); const payloadInputParameter = moddle.create("camunda:InputParameter", { @@ -140,8 +145,11 @@ export function Connector({ element, translate, filteredUrls, methodUrlList }) { inputParameters.push(payloadInputParameter); let outputParameters = determineOutputParameters( - element.businessObject.yaml + element.businessObject.yaml, + element.businessObject.connectorUrl, + value ); + let camundaOutputParameters = constructCamundaOutputParameters(outputParameters); @@ -245,88 +253,85 @@ function determineInputParameters(yamlData, schemePath, method) { } } -function determineOutputParameters(yamlData) { - // Parse the YAML data +function determineOutputParameters(yamlData, schemePath, method) { 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.values(data.paths)) { - console.log(data.paths); - for (const details of Object.values(methods)) { - console.log(details); - if (details.responses) { - const response = details.responses; - // Access the properties of the schema - // Access the schema referenced by "200" - const statusCode = "200"; - let responseStatusCode = statusCode; - - if (response[statusCode] === undefined) { - // If the response for the specified status code is not defined - // Find another response with a status code starting with 2 - responseStatusCode = Object.keys(response).find((code) => - code.startsWith("2") - ); - console.log(responseStatusCode); - } + const methods = data.paths[schemePath]; + if (!methods || !methods[method]) return []; - if (responseStatusCode !== undefined) { - let schema = - response[responseStatusCode].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 - // eslint-disable-next-line no-inner-declarations - 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); - } - } else { - return []; + const details = methods[method]; + + if (details.responses) { + const response = details.responses; + let responseStatusCode = "200"; + + if (!response[responseStatusCode]) { + responseStatusCode = Object.keys(response).find((code) => + code.startsWith("2") + ); + } + + if (responseStatusCode && response[responseStatusCode]) { + let schema = + response[responseStatusCode].content?.["application/json"]?.schema; + if (!schema) return []; + + if (schema.$ref) { + const schemaPath = schema.$ref.replace("#/", "").replaceAll("/", "."); + schema = getObjectByPath(yaml.load(yamlData), schemaPath); + } + + if (schema?.properties) { + outputParameters = Object.keys(schema.properties); } } } + return outputParameters; + + function getObjectByPath(obj, path) { + return path.split(".").reduce((o, key) => (o ? o[key] : undefined), obj); + } } function constructCamundaOutputParameters(parameters) { let outputParameters = []; for (let param of parameters) { let moddle = getModeler().get("moddle"); - const script = moddle.create("camunda:Script", { - scriptFormat: "Groovy", - value: + let scriptContent; + if (param === "visualization") { + scriptContent = + "import org.camunda.bpm.engine.variable.value.FileValue\n" + + "import org.camunda.bpm.engine.variable.Variables\n" + + "import groovy.json.JsonSlurper\n" + + 'def slurper = new JsonSlurper().parseText(connector.getVariable("response"))\n' + + 'String filename = "circuit.png"\n' + + "FileValue typedFileValue = Variables.fileValue(filename)\n" + + " .file(slurper.visualization.decodeBase64())\n" + + ' .mimeType("image/png")\n' + + " .create()\n" + + "return typedFileValue"; + } else { + scriptContent = 'def resp = connector.getVariable("response");\n' + "resp = new groovy.json.JsonSlurper().parseText(resp);\n" + "def " + param + - " = resp.get(" + + ' = resp.get("' + param + - ");\n" + + '");\n' + "println(" + param + ");\n" + "return " + param + - ";", - resource: "Inline", + ";"; + } + + const script = moddle.create("camunda:Script", { + scriptFormat: "Groovy", + value: scriptContent, }); const outputParameter = moddle.create("camunda:OutputParameter", { 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 index f63ac4ee3..cd6d36024 100644 --- 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 @@ -101,6 +101,13 @@ text-overflow: ellipsis; } +.set-flask { + 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/replacement/OnDemandTransformator.js b/components/bpmn-q/modeler-component/extensions/opentosca/replacement/OnDemandTransformator.js index 3cc053ca3..6e4e193d8 100644 --- a/components/bpmn-q/modeler-component/extensions/opentosca/replacement/OnDemandTransformator.js +++ b/components/bpmn-q/modeler-component/extensions/opentosca/replacement/OnDemandTransformator.js @@ -199,8 +199,20 @@ try { println post; println post.getInputStream(); def location = post.getHeaderFields()['Location'][0]; + println("retrieved location with possibly wrong ip and port: " + location); + + // Parse both URLs + def correctBase = new URL(url).getProtocol() + "://" + new URL(url).getAuthority() + def brokenPath = new URL(location).getPath() + + // Construct the fixed URL + def fixedLocation= correctBase + brokenPath + println("Fixed location of completed deployment model: "+ fixedLocation) + + def saveVarName = "completeModelUrl_" + "${taskId}"; - execution.setVariable(saveVarName, location); + execution.setVariable(saveVarName, fixedLocation); + println("Set completed deploymentmodel location with variable name: " + saveVarName + " and value: " + fixedLocation); }else{ throw new org.camunda.bpm.engine.delegate.BpmnError("Received status code " + status + " while completing Deployment Model!"); } @@ -219,6 +231,7 @@ function createCheckForEquivalencyScript(taskId) { import groovy.json.* def url = execution.getVariable("completeModelUrl_" + "${taskId}"); url = url + "topologytemplate/checkforequivalentcsars?includeSelf=true" +println("Check for equivalent Csars at: " + url); try { def post = new URL(url).openConnection(); @@ -239,7 +252,7 @@ try { def saveVarName = "equivalentCSARs_" + "${taskId}"; execution.setVariable(saveVarName, json); }else{ - throw new org.camunda.bpm.engine.delegate.BpmnError("Received status code " + status + " while completing Deployment Model!"); + throw new org.camunda.bpm.engine.delegate.BpmnError("Received status code " + status + " while checking for equivalent Deployment Model!"); } } catch(org.camunda.bpm.engine.delegate.BpmnError e) { println e.errorCode; 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 91e8949e7..10bd26a86 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 @@ -497,7 +497,7 @@ export default class DeploymentPlugin extends PureComponent { ); // complete CSAR and refresh meta data - const locationOfCompletedCSAR = completeIncompleteDeploymentModel( + let locationOfCompletedCSAR = completeIncompleteDeploymentModel( csar.url, blacklistedNodetypes, policies @@ -527,7 +527,10 @@ export default class DeploymentPlugin extends PureComponent { .split("/") .filter((x) => x.length > 1) .pop(); - csar.url = locationOfCompletedCSAR + "?csar"; + csar.url = + getWineryEndpoint() + + locationOfCompletedCSAR.split("/winery").pop() + + "?csar"; csar.csarName = nameOfCompletedCSAR + ".csar"; csar.incomplete = false; console.log("Completed CSAR. New name: ", csar.csarName);