/* eslint-disable react/no-multi-comp */ 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"; import {isMobile} from "react-device-detect"; import theme from '../theme.jsx'; import { validateJson, GetIconInfo } from "./Workflows.jsx"; import { green, yellow } from "./AngularWorkflow.jsx"; import { Tooltip, IconButton, CircularProgress, TextField, Button, ButtonGroup, Paper, Typography, Divider, } from '@mui/material'; import { Preview as PreviewIcon, ContentCopy as ContentCopyIcon, } from '@mui/icons-material'; const hrefStyle = { color: "white", textDecoration: "none" } const bodyDivStyle = { margin: "auto", marginTop: 100, width: isMobile? "100%":"500px", position: "relative", } const RunWorkflow = (defaultprops) => { const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, serverside } = defaultprops; 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({}); const [executionArgument, setExecutionArgument] = useState(""); const [executionLoading, setExecutionLoading] = useState(false); const [executionData, setExecutionData] = React.useState({}); const [executionRunning, setExecutionRunning] = useState(false); const [workflowQuestion, setWorkflowQuestion] = useState(""); 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", paddingLeft: "30px", paddingRight: "30px", paddingBottom: "30px", paddingTop: "30px", backgroundColor: theme.palette.surfaceColor, marginBottom: 150, } const params = useParams(); var props = JSON.parse(JSON.stringify(defaultprops)) props.match = {} props.match.params = params const defaultTitle = "Run Workflow" if (document != undefined && document.title != defaultTitle) { document.title = defaultTitle } const parsedsearch = serverside === true ? "" : window.location.search if (serverside !== true) { const tmpMessage = new URLSearchParams(window.location.search).get("message") if (tmpMessage !== undefined && tmpMessage !== null && message !== tmpMessage) { setMessage(tmpMessage) } } // Used to swap from login to register. True = login, false = register // Error messages etc const [executionInfo, setExecutionInfo] = useState(""); const handleValidateForm = (executionArgument) => { return true } const getApps = () => { fetch(globalUrl + "/api/v1/apps", { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for apps :O!"); } return response.json(); }) .then((responseJson) => { setApps(responseJson) }) .catch(error => { console.log("App error: ", error); }) } const ShowExecutionResults = (props) => { const { executionData } = props; if (executionData === undefined || executionData === null || executionData === {}) { return null } const executionMargin = 20 const defaultReturn = null /*
No results yet
*/ if (executionData.results === undefined || executionData.results === null) { return defaultReturn } return (
{/*executionData !== undefined && executionData !== null && executionData !== {} && executionData.status !== undefined && (answer === undefined || answer === null) ?
Status  {executionData.status}
: null*/} {/* Results: {executionData.results.length}/{executionData.workflow.actions.length} */} {executionData.result !== undefined && executionData.result !== null && executionData.result.length > 0 ? {executionData.result} : null} {/*executionData.results.map((data, index) => { if (executionData.results.length !== 1 && (data.status === "SKIPPED")) { return null; } // FIXME: The latter replace doens't really work if ' is used in a string var showResult = data.result.trim(); const validate = validateJson(showResult); const curapp = apps.find((a) => a.name === data.action.app_name && a.app_version === data.action.app_version); const imgsize = 50; const statusColor = data.status === "FINISHED" || data.status === "SUCCESS" ? green : data.status === "ABORTED" || data.status === "FAILURE" ? "red" : yellow; var imgSrc = curapp === undefined ? "" : curapp.large_image; if ( imgSrc.length === 0 && workflow.actions !== undefined && workflow.actions !== null ) { // Look for the node in the workflow const action = workflow.actions.find( (action) => action.id === data.action.id ); if (action !== undefined && action !== null) { imgSrc = action.large_image; } } var actionimg = curapp === null ? null : ( {data.action.app_name} ); if (data.action.app_name === "shuffle-subflow") { //const parsedImage = triggers[2].large_image; //actionimg = ( // {"Shuffle //); } else if (data.action.app_name === "User Input") { //actionimg = ( // {"Shuffle //); } if (validate.valid && typeof validate.result === "string") { validate.result = JSON.parse(validate.result); } if (validate.valid && typeof validate.result === "object") { if ( validate.result.result !== undefined && validate.result.result !== null ) { try { validate.result.result = JSON.parse(validate.result.result); } catch (e) { //console.log("ERROR PARSING: ", e) } } } var similarActionsView = null if (data.similar_actions !== undefined && data.similar_actions !== null) { var minimumMatch = 85 var matching_executions = [] if (data.similar_actions !== undefined && data.similar_actions !== null) { for (let [k,kval] in Object.entries(data.similar_actions)){ if (data.similar_actions.hasOwnProperty(k)) { if (data.similar_actions[k].similarity > minimumMatch) { matching_executions.push(data.similar_actions[k].execution_id) } } } } if (matching_executions.length !== 0) { var parsed_url = matching_executions.join(",") similarActionsView = { //navigate(`?execution_highlight=${parsed_url}`) }} > } } return (
{ }} onMouseOut={() => { }} >
Status  {data.status}
) })*/}
) } const onSubmit = (event, execution_id, authorization, answer) => { if (event !== null) { event.preventDefault() } console.log("ONSUBMIT: ", event, execution_id, authorization, answer) stop() setMessage("") setExecutionLoading(true) setExecutionData({}) setExecutionInfo("") var data = { "execution_argument": executionArgument, "execution_source": "questions", } if (workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0) { try { data["execution_argument"] = JSON.stringify(executionArgument) } catch (e) { console.log("Error parsing execution argument: ", e) } } if (workflow.start !== undefined && workflow.start !== null && workflow.start.length > 0) { //data.start = workflow.start } else { /* if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) { for (let actionkey in workflow.actions) { if (workflow.actions[actionkey].isStartNode) { data.start = workflow.actions[actionkey].id break } } } */ } var url = `${globalUrl}/api/v1/workflows/${props.match.params.key}/execute` var fetchBody = { headers: { 'Content-Type': 'application/json; charset=utf-8', }, mode: 'cors', credentials: 'include', crossDomain: true, withCredentials: true, } if (answer !== undefined && execution_id !== undefined && authorization !== undefined) { 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) } // 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) { if (answer !== undefined && execution_id !== undefined && authorization !== undefined) { setExecutionLoading(false) setExecutionRunning(true); setExecutionRequest({ "execution_id": execution_id, "authorization": authorization, }) start(); return response.json() } } return response.json() }) .then(responseJson => { setExecutionLoading(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") if (answer !== undefined && answer !== null) { console.log("Skipping start") } else { setExecutionRunning(true); setExecutionRequest(responseJson) 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, selectedNode) => { fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { method: "GET", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for workflows :O!"); } return response.json(); }) .then((responseJson) => { // Not sure why this is necessary. if (responseJson.isValid === undefined) { responseJson.isValid = true; } if (responseJson.errors === undefined) { responseJson.errors = []; } if (responseJson.actions === undefined || responseJson.actions === null) { responseJson.actions = []; } if (responseJson.triggers === undefined || responseJson.triggers === null) { responseJson.triggers = []; } if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0) { var newexec = {} for (let questionkey in responseJson.input_questions) { const question = responseJson.input_questions[questionkey] newexec[question.value] = "" } 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); }) .catch((error) => { console.log("Get workflow error: ", error.toString()); }); }; const { start, stop } = useInterval({ duration: 1500, startImmediate: true, callback: () => { fetchUpdates(executionRequest.execution_id, executionRequest.authorization) }, }); const handleUpdateResults = (responseJson, executionRequest) => { if (responseJson === undefined || responseJson === null || responseJson.success === false) { return } //console.log("Got response: ", responseJson) ReactDOM.unstable_batchedUpdates(() => { if (JSON.stringify(responseJson) !== JSON.stringify(executionData)) { // FIXME: If another is selected, don't edit.. // Doesn't work because this is some async garbage if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) { if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || (executionData.results !== undefined && responseJson.results !== null && executionData.results.length !== responseJson.results.length)) { if (responseJson.result !== undefined && responseJson.result !== null && responseJson.result.length > 0) { if (responseJson.result.startsWith("[") && responseJson.result.endsWith("]")) { try { responseJson.result = JSON.parse(responseJson.result).length console.log("Set length to: ", responseJson.result) } catch (e) { console.log("Error parsing length: ", e) } } } //console.log("Updating data!") setExecutionData(responseJson) for (var key in responseJson.results) { if (responseJson.results[key].status === "WAITING") { const validate = validateJson(responseJson.results[key].result) if (validate.valid && typeof validate.result === "string") { validate.result = JSON.parse(validate.result) } if (validate.result["information"] !== undefined && validate.result["information"] !== null) { setWorkflowQuestion(validate.result["information"]) } break } } } else { console.log("NOT updating executiondata state."); } } } if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING") { stop(); if (executionRunning) { setExecutionRunning(false); } //getWorkflowExecution(props.match.params.key, ""); } else if (responseJson.status === "FINISHED") { setExecutionRunning(false) stop(); //getWorkflowExecution(props.match.params.key, ""); } }) } const handleGetOrg = (orgId, execution_id, authorization) => { if (orgId.length === 0) { return; } // Just use this one? var url = execution_id !== undefined && authorization !== undefined ? `${globalUrl}/api/v1/orgs/${orgId}?reference_execution=${execution_id}&authorization=${authorization}` : `${globalUrl}/api/v1/orgs/${orgId}`; fetch(url, { method: "GET", credentials: "include", headers: { "Content-Type": "application/json", }, }) .then((response) => { if (response.status === 401) { } return response.json(); }) .then((responseJson) => { if (responseJson["success"] === false) { } else { if (responseJson.sync_features === undefined || responseJson.sync_features === null) { } if (document != undefined && document.title != defaultTitle) { document.title = responseJson.name + " - " + defaultTitle } setSelectedOrganization(responseJson) } }) .catch((error) => { console.log("Error getting org: ", error); }); }; const fetchUpdates = (execution_id, authorization, getorg) => { if (execution_id === undefined || execution_id === null || execution_id === "") { stop() return } const innerRequest = { "execution_id": execution_id, "authorization": authorization } if (executionRequest.execution_id !== innerRequest.execution_id) { setExecutionRequest(innerRequest) } if (execution_id === "" || authorization === "") { setExecutionLoading(false) setExecutionRunning(false) stop() return } 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!"); } return response.json(); }) .then((responseJson) => { if (getorg === true) { handleGetOrg(responseJson.org_id, execution_id, authorization) } handleUpdateResults(responseJson, executionRequest); }) .catch((error) => { console.log("Execution result Error: ", error); }); }; 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, sourceNode) if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null) { console.log("Get execution: ", execution_id) fetchUpdates(execution_id, authorization, true) } if (answer !== undefined && answer !== null) { console.log("Got answer: ", answer) } }, []) 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"} 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" const image = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.image !== undefined && selectedOrganization.image !== null && selectedOrganization.image !== "" ? selectedOrganization.image : theme.palette.defaultImage //console.log("IMG: ", image, "ORG: ", selectedOrganization) if (!disabledButtons && answer !== undefined && answer !== null && organization !== "Unknown" && buttonClicked.length === 0) { console.log("Finding button!") // Find the button var buttonid = "" if (answer === "false") { buttonid = "abort_execution" } else if (answer === "true") { buttonid = "continue_execution" } if (buttonid !== "") { const foundButton = document.getElementById(buttonid) console.log("Button: ", foundButton) if (foundButton !== undefined && foundButton !== null) { foundButton.click() } } } const basedata =
{onSubmit(e)}} style={{margin: "15px 15px 15px 15px"}}> {workflow.name} {organization} {disabledButtons && message.length > 0 ? null : {message} } {answer !== undefined && answer !== null ? null : {workflow.name} } {workflowQuestion.length > 0 ?
{workflowQuestion}
: null} {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
{workflow.input_questions.map((question, index) => { return (
{question.name} { //setExecutionArgument(e.target.value) executionArgument[question.value] = e.target.value setUpdate(Math.random()) }} />
) })}
: answer !== undefined && answer !== null ? null : Runtime Argument
{ setExecutionArgument(e.target.value) }} />
} {executionRunning ? {executionData.status !== undefined && executionData.status !== null && executionData.status !== "" ? Status: {executionData.status} : null} : ((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) ? {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? }
 or 
:
} {/*buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ? finalize workflow animation { console.log("Img loaded.") setTimeout(() => { console.log("Img closing.") setButtonClicked("finished") }, 1250) }} /> : null*/}
{executionInfo}
{answer !== undefined && answer !== null ? null : }
const loadedCheck = isLoaded ?
{basedata}
:
return (
{loadedCheck}
) } export default RunWorkflow;