From 16d034b8e209172737d54ecfd2e83b4464783e60 Mon Sep 17 00:00:00 2001 From: frikky Date: Tue, 4 May 2021 17:35:03 +0200 Subject: [PATCH] Added more descriptive features to workflows --- backend/go-app/main.go | 22 +- frontend/package.json | 1 + frontend/src/components/ParsedAction.jsx | 34 +- frontend/src/defaultCytoscapeStyle.js | 13 +- frontend/src/views/Admin.jsx | 2 +- frontend/src/views/AngularWorkflow.jsx | 780 +++++++++++++++++++---- frontend/src/views/AppCreator.jsx | 20 +- 7 files changed, 723 insertions(+), 149 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 7cae58e8..6298f9cf 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -5181,16 +5181,34 @@ func runInit(ctx context.Context) { log.Printf("Failed setting up new environment") } } else if len(activeOrgs) == 1 { - log.Printf("Setting up all environments with org %s", activeOrgs[0].Id) + log.Printf("[INFO] Setting up all environments with org %s", activeOrgs[0].Id) var environments []shuffle.Environment q := datastore.NewQuery("Environments") _, err = dbclient.GetAll(ctx, q, &environments) if err == nil { + existingEnv := []string{} + _ = existingEnv for _, item := range environments { - if item.OrgId == activeOrgs[0].Id { + //if shuffle.ArrayContains(existingEnv, item.Name) { + // log.Printf("[WARNING] Env %s already exists - deleting it. %#v", item.Name, item) + // err = DeleteKey(ctx, "Environments", item.Name) + // if err != nil { + // log.Printf("[WARNING] Env deletion error: %s", err) + // } + + // continue + //} + + //existingEnv = append(existingEnv, item.Name) + + if item.OrgId == activeOrgs[0].Id && len(item.Id) > 0 { continue } + if len(item.Id) == 0 { + item.Id = uuid.NewV4().String() + } + item.OrgId = activeOrgs[0].Id err = setEnvironment(ctx, &item) if err != nil { diff --git a/frontend/package.json b/frontend/package.json index be560034..26dff61a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,6 +4,7 @@ "version": "0.8.76", "private": true, "dependencies": { + "@babel/helper-regex": "^7.10.5", "@material-ui/core": "^4.5.2", "@material-ui/data-grid": "^4.0.0-alpha.22", "@material-ui/icons": "^4.5.1", diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 4c65e764..f906e815 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -492,6 +492,7 @@ const ParsedAction = (props) => { // FIXME: Issue #40 - selectedActionParameters not reset if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { + var authWritten = false return (
Parameters @@ -509,8 +510,17 @@ const ParsedAction = (props) => { selectedAction.parameters[count].value = selectedAction.selectedAuthentication.fields[data.name] setSelectedAction(selectedAction) //setUpdate(Math.random()) + + if (authWritten) { + return null + } - return null + authWritten = true + return ( + + Authentication fields are hidden + + ) } var staticcolor = "inherit" @@ -550,6 +560,7 @@ const ParsedAction = (props) => { console.log("GENERATED WITH DATA: ", data) return null } + if (selectedApp.generated && data.name === "body") { const regex = /\${(\w+)}/g const found = placeholder.match(regex) @@ -747,7 +758,7 @@ const ParsedAction = (props) => { changeActionParameter(e, count, data) setUpdate(Math.random()) }} - style={{backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px"}} + style={{backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px", borderRadius: theme.palette.borderRadius,}} > {selectedActionParameters[count].options.map((data, index) => { const split_data = data.split("||") @@ -1086,7 +1097,7 @@ const ParsedAction = (props) => { onClick={() => setShowAutocomplete(true)} fullWidth open={showAutocomplete} - style={{border: `2px solid #f85a3e`, color: "white", height: 50, marginTop: 2,}} + style={{border: `2px solid #f85a3e`, color: "white", height: 50, marginTop: 2, borderRadius: theme.palette.borderRadius,}} onChange={(e) => { if (selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === ".") { e.target.value.autocomplete = e.target.value.autocomplete.slice(1, e.target.value.autocomplete.length) @@ -1168,7 +1179,7 @@ const ParsedAction = (props) => { - What are actions? + What are actions? {selectedAction.errors !== undefined && selectedAction.errors !== null && selectedAction.errors.length > 0 ?
Errors: {selectedAction.errors.join("\n")} @@ -1197,7 +1208,7 @@ const ParsedAction = (props) => { getApp(newversion.id, true) } }} - style={{marginTop: 10, backgroundColor: theme.palette.surfaceColor, backgroundColor: theme.palette.inputColor, color: "white", height: 35, marginleft: 10,}} + style={{marginTop: 10, backgroundColor: theme.palette.surfaceColor, backgroundColor: theme.palette.inputColor, color: "white", height: 35, marginleft: 10, borderRadius: theme.palette.borderRadius,}} SelectDisplayProps={{ style: { marginLeft: 10, @@ -1265,7 +1276,7 @@ const ParsedAction = (props) => { setSelectedAction(selectedAction) setUpdate(Math.random()) }} - style={{backgroundColor: theme.palette.inputColor, color: "white", height: 50, maxWidth: rightsidebarStyle.maxWidth-80,}} + style={{backgroundColor: theme.palette.inputColor, color: "white", height: 50, maxWidth: rightsidebarStyle.maxWidth-80, borderRadius: theme.palette.borderRadius,}} > {selectedAction.authentication.map(data => { //console.log("AUTH DATA: ", data) @@ -1295,6 +1306,7 @@ const ParsedAction = (props) => {
: null} + {showEnvironment !== undefined && showEnvironment ?
@@ -1315,7 +1327,7 @@ const ParsedAction = (props) => { selectedAction.environment = env.Name setSelectedAction(selectedAction) }} - style={{backgroundColor: theme.palette.inputColor, color: "white", height: "50px"}} + style={{backgroundColor: theme.palette.inputColor, color: "white", height: "50px", borderRadius: theme.palette.borderRadius,}} > {environments.map(data => { if (data.archived) { @@ -1331,6 +1343,7 @@ const ParsedAction = (props) => {
: null} + {workflow.execution_variables !== undefined && workflow.execution_variables !== null && workflow.execution_variables.length > 0 ?
Set execution variable (optional) @@ -1352,7 +1365,7 @@ const ParsedAction = (props) => { setSelectedAction(selectedAction) setUpdate(Math.random()) }} - style={{backgroundColor: theme.palette.inputColor, color: "white", height: "50px"}} + style={{backgroundColor: theme.palette.inputColor, color: "white", height: "50px", borderRadius: theme.palette.borderRadius,}} > No selection @@ -1366,6 +1379,7 @@ const ParsedAction = (props) => {
: null} +
{hideExtraTypes ? null : @@ -1373,6 +1387,7 @@ const ParsedAction = (props) => { Actions
} + {setNewSelectedAction !== undefined ? : null} + {selectedAction.description !== undefined && selectedAction.description.length > 0 && hideExtraTypes !== true ?
{selectedAction.description}
: null} +
diff --git a/frontend/src/defaultCytoscapeStyle.js b/frontend/src/defaultCytoscapeStyle.js index fb3a84d5..c24300ba 100644 --- a/frontend/src/defaultCytoscapeStyle.js +++ b/frontend/src/defaultCytoscapeStyle.js @@ -24,7 +24,7 @@ const data = [{ 'curve-style': 'unbundled-bezier', 'label': 'data(label)', 'text-margin-y': '-15px', - 'width': '2px', + 'width': '5px', "color": "white", "line-fill": "linear-gradient", "line-gradient-stop-positions": ["0.0", "100"], @@ -234,6 +234,17 @@ const data = [{ 'transition-duration': '0.25s', }, }, + { + selector: `edge[?decorator]`, + css: { + 'width': '1px', + 'line-style': 'dashed', + "line-fill": "linear-gradient", + 'target-arrow-color': '#f34079', + "line-gradient-stop-positions": ["0.0", "100"], + "line-gradient-stop-colors": ["#f86a3e", "#f34079"], + }, + }, { selector: 'edge.success-highlight', css: { diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 85291e1b..6f869ff5 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -1644,7 +1644,7 @@ const Admin = (props) => { } const item = selectedOrganization.sync_features[key] - const newkey = key.replace("_", " ") + const newkey = key.replaceAll("_", " ") const griditem = { "primary": newkey, "secondary": item.description === undefined || item.description === null || item.description.length === 0 ? "Not defined yet" : item.description, diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 4bfc531d..d244d8a9 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -131,6 +131,7 @@ const AngularWorkflow = (props) => { const [leftBarSize, setLeftBarSize] = React.useState(350) const [executionText, setExecutionText] = React.useState(""); const [executionRequestStarted, setExecutionRequestStarted] = React.useState(false); + const [scrollLocation, setScrollLocation] = React.useState(0) const [appAuthentication, setAppAuthentication] = React.useState([]); const [variablesModalOpen, setVariablesModalOpen] = React.useState(false); @@ -255,6 +256,7 @@ const AngularWorkflow = (props) => { if (responseJson !== undefined) { setWorkflows(responseJson) + // Sets up subflow trigger with the right info if (trigger_index > -1) { var outersub = {} const trigger = workflow.triggers[trigger_index] @@ -262,10 +264,15 @@ const AngularWorkflow = (props) => { for (var key in trigger.parameters) { const param = trigger.parameters[key] if (param.name === "workflow") { - const sub = responseJson.find(data => data.id === param.value) - if (sub !== undefined && subworkflow.id !== sub.id) { - setSubworkflow(sub) - outersub = sub + if (param.value === workflow.id) { + setSubworkflow(workflow) + outersub = workflow + } else { + const sub = responseJson.find(data => data.id === param.value) + if (sub !== undefined && subworkflow.id !== sub.id) { + setSubworkflow(sub) + outersub = sub + } } } @@ -711,6 +718,7 @@ const AngularWorkflow = (props) => { var newActions = [] var newTriggers = [] var newBranches = [] + var newVBranches = [] for (var key in cyelements) { if (cyelements[key].data === undefined) { continue @@ -728,9 +736,14 @@ const AngularWorkflow = (props) => { source_id: cyelements[key].data().source, destination_id: cyelements[key].data().target, conditions: cyelements[key].data().conditions, + decorator: cyelements[key].data().decorator, } - newBranches.push(parsedElement) + if (parsedElement.decorator) { + newVBranches.push(parsedElement) + } else { + newBranches.push(parsedElement) + } } else { if (type === "ACTION") { const cyelement = cyelements[key].data() @@ -789,6 +802,7 @@ const AngularWorkflow = (props) => { useworkflow.actions = newActions useworkflow.triggers = newTriggers useworkflow.branches = newBranches + useworkflow.visual_branches = newVBranches // Errors are backend defined useworkflow.errors = [] @@ -1125,7 +1139,7 @@ const AngularWorkflow = (props) => { //var tmpapps = [] //tmpapps = tmpapps.concat(getExtraApps()) //tmpapps = tmpapps.concat(responseJson) - //console.log("APPS: ", responseJson) + console.log("APPS: ", responseJson) setApps(responseJson) if (isCloud) { @@ -1246,6 +1260,7 @@ const AngularWorkflow = (props) => { // Can be used for right side view setRightSideBarOpen(false) + setScrollLocation(0) console.timeEnd("UNSELECT") } @@ -1258,8 +1273,13 @@ const AngularWorkflow = (props) => { const triggercheck = workflow.triggers.find(trigger => trigger.id === event.target.data()["source"]) if (triggercheck === undefined) { */ - setSelectedEdgeIndex(workflow.branches.findIndex(data => data.id === event.target.data()["id"])) - setSelectedEdge(event.target.data()) + console.log(event.target.data()) + if (event.target.data().decorator) { + alert.info("This edge can't be edited.") + } else { + setSelectedEdgeIndex(workflow.branches.findIndex(data => data.id === event.target.data()["id"])) + setSelectedEdge(event.target.data()) + } /* } else { @@ -1278,6 +1298,7 @@ const AngularWorkflow = (props) => { x: 0, y: 0, } + const onNodeDragStop = (event, selectedAction) => { const nodedata = event.target.data() @@ -1306,7 +1327,7 @@ const AngularWorkflow = (props) => { if (curElement !== null && curElement !== undefined) { console.log("ELE: ", curElement) curElement.style.border = curElement.style.original_border - const newValue = "$"+nodedata.label.toLowerCase().replace(" ", "_") + const newValue = "$"+nodedata.label.toLowerCase().replaceAll(" ", "_") curElement.value = newValue if (curElement.id.startsWith("rightside_field_")) { console.log("FOUND FIELD WITH NUMBER: ", curElement.id) @@ -1334,6 +1355,7 @@ const AngularWorkflow = (props) => { //} const nodedata = event.target.data() + console.log("Dragging node!!") if (nodedata.app_name == "Shuffle Tools" || nodedata.app_name == "Testing") { //console.log("NODE: ", //selector: `node[app_name="Shuffle Tools"]`, @@ -1654,6 +1676,7 @@ const AngularWorkflow = (props) => { "id_": edge.id, "id": edge.id, "hasErrors": false, + "decorator": false, } if (!found) { @@ -1719,7 +1742,7 @@ const AngularWorkflow = (props) => { workflow.triggers = workflow.triggers.filter(a => a.id !== data.id) if (workflow.start === data.id && workflow.actions.length > 0) { // FIXME - should check branches connected to startnode, as picking random - // might just be confusing + // is just confusing if (workflow.actions[0].id !== data.id) { const ele = cy.getElementById(workflow.actions[0].id) if (ele !== undefined && ele !== null) { @@ -1944,8 +1967,9 @@ const AngularWorkflow = (props) => { //console.log("IN ESTABLISHED!") setEstablished(true) + // Validate if the node is just a node lol cy.edgehandles({ - handleNodes: (el) => el.isNode(), + handleNodes: (el) => el.isNode() && !el.data("isButton") && !el.data("isDescriptor"), preview: false, toggleOffOnLeave: false, loopAllowed: function( node ){ @@ -1991,11 +2015,45 @@ const AngularWorkflow = (props) => { } }) + useEffect(() => { + console.log("Inside useeffect for action") + if (Object.getOwnPropertyNames(selectedAction).length !== 0) { + var rightSideActionView = document.getElementById("rightside_actions") + if (rightSideActionView !== undefined && rightSideActionView !== null) { + console.log("set scroll for ", rightSideActionView) + //rightSideActionView.scrollTop = 250 + //rightSideActionView.scrollRight = 250 + + console.log("FOUND RIGHTSIDE: ", rightSideActionView.scrollTop, scrollLocation) + if (scrollLocation !== 0 && scrollLocation !== undefined && scrollLocation !== 0) { + console.log("SCROLL IS NOT 0: ", scrollLocation) + rightSideActionView.scrollTop = scrollLocation + } else { + console.log("SCROLL IS 0: ", scrollLocation, rightSideActionView.scrollTop) + if (rightSideActionView.scrollTop !== scrollLocation) { + setScrollLocation(rightSideActionView.scrollTop) + } + } + } + } + }, [selectedAction]) + var previousnodecolor = "" //var previousedgecolor = "" const animationDuration = 150 const onNodeHoverOut = (event) => { const nodedata = event.target.data() + if (nodedata.app_name !== undefined) { + const allNodes = cy.nodes().jsons() + console.log("NOT UNDEFINED IN HOVEROUT!", allNodes) + for (var key in allNodes) { + const currentNode = allNodes[key] + if (currentNode.data.isButton && currentNode.data.attachedTo !== nodedata.id) { + cy.getElementById(currentNode.data.id).remove() + } + } + } + var parsedStyle = { "border-width": "1px", 'font-size': '18px', @@ -2016,6 +2074,44 @@ const AngularWorkflow = (props) => { } const onNodeHover = (event) => { + console.log("TAR: ", event.target) + + //var parentNode = cy.$('#' + event.target.data("id")); + //if (parentNode.data('isButton') || parentNode.data('buttonId')) + // return + + //const nodedata = event.target.data() + //if (event.target.data().app_name !== undefined) { + // const allNodes = cy.nodes().jsons() + // console.log("NOT UNDEFINED IN HOVEROUT!", allNodes) + // for (var key in allNodes) { + // const currentNode = allNodes[key] + // if (currentNode.data.isButton && currentNode.data.attachedTo !== nodedata.id) { + // cy.getElementById(currentNode.data.id).remove() + // } + // } + + // parentNode.lock() + // const px = parentNode.position('x') + // const py = parentNode.position('y') - 100 + // const circleId = newNodeId = uuid.v4() + + // parentNode.data('circleId', circleId) + // cy.add({ + // group: 'nodes', + // data: { weight: 30, id: circleId, name: "TEEEXT", isButton: true, attachedTo: event.target.data("id")}, + // position: { x: px, y: py }, + // locked: true + // }).css({ + // 'background-color': 'blue', + // 'shape': 'ellipse', + // 'background-opacity': 0.5, + // 'height': '30px', + // 'width': '30px', + // 'z-index': 5002, + // }).unselectify() + //} + const parsedStyle = { "border-width": "7px", "border-opacity": ".7", @@ -2033,6 +2129,16 @@ const AngularWorkflow = (props) => { } const onEdgeHoverOut = (event) => { + if (event === null || event === undefined) { + event.target.removeStyle() + return + } + + const edgeData = event.target.data() + if (edgeData.decorator === true) { + return + } + event.target.removeStyle() } @@ -2042,13 +2148,18 @@ const AngularWorkflow = (props) => { return } + const edgeData = event.target.data() + if (edgeData.decorator === true) { + return + } + const sourcecolor = cy.getElementById(event.target.data("source")).style("border-color") const targetcolor = cy.getElementById(event.target.data("target")).style("border-color") if (sourcecolor !== null && sourcecolor !== undefined && targetcolor !== null && targetcolor !== undefined) { event.target.animate({ style: { - "line-fill": "linear-gradient", 'target-arrow-color': targetcolor, + "line-fill": "linear-gradient", "line-gradient-stop-colors": [sourcecolor, targetcolor], "line-gradient-stop-positions": [0, 1], }, @@ -2094,7 +2205,7 @@ const AngularWorkflow = (props) => { // FIXME - tmp branch update var insertedNodes = [].concat(actions, triggers) - const edges = workflow.branches.map((branch, index) => { + var edges = workflow.branches.map((branch, index) => { //workflow.branches[index].conditions = [{ const edge = { }; @@ -2117,8 +2228,9 @@ const AngularWorkflow = (props) => { target: branch.destination_id, label: label, conditions: conditions, - hasErrors: branch.has_errors - }; + hasErrors: branch.has_errors, + decorator: false, + } // This is an attempt at prettier edges. The numbers are weird to work with. /* @@ -2147,6 +2259,30 @@ const AngularWorkflow = (props) => { return edge; }) + if (workflow.visual_branches !== undefined && workflow.visual_branches !== null && workflow.visual_branches.length > 0) { + const visualedges = workflow.visual_branches.map((branch, index) => { + const edge = { }; + var conditions = workflow.branches[index].conditions + if (conditions === undefined || conditions === null) { + conditions = [] + } + + const label = "Subflow" + edge.data = { + id: branch.id, + _id: branch.id, + source: branch.source_id, + target: branch.destination_id, + label: label, + decorator: true, + } + + return edge; + }) + + edges = edges.concat(visualedges) + } + setWorkflow(workflow) // Verifies if a branch is valid and skips others @@ -2766,20 +2902,15 @@ const AngularWorkflow = (props) => { } const newEdgeUuid = uuid.v4() - const newbranch = { - "source_id": newNodeId, - "destination_id": workflow.start, - "id_": newEdgeUuid, - "id": newEdgeUuid, - "hasErrors": false, - } - const newcybranch = { "source": newNodeId, "target": workflow.start, + "source_id": newNodeId, + "destination_id": workflow.start, "_id": newEdgeUuid, "id": newEdgeUuid, "hasErrors": false, + "decorator": false, } const edgeToBeAdded = { @@ -3024,7 +3155,7 @@ const AngularWorkflow = (props) => { if (newAppname.length > maxlen) { newAppname = newAppname.slice(0, maxlen)+".." } - app.name.replaceAll("_", " ", -1) + newAppname = newAppname.replaceAll("_", " ") //const image = "url("+app.large_image+")" const image = app.large_image @@ -3050,7 +3181,9 @@ const AngularWorkflow = (props) => { - {newAppname} + + {newAppname} + @@ -3071,7 +3204,8 @@ const AngularWorkflow = (props) => { const runSearch = (value) => { if (value.length > 0) { - setVisibleApps(allApps.filter(app => app.name.toLowerCase().includes(value.trim().toLowerCase()))) + const newApps = allApps.filter(app => app.name.toLowerCase().includes(value.trim().toLowerCase() || app.description.toLowerCase().includes(value.trim().toLowerCase()))) + setVisibleApps(newApps) } else { setVisibleApps(prioritizedApps.concat(filteredApps.filter(innerapp => !internalIds.includes(innerapp.id)))) } @@ -3239,22 +3373,38 @@ const AngularWorkflow = (props) => { setSelectedTrigger(selectedTrigger) } + // Starts on current node and climbs UP the tree to the root object. + // Sends back everything in it's path const getParents = (action) => { var allkeys = [action.id] var handled = [] var results = [] + // maxiter = max amount of parent nodes to loop + // also handles breaks if there are issues + var iterations = 0 + var maxiter = 10 while(true) { for (var key in allkeys) { var currentnode = cy.getElementById(allkeys[key]) - if (handled.includes(currentnode.data().id)) { + if (currentnode === undefined || currentnode === null) { continue + } + + if (currentnode.data() === undefined) { + console.log("Node doesn't have any data (getParents)! Probably a trigger.") + handled.push(allkeys[key]) + results.push({"id": allkeys[key], "type": "TRIGGER"}) } else { - // Get the name / label here too? - handled.push(currentnode.data().id) - results.push(currentnode.data()) + if (handled.includes(currentnode.data().id)) { + continue + } else { + handled.push(currentnode.data().id) + results.push(currentnode.data()) + } } + // Get the name / label here too? if (currentnode.length === 0) { continue } @@ -3271,9 +3421,12 @@ const AngularWorkflow = (props) => { } } } - if (results.length === allkeys.length) { + + if (results.length === allkeys.length || iterations === maxiter) { break } + + iterations += 1 } // Remove self @@ -3289,7 +3442,6 @@ const AngularWorkflow = (props) => { // VALUE (JSON) // {data.name}, {data.description}, {data.required}, {data.schema.type} - //height: "100%", const appApiViewStyle = { display: "flex", @@ -3304,11 +3456,7 @@ const AngularWorkflow = (props) => { overflow: "auto", } - - - - - const rightsidebarStyle = { + var rightsidebarStyle = { position: "fixed", right: 0, top: appBarSize+1, @@ -3323,43 +3471,6 @@ const AngularWorkflow = (props) => { zIndex: 1000, } - const appApiView = Object.getOwnPropertyNames(selectedAction).length > 0 ? - - - : - null - const setTriggerFolderWrapperMulti = event => { const { options } = event.target const value = [] @@ -4368,6 +4479,302 @@ const AngularWorkflow = (props) => { const [showDropdown, setShowDropdown] = React.useState(false) const [showDropdownNumber, setShowDropdownNumber] = React.useState(0) const [showAutocomplete, setShowAutocomplete] = React.useState(false) + const [actionlist, setActionlist] = React.useState([]) + + if (actionlist.length === 0) { + // FIXME: Have previous execution values in here + actionlist.push({"type": "Execution Argument", "name": "Execution Argument", "value": "$exec", "highlight": "exec", "autocomplete": "exec", "example": "hello"}) + if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) { + for (var key in workflow.workflow_variables) { + const item = workflow.workflow_variables[key] + actionlist.push({"type": "workflow_variable", "name": item.name, "value": item.value, "id": item.id, "autocomplete": `${item.name.split(" ").join("_")}`, "example": item.value}) + } + } + + // FIXME: Add values from previous executions if they exist + if (workflow.execution_variables !== null && workflow.execution_variables !== undefined && workflow.execution_variables.length > 0) { + for (var key in workflow.execution_variables) { + const item = workflow.execution_variables[key] + actionlist.push({"type": "execution_variable", "name": item.name, "value": item.value, "id": item.id, "autocomplete": `${item.name.split(" ").join("_")}`, "example": ""}) + } + } + + var parents = getParents(selectedAction) + if (parents.length > 1) { + for (var key in parents) { + const item = parents[key] + if (item.label === "Execution Argument") { + continue + } + + var exampledata = item.example === undefined ? "" : item.example + // Find previous execution and their variables + //exampledata === "" && + if (workflowExecutions.length > 0) { + // Look for the ID + const found = false + for (var key in workflowExecutions) { + if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { + continue + } + + var foundResult = workflowExecutions[key].results.find(result => result.action.id === item.id) + if (foundResult === undefined) { + continue + } + + foundResult.result = foundResult.result.trim() + foundResult.result = foundResult.result.split(" None").join(" \"None\"") + foundResult.result = foundResult.result.split(" False").join(" false") + foundResult.result = foundResult.result.split(" True").join(" true") + + var jsonvalid = true + try { + const tmp = String(JSON.parse(foundResult.result)) + if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) { + jsonvalid = false + } + } catch (e) { + try { + foundResult.result = foundResult.result.split("\'").join("\"") + const tmp = String(JSON.parse(foundResult.result)) + if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) { + jsonvalid = false + } + } catch (e) { + jsonvalid = false + } + } + + // Finds the FIRST json only + if (jsonvalid) { + exampledata = JSON.parse(foundResult.result) + break + } + //else { + // console.log("Invalid JSON: ", foundResult.result) + //} + } + } + + // 1. Take + const actionvalue = {"type": "action", "id": item.id, "name": item.label, "autocomplete": `${item.label.split(" ").join("_")}`, "example": exampledata} + actionlist.push(actionvalue) + } + + } + + console.log("ACTIONS: ", actionlist) + setActionlist(actionlist) + } + + // Shows nested list of nodes > their JSON lists + const ActionlistWrapper = (props) => { + const { data } = props; + + const handleMenuClose = () => { + setShowAutocomplete(false) + + //if (!selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === "$") { + // setShowDropdown(false) + //} + + setUpdate(Math.random()) + setMenuPosition(null) + } + + const handleItemClick = (values) => { + if (values === undefined || values === null || values.length === 0) { + return + } + + var toComplete = workflow.triggers[selectedTriggerIndex].parameters[1].value.trim().endsWith("$") ? values[0].autocomplete : "$"+values[0].autocomplete + for (var key in values) { + if (key == 0 || values[key].autocomplete.length === 0) { + continue + } + + toComplete += values[key].autocomplete + } + + // Handles the fields under OpenAPI body to be parsed. + if (data.name.startsWith("${") && data.name.endsWith("}")) { + console.log("INSIDE VALUE REPLACE: ", data.name, toComplete) + // PARAM FIX - Gonna use the ID field, even though it's a hack + const paramcheck = selectedAction.parameters.find(param => param.name === "body") + if (paramcheck !== undefined) { + if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) { + paramcheck["value_replace"] = [{ + "key": data.name, + "value": toComplete, + }] + + } else { + const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name) + if (subparamindex === -1) { + paramcheck["value_replace"].push({ + "key": data.name, + "value": toComplete, + }) + } else { + paramcheck["value_replace"][subparamindex]["value"] += toComplete + } + } + + workflow.triggers[selectedTriggerIndex].parameters[1]["value_replace"] = paramcheck + setSelectedAction(selectedAction) + setUpdate(Math.random()) + + setShowDropdown(false) + setMenuPosition(null) + return + } + } + + //workflow.triggers[selectedTriggerIndex].parameters[1].value = selectedActionParameters[count].value + setSelectedAction(selectedAction) + setUpdate(Math.random()) + + setShowDropdown(false) + setMenuPosition(null) + } + + const iconStyle = { + marginRight: 15, + } + + return ( + { + handleMenuClose() + }} + open={!!menuPosition} + style={{ + border: `2px solid #f85a3e`, + color: "white", + marginTop: 2, + }} + > + {actionlist.map(innerdata => { + const icon = innerdata.type === "action" ? : innerdata.type === "workflow_variable" || innerdata.type === "execution_variable" ? : + + const handleExecArgumentHover = (inside) => { + var exec_text_field = document.getElementById("execution_argument_input_field") + if (exec_text_field !== null) { + if (inside) { + exec_text_field.style.border = "2px solid #f85a3e" + } else { + exec_text_field.style.border = "" + } + } + + // Also doing arguments + if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length > 0) { + for (var key in workflow.triggers) { + const item = workflow.triggers[key] + + if (cy !== undefined) { + var node = cy.getElementById(item.id) + if (node.length > 0) { + if (inside) { + node.addClass('shuffle-hover-highlight') + } else { + node.removeClass('shuffle-hover-highlight') + } + } + } + } + } + } + + const handleActionHover = (inside, actionId) => { + if (cy !== undefined) { + var node = cy.getElementById(actionId) + if (node.length > 0) { + if (inside) { + node.addClass('shuffle-hover-highlight') + } else { + node.removeClass('shuffle-hover-highlight') + } + } + } + } + + const handleMouseover = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(true) + } else if (innerdata.type === "action") { + handleActionHover(true, innerdata.id) + } + } + + const handleMouseOut = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(false) + } else if (innerdata.type === "action") { + handleActionHover(false, innerdata.id) + } + } + + var parsedPaths = [] + if (typeof(innerdata.example) === "object") { + parsedPaths = GetParsedPaths(innerdata.example, "") + } + + return ( + parsedPaths.length > 0 ? + + {icon} {innerdata.name} + + } + parentMenuOpen={!!menuPosition} + style={{backgroundColor: theme.palette.inputColor, color: "white", minWidth: 250,}} + onClick={() => { + handleItemClick([innerdata]) + }} + > + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + const icon = pathdata.type === "value" ? : pathdata.type === "list" ? : + return ( + {}} + onClick={() => { + handleItemClick([innerdata, pathdata]) + }} + > + +
+ {icon} {pathdata.name} +
+
+
+ ) + + })} +
+ : + handleMouseover()} onMouseOut={() => {handleMouseOut()}} + onClick={() => { + handleItemClick([innerdata]) + }} + > + +
+ {icon} {innerdata.name} +
+
+
+ + ) + })} +
+ ) + } if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { if (workflow.triggers[selectedTriggerIndex] === undefined) { @@ -4436,7 +4843,6 @@ const AngularWorkflow = (props) => { }} fullWidth onChange={(e) => { - setSubworkflow(e.target.value) setUpdate(Math.random()) workflow.triggers[selectedTriggerIndex].parameters[0].value = e.target.value.id setWorkflow(workflow) @@ -4444,6 +4850,7 @@ const AngularWorkflow = (props) => { // Sets the startnode if (e.target.value.id !== workflow.id) { + setSubworkflow(e.target.value) const startnode = e.target.value.actions.find(action => action.id === e.target.value.start) if (startnode !== undefined && startnode !== null) { //ddsetSubworkflowStartnode(innernode) @@ -4459,6 +4866,8 @@ const AngularWorkflow = (props) => { } } console.log("STARTNODE: ", startnode) + } else { + setSubworkflow(workflow) } }} style={{backgroundColor: inputColor, color: "white", height: "50px"}} @@ -4502,6 +4911,49 @@ const AngularWorkflow = (props) => { fullWidth onChange={(e) => { setSubworkflowStartnode(e.target.value) + console.log("WF: ", workflow) + + const branchId = uuid.v4() + const newbranch = { + "source_id": workflow.triggers[selectedTriggerIndex].id, + "destination_id": e.target.value.id, + "source": workflow.triggers[selectedTriggerIndex].id, + "target": e.target.value.id, + "has_errors": false, + "id": branchId, + "_id": branchId, + "label": "Subflow", + "decorator": true, + } + + if (workflow.visual_branches !== undefined) { + if (workflow.visual_branches === null) { + workflow.visual_branches = [newbranch] + } else if (workflow.visual_branches.length === 0) { + workflow.visual_branches.push(newbranch) + } else { + const foundIndex = workflow.visual_branches.findIndex(branch => branch.source_id === newbranch.source_id) + if (foundIndex !== -1) { + const currentEdge = cy.getElementById(workflow.visual_branches[foundIndex].id) + if (currentEdge !== undefined && currentEdge !== null) { + currentEdge.remove() + } + } + + workflow.visual_branches.splice(foundIndex, 1) + workflow.visual_branches.push(newbranch) + } + } + + const cybranch = { + group: "edges", + source: newbranch.source_id, + target: newbranch.destination_id, + id: branchId, + data: newbranch, + } + + cy.add(cybranch) try { workflow.triggers[selectedTriggerIndex].parameters[3].value = e.target.value.id @@ -4551,7 +5003,7 @@ const AngularWorkflow = (props) => { top: event.pageY+10, left: event.pageX+10, }) - //setShowDropdownNumber(count) + //setShowDropdownNumber(3) setShowDropdown(true) setShowAutocomplete(true) }}/> @@ -4571,6 +5023,9 @@ const AngularWorkflow = (props) => { setWorkflow(workflow) }} /> + {showDropdown ? + + : null}
@@ -4709,13 +5164,13 @@ const AngularWorkflow = (props) => { style={{backgroundColor: inputColor, borderRadius: theme.palette.borderRadius,}} id="webhook_uri_field" onClick={() => { - var copyText = document.getElementById("webhook_uri_field"); + var copyText = document.getElementById("webhook_uri_field") navigator.clipboard.writeText(copyText.value) - copyText.select(); - copyText.setSelectionRange(0, 99999); /* For mobile devices */ + copyText.select() + copyText.setSelectionRange(0, 99999) /* For mobile devices */ /* Copy the text inside the text field */ - document.execCommand("copy"); + document.execCommand("copy") alert.success("Copied Webhook URL") }} InputProps={{ @@ -4775,7 +5230,7 @@ const AngularWorkflow = (props) => { // }) // .catch(error => { // console.log(error.toString()) - // }); + // }) //} const stopMailSub = (trigger, triggerindex) => { @@ -5403,7 +5858,7 @@ const AngularWorkflow = (props) => { } const FileMenu = () => { - const [newAnchor, setNewAnchor] = React.useState(null); + const [newAnchor, setNewAnchor] = React.useState(null) const [showShuffleMenu, setShowShuffleMenu] = React.useState(false) { /*const [showShuffleMenu, setShowShuffleMenu] = React.useState(true) */} @@ -5461,7 +5916,7 @@ const AngularWorkflow = (props) => { } const WorkflowMenu = () => { - const [newAnchor, setNewAnchor] = React.useState(null); + const [newAnchor, setNewAnchor] = React.useState(null) const [showShuffleMenu, setShowShuffleMenu] = React.useState(false) { /*const [showShuffleMenu, setShowShuffleMenu] = React.useState(true) */} @@ -5519,7 +5974,7 @@ const AngularWorkflow = (props) => { } const BottomCytoscapeBar = () => { - const [anchorEl, setAnchorEl] = React.useState(null); + const [anchorEl, setAnchorEl] = React.useState(null) const boxSize = 100 const executionButton = executionRunning ? @@ -5583,11 +6038,11 @@ const AngularWorkflow = (props) => { let exportFileDefaultName = data.name+'.json'; let dataStr = JSON.stringify(data) - let dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); - let linkElement = document.createElement('a'); - linkElement.setAttribute('href', dataUri); - linkElement.setAttribute('download', exportFileDefaultName); - linkElement.click(); + let dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr) + let linkElement = document.createElement('a') + linkElement.setAttribute('href', dataUri) + linkElement.setAttribute('download', exportFileDefaultName) + linkElement.click() }}> @@ -5627,16 +6082,22 @@ const AngularWorkflow = (props) => { ) } - const RightSideBar = () => { + const RightSideBar = (props) => { if (!rightSideBarOpen) { return null } if (Object.getOwnPropertyNames(selectedAction).length > 0) { - //console.time('ACTIONSTART') + if (Object.getOwnPropertyNames(selectedAction).length === 0) { + return null + } + + console.time('ACTIONSTART') return( -
- {appApiView} +
{ + console.log("LOADED RIGHTSIDE") + }}> + HELO
) } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { @@ -5805,11 +6266,11 @@ const AngularWorkflow = (props) => { var copyText = document.getElementById(elementName); if (copyText !== null && copyText !== undefined) { navigator.clipboard.writeText(JSON.stringify(copy)) - copyText.select(); - copyText.setSelectionRange(0, 99999); /* For mobile devices */ + copyText.select() + copyText.setSelectionRange(0, 99999) /* For mobile devices */ /* Copy the text inside the text field */ - document.execCommand("copy"); + document.execCommand("copy") alert.success("Copied data") } } @@ -5849,10 +6310,10 @@ const AngularWorkflow = (props) => { to_be_copied.replaceAll(" ", "_") const elementName = "copy_element_shuffle" - var copyText = document.getElementById(elementName); + var copyText = document.getElementById(elementName) if (copyText !== null && copyText !== undefined) { navigator.clipboard.writeText(to_be_copied) - copyText.select(); + copyText.select() copyText.setSelectionRange(0, 99999); /* For mobile devices */ /* Copy the text inside the text field */ @@ -6423,7 +6884,45 @@ const AngularWorkflow = (props) => { />
{executionModal} - + + {rightSideBarOpen && Object.getOwnPropertyNames(selectedAction).length !== 0 ? +
+ +
+ : + + }
@@ -6435,6 +6934,7 @@ const AngularWorkflow = (props) => {
*/ + const executionVariableModal = executionVariablesModalOpen ? { {/*selectedApp.link.length > 0 ?
: null*/}
{selectedApp.authentication.parameters.map((data, index) => { - return ( -
- - {data.name} - { - authenticationOption.fields[data.name] = event.target.value - }} - /> -
- ) - })} + console.log("AUTH: ", data) + + return ( +
+ + {data.name} + + {data.schema !== undefined && data.schema !== null && data.schema.type === "bool" ? + + : + { + authenticationOption.fields[data.name] = event.target.value + }} + /> + } +
+ ) + })}