diff --git a/frontend/src/components/WorkflowValidationTimeline.jsx b/frontend/src/components/WorkflowValidationTimeline.jsx new file mode 100644 index 00000000..cbbb7bd6 --- /dev/null +++ b/frontend/src/components/WorkflowValidationTimeline.jsx @@ -0,0 +1,443 @@ +import React, { useState, } from "react"; +import { makeStyles, createStyles } from "@mui/styles"; +import { toast } from "react-toastify" + +import { + Tooltip, + Typography, + + Avatar, + AvatarGroup, +} from "@mui/material" + +import { + green, + yellow, + red, +} from "../views/AngularWorkflow.jsx" + +import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; +import theme from "../theme.jsx"; +const itemHeight = 40 + +export const getParentNodes = (workflow, action) => { + if (action === undefined || action === null) { + return [] + } + + if (workflow.actions === undefined || workflow.actions === null) { + workflow.actions = [] + } + + if (workflow.triggers === undefined || workflow.triggers === null) { + workflow.triggers = [] + } + + if (workflow.branches === undefined || workflow.branches === null) { + workflow.branches = [] + } + + 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 (let parentkey in allkeys) { + if (allkeys[parentkey] === undefined) { + continue + } + + var currentnode = workflow.actions.find((element) => element.id === allkeys[parentkey]) + if (currentnode === undefined) { + currentnode = workflow.triggers.find((element) => element.id === allkeys[parentkey]) + + if (currentnode === undefined) { + console.log("Could not find parent node for: ", allkeys[parentkey]) + continue + } + } + + if (handled.includes(currentnode.id)) { + continue + } else { + handled.push(currentnode.id); + results.push(currentnode); + } + + // Get the name / label here too? + if (currentnode.length === 0) { + continue; + } + + // FIXME: This part is only handling first level, + // but needs to recurse + var incomingEdges = [] + for (var branchkey in workflow.branches) { + const branch = workflow.branches[branchkey] + if (branch.destination_id !== currentnode.id) { + continue + } + + // Go up in the levels + const parents = getParentNodes(workflow, { + id: branch.source_id, + }) + if (parents.length > 0) { + incomingEdges = incomingEdges.concat(parents) + } + + incomingEdges.push(branch) + } + + for (let i = 0; i < incomingEdges.length; i++) { + var tmp = incomingEdges[i]; + if (tmp.decorator === true) { + continue + } + + if (!allkeys.includes(tmp.source_id)) { + allkeys.push(tmp.source_id) + } + } + } + + if (results.length === allkeys.length || iterations === maxiter) { + break + } + + iterations += 1 + } + + // Remove on the end as we don't want to remove everything + results = results.filter((data) => data.id !== action.id) + results = results.filter((data) => data.type === "ACTION" || data.app_name === "Shuffle Workflow" || data.app_name === "User Input" || data.app_name === "shuffle-subflow") + results.push({ label: "Execution Argument", type: "INTERNAL" }) + + return results +} + +const WorkflowValidationTimeline = (props) => { + const { workflow, originalWorkflow, apps, getParents, execution} = props + + + if (workflow === undefined || workflow === null) { + return null + } + + if (workflow.validation === undefined || workflow.validation === null) { + return null + } + + if (workflow.actions === undefined || workflow.actions === null) { + workflow.actions = [] + } + + if (workflow.triggers === undefined || workflow.triggers === null) { + workflow.triggers = [] + + } + + if (workflow.branches === undefined || workflow.branches === null) { + workflow.branches = [] + + } + + var results = [] + if (execution !== undefined) { + results = execution.results + } + + // 1. Find startnode + // 2. Map childnodes from it + const startnodeId = workflow.start + + // Find parent of startnodeId and if it's a webhook + var relevantactions = [] + for (var key in workflow.branches) { + const branch = workflow.branches[key] + if (branch.destination_id !== startnodeId) { + continue + } + + for (var triggerkey in workflow.triggers) { + const trigger = workflow.triggers[triggerkey] + if (trigger.trigger_type !== "WEBHOOK") { + continue + } + + if (trigger.id === branch.source_id) { + trigger.order = -1 + relevantactions.push(trigger) + break + } + } + } + + + if (getParents !== undefined) { + for (var key in workflow.actions) { + const action = workflow.actions[key] + if (action.id === startnodeId) { + action.order = 0 + relevantactions.push(action) + continue + } + + const parents = getParents(action) + //const parents = getParentNodes(workflow, action) + //console.log("PARENTS", key, parents) + if (parents !== undefined && parents !== null) { + const parentfound = parents.find((element) => element.id === startnodeId) + if (parentfound !== undefined) { + + // FIXME: add order here based on how many steps away from the startnode + // This just has the parent count + action.order = parents.length + + relevantactions.push(action) + } + } + } + } else { + for (var key in workflow.triggers) { + const trigger = workflow.triggers[key] + if (trigger.trigger_type !== "SUBFLOW" && trigger.trigger_type !== "USERINPUT") { + continue + } + + if (workflow.actions.find((element) => element.id === trigger.id) === undefined) { + workflow.actions.push(trigger) + } + } + + relevantactions = workflow.actions + } + + // Sort according to how many parents a node has. MAY be wrong~ + relevantactions.sort((a, b) => { + if (a.order === undefined) { + return 1 + } + + if (b.order === undefined) { + return -1 + } + + return a.order - b.order + }) + + // FIXME: Add other relevant items as well from subflows (?) + var nodecolor = "grey" + var branchcolor = "grey" + var skipped = false + + var previousTools = false + + return ( +
+
+ {relevantactions.map((action, index) => { + action.result = {} + if (results !== undefined) { + const foundResult = results.find((element) => element.action.id === action.id) + if (foundResult !== undefined) { + action.result = foundResult + + action.status = foundResult.status + } + } + + const lastitem = index === relevantactions.length - 1 + if (!lastitem) { + if (action.app_name === "Shuffle Tools") { + if (action.status === "SUCCESS") { + branchcolor = red + + // Check action.result for the actual status + const validate = validateJson(action.result.result) + if (validate.valid) { + if (validate.result.success === true) { + branchcolor = green + } else { + branchcolor = "grey" + } + } + + + } else if (action.status === "SKIPPED") { + branchcolor = "grey" + } else { + if (action.status === undefined) { + branchcolor = green + } else { + branchcolor = red + } + } + + previousTools = true + return null + } else { + if (action.status === "SUCCESS") { + nodecolor = green + } else if (action.status === "SKIPPED") { + nodecolor = "grey" + } else { + if (action.status === undefined) { + nodecolor = green + } else { + nodecolor = red + } + } + } + } else { + nodecolor = "grey" + } + + if (action.status === "SKIPPED") { + skipped = true + } + + var image = "" + if (action.large_image !== undefined && action.large_image !== null && action.large_image !== "") { + image = action.large_image + } else { + if (originalWorkflow !== undefined) { + for (var key in originalWorkflow.actions) { + if (originalWorkflow.actions[key].id === action.id) { + image = originalWorkflow.actions[key].large_image + break + } + } + + if (image === "") { + for (var key in originalWorkflow.triggers) { + if (originalWorkflow.triggers[key].id === action.id) { + image = originalWorkflow.triggers[key].large_image + break + } + } + + } + } + } + + var founderror = "" + if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.errors !== undefined && workflow.validation.errors !== null) { + const foundError = workflow.validation.errors.find((element) => element.action_id === action.id) + if (foundError !== undefined) { + founderror = foundError.error + nodecolor = yellow + branchcolor = yellow + } + } + + if (skipped && !lastitem) { + nodecolor = "grey" + branchcolor = "grey" + } + + if (previousTools) { + previousTools = false + } else { + branchcolor = nodecolor + } + + var appgroup = [] + if (action.trigger_type === "WEBHOOK") { + nodecolor = green + branchcolor = green + } else if (action.app_name === "shuffle-subflow") { + if (action.status === "SUCCESS") { + nodecolor = green + branchcolor = green + } + + for (var subflowkey in workflow.validation.subflow_apps) { + const subflowApp = workflow.validation.subflow_apps[subflowkey] + if (subflowApp.error === action.id) { + appgroup.push(subflowApp) + } + } + + } + + var flex = index !== 0 && index !== relevantactions.length - 1 ? 1 : 3 + const branchTooltip = branchcolor === yellow ? "Check nodes for errors" : "" + + return ( +
+ {lastitem ? + +
+ + : null} + + {appgroup.length > 0 ? + + {appgroup.map((subflowApp, subflowIndex) => { + var appimage = "" + if (apps !== undefined && apps !== null && apps.length > 0) { + for (var key in apps) { + const app = apps[key] + if (app.name === subflowApp.app_name) { + appimage = apps[key].large_image + break + } + } + } + + return ( + + + + ) + })} + + : + + {founderror.length > 0 ? founderror : ``} + + } placement="top"> + + + {image !== "" ? + + : null} + + + } + + {lastitem ? null : + +
+ + } +
+ ) + })} +
+ +
+ ) +} + +export default WorkflowValidationTimeline