From 52227397a9d0118b4a574b77dfbe1bfe9b4780c7 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 22 Jun 2024 23:44:01 +0200 Subject: [PATCH] Fixed user input to work better --- backend/go-app/walkoff.go | 2 +- frontend/src/components/EditWorkflow.jsx | 4 +- frontend/src/views/AngularWorkflow.jsx | 39 ++-- frontend/src/views/RunWorkflow.jsx | 245 ++++++++++++++++++----- 4 files changed, 211 insertions(+), 79 deletions(-) diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 8ea1550a..7ff135e3 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1076,7 +1076,7 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request // return workflowExecution, fmt.Sprintf("%s", err), nil } else { log.Printf("[ERROR] Failed in prepareExecution: '%s'", err) - return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed starting workflow: %s", err), err + return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed running: %s", err), err } } diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index cc4a33b4..e2443664 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -685,8 +685,8 @@ const EditWorkflow = (props) => { Input fields - - Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the workflow run page. + + Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the workflow run page. If chosen in the User Input node, these will be required fields. diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index fb28083d..baab3dab 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1073,22 +1073,20 @@ const releaseToConnectLabel = "Release to Connect" // User Input & Subflow nodes if (param.name === "workflow" || param.name === "subflow") { - const paramIndex = param.name === "workflow" ? 0 : 5 - console.log("Current vs new: ", workflow.triggers[trigger_index].parameters[paramIndex].value, subworkflow.id) - - if (workflow.triggers[trigger_index].parameters[paramIndex].value !== subworkflow.id) { - if (param.value === workflow.id) { - setSubworkflow(workflow); - baseSubflow = workflow - } else { - const sub = responseJson.find((data) => data.id === param.value); - if (sub !== undefined && subworkflow.id !== sub.id) { - baseSubflow = sub - setSubworkflow(sub); - } - } - } - } + const paramIndex = param.name === "workflow" ? 0 : 5 + if (workflow.triggers[trigger_index].parameters[paramIndex].value !== subworkflow.id) { + if (param.value === workflow.id) { + setSubworkflow(workflow); + baseSubflow = workflow + } else { + const sub = responseJson.find((data) => data.id === param.value); + if (sub !== undefined && subworkflow.id !== sub.id) { + baseSubflow = sub + setSubworkflow(sub); + } + } + } + } if (param.name === "startnode" && param.value !== undefined && param.value !== null) { @@ -3255,7 +3253,6 @@ const releaseToConnectLabel = "Release to Connect" const getChildWorkflows = (parentWorkflowId) => { if (originalWorkflow.suborg_distribution === undefined || originalWorkflow.suborg_distribution === null || originalWorkflow.suborg_distribution.length === 0) { - console.log("No suborg distribution") return } @@ -14866,10 +14863,6 @@ const releaseToConnectLabel = "Release to Connect" }) } - if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length > 0 && selectedTriggerIndex >= 0 && selectedTriggerIndex < workflow.triggers.length) { - console.log(workflow.triggers[selectedTriggerIndex]) - } - const UserinputSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "USERINPUT" ? null :

@@ -15217,7 +15210,7 @@ const releaseToConnectLabel = "Release to Connect"

- Enabled Input-Questions + Required Input-Questions {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
{workflow.input_questions.map((question, index) => { @@ -17156,10 +17149,12 @@ const releaseToConnectLabel = "Release to Connect" //defaultReturn = return null; } else { + /* console.log( "Unable to handle invalid trigger type " + selectedTrigger.trigger_type ); + */ return null; } } else if (Object.getOwnPropertyNames(selectedEdge).length > 0) { diff --git a/frontend/src/views/RunWorkflow.jsx b/frontend/src/views/RunWorkflow.jsx index ee1802d7..4d8dc1bf 100644 --- a/frontend/src/views/RunWorkflow.jsx +++ b/frontend/src/views/RunWorkflow.jsx @@ -2,6 +2,7 @@ import React, {useState, useEffect} from 'react'; import ReactDOM from "react-dom" +import { ToastContainer, toast } from "react-toastify" import { useInterval } from "react-powerhooks"; import { makeStyles } from '@mui/material/styles'; import { useNavigate, Link, useParams } from "react-router-dom"; @@ -43,7 +44,8 @@ const bodyDivStyle = { const RunWorkflow = (defaultprops) => { const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, serverside } = defaultprops; - let navigate = useNavigate(); + let navigate = useNavigate(); + const [_, setUpdate] = useState(""); // Used to force rendring, don't remove const [message, setMessage] = useState(""); const [workflow, setWorkflow] = React.useState({}); const [executionRequest, setExecutionRequest] = React.useState({}); @@ -55,6 +57,7 @@ const RunWorkflow = (defaultprops) => { const [selectedOrganization, setSelectedOrganization] = React.useState(undefined); const [apps, setApps] = React.useState([]); const [buttonClicked, setButtonClicked] = React.useState(""); + const [foundSourcenode, setFoundSourcenode] = React.useState(undefined); const boxStyle = { color: "white", @@ -340,6 +343,8 @@ const RunWorkflow = (defaultprops) => { event.preventDefault() } + console.log("ONSUBMIT: ", event, execution_id, authorization, answer) + stop() setMessage("") setExecutionLoading(true) @@ -389,12 +394,27 @@ const RunWorkflow = (defaultprops) => { url += `?reference_execution=${execution_id}&authorization=${authorization}&answer=${answer}` data = {} fetchBody.method = "GET" + + if (executionArgument !== undefined && executionArgument !== null) { + try { + if (typeof executionArgument === "string") { + url += "¬e=" + executionArgument + } else { + url += "¬e=" + JSON.stringify(executionArgument) + } + } catch (e) { + url += "¬e=" + executionArgument + } + } + } else { fetchBody.method = "POST" fetchBody.body = JSON.stringify(data) } - console.log("Pre request: ", url, fetchBody) + // IF there is an execution argument, we should use it + console.log("FULL URL: ", url) + fetch(url, fetchBody) .then((response) => { if (response.status !== 200 && response.status !== 201) { @@ -408,16 +428,26 @@ const RunWorkflow = (defaultprops) => { }) start(); - return + return response.json() } } - return response.json(); + return response.json() }) .then(responseJson => { setExecutionLoading(false) - if (responseJson["success"] === false) { + if (responseJson.success === false) { console.log("Failed sending execution request") + if (responseJson.reason !== undefined && responseJson.reason !== null) { + toast.warn(responseJson.reason) + } + + stop() + setMessage("") + setExecutionData({}) + setExecutionInfo("") + setExecutionRunning(false) + setExecutionRequest({}) } else { console.log("Started execution") @@ -426,17 +456,24 @@ const RunWorkflow = (defaultprops) => { } else { setExecutionRunning(true); setExecutionRequest(responseJson) - start(); + start() } } }) .catch(error => { //setExecutionInfo("Error in workflow startup: " + error) + toast.warn("Error in workflow startup: " + error) + + stop() + setMessage("") + setExecutionData({}) + setExecutionInfo("") + setExecutionLoading(false) }) } - const getWorkflow = (workflow_id) => { + const getWorkflow = (workflow_id, selectedNode) => { fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { method: "GET", headers: { @@ -480,6 +517,48 @@ const RunWorkflow = (defaultprops) => { setExecutionArgument(newexec) } + if (selectedNode !== undefined && selectedNode !== null && selectedNode.length > 0) { + var found = false + for (var actionkey in responseJson.actions) { + if (responseJson.actions[actionkey].id === selectedNode) { + found = true + setFoundSourcenode(responseJson.actions[actionkey]) + break + } + } + + if (!found) { + for (var triggerkey in responseJson.triggers) { + if (responseJson.triggers[triggerkey].id !== selectedNode) { + continue + } + + setFoundSourcenode(responseJson.triggers[triggerkey]) + + if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0 && responseJson.triggers[triggerkey].trigger_type === "USERINPUT") { + + // Look for input questions param + for (var paramkey in responseJson.triggers[triggerkey].parameters) { + if (responseJson.triggers[triggerkey].parameters[paramkey].name === "input_questions") { + + var relevantquestions = [] + for (var questionkey in responseJson.input_questions) { + if (responseJson.triggers[triggerkey].parameters[paramkey].value.includes(responseJson.input_questions[questionkey].name)) { + relevantquestions.push(responseJson.input_questions[questionkey]) + } + } + + responseJson.input_questions = relevantquestions + } + } + } + + + break + } + } + } + handleGetOrg(responseJson.org_id) setWorkflow(responseJson); }) @@ -489,7 +568,7 @@ const RunWorkflow = (defaultprops) => { }; const { start, stop } = useInterval({ - duration: 3000, + duration: 1500, startImmediate: true, callback: () => { fetchUpdates(executionRequest.execution_id, executionRequest.authorization) @@ -526,15 +605,11 @@ const RunWorkflow = (defaultprops) => { for (var key in responseJson.results) { if (responseJson.results[key].status === "WAITING") { - console.log("Found: ", responseJson.results[key]) - const validate = validateJson(responseJson.results[key].result) - console.log("Validate: ", validate) if (validate.valid && typeof validate.result === "string") { validate.result = JSON.parse(validate.result) } - console.log("Newresult: ", validate.result) if (validate.result["information"] !== undefined && validate.result["information"] !== null) { setWorkflowQuestion(validate.result["information"]) } @@ -623,15 +698,15 @@ const RunWorkflow = (defaultprops) => { return } - fetch(globalUrl + "/api/v1/streams/results", { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(innerRequest), - credentials: "include", - }) + fetch(globalUrl + "/api/v1/streams/results", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(innerRequest), + credentials: "include", + }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for stream results :O!"); @@ -651,11 +726,14 @@ const RunWorkflow = (defaultprops) => { }); }; - const answer = new URLSearchParams(window.location.search).get("answer") - const execution_id = new URLSearchParams(window.location.search).get("reference_execution") - const authorization = new URLSearchParams(window.location.search).get("authorization") + const searchParams = new URLSearchParams(window.location.search) + const answer = searchParams.get("answer") + const execution_id = searchParams.get("reference_execution") + const authorization = searchParams.get("authorization") + const sourceNode = searchParams.get("source_node") + useEffect(() => { - getWorkflow(props.match.params.key) + getWorkflow(props.match.params.key, sourceNode) if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null) { console.log("Get execution: ", execution_id) fetchUpdates(execution_id, authorization, true) @@ -667,13 +745,60 @@ const RunWorkflow = (defaultprops) => { }, []) + useEffect(() => { + if (executionData === undefined || executionData === null || executionData === {}) { + return + } + + if (foundSourcenode === undefined || foundSourcenode === null || foundSourcenode === {}) { + return + } + + if (foundSourcenode.trigger_type !== "USERINPUT") { + return + } + + if (executionData.results === undefined || executionData.results === null || executionData.results.length === 0) { + return + } + + for (var resultkey in executionData.results) { + const result = executionData.results[resultkey] + if (result.action.id !== foundSourcenode.id) { + continue + } + + var parsedresult = result.result + try { + parsedresult = JSON.parse(parsedresult) + } catch (e) { + console.log("Error parsing result: ", e) + } + + if (result.status !== "WAITING") { + if (parsedresult.click_info !== undefined && parsedresult.click_info !== null) { + if (parsedresult.click_info.user !== undefined && parsedresult.click_info.user !== null && parsedresult.click_info.user.length > 0) { + setMessage("Already answered by " + parsedresult.click_info.user) + } + } else { + setMessage("Answered.") + } + + } + + break + } + + }, [executionData, foundSourcenode]) const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)" const buttonStyle = {borderRadius: 25, height: 50, fontSize: 18, backgroundImage: handleValidateForm(executionArgument) || executionLoading ? buttonBackground : "grey", color: "white"} - //console.log("execdata: ", executionData) const disabledButtons = message.length > 0 || executionData.status === "FINISHED" || executionData.status === "ABORTED" + //{disabledButtons ? null : + + const organization = selectedOrganization !== undefined && selectedOrganization !== null ? selectedOrganization.name : "Unknown" const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.org !== undefined && selectedOrganization.org !== null? selectedOrganization.org : "support@shuffler.io" //const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.contact !== undefined && selectedOrganization.contact !== null? selectedOrganization.contact : "support@shuffler.io" @@ -724,49 +849,54 @@ const RunWorkflow = (defaultprops) => { {organization} - - {contact} - - {message} + + {disabledButtons && message.length > 0 ? null : + + {message} + + } {answer !== undefined && answer !== null ? null : - {workflow.name} + {workflow.name} } {workflowQuestion.length > 0 ? - - {workflowQuestion} - +
+ + {workflowQuestion} + +
: null} {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
{workflow.input_questions.map((question, index) => { - return (
{question.name} { + onBlur={(e) => { //setExecutionArgument(e.target.value) executionArgument[question.value] = e.target.value + setUpdate(Math.random()) }} />
@@ -813,20 +943,27 @@ const RunWorkflow = (defaultprops) => { : null} - : - answer !== undefined && answer !== null ? + ((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) ? - - {disabledButtons ? "Already answered. Nothing to do." : ""} - + + {disabledButtons && message.length > 0 ? + + {message}. You may close this window. + + : + + {disabledButtons ? "Answered. You may close this window." : ""} + + } + {disabledButtons ? null : What do you want to do? }
-
} - {buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ? + {/*buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ? finalize workflow animation { console.log("Img loaded.") @@ -868,7 +1005,7 @@ const RunWorkflow = (defaultprops) => { }} /> - : null} + : null*/}
{executionInfo}