diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod
index 8e6d42ad..11da76e2 100644
--- a/backend/go-app/go.mod
+++ b/backend/go-app/go.mod
@@ -1,6 +1,6 @@
module shuffle-shared
-//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
+replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
go 1.19
diff --git a/frontend/src/components/RuntimeDebugger.jsx b/frontend/src/components/RuntimeDebugger.jsx
index 1b007021..d5df7d4e 100644
--- a/frontend/src/components/RuntimeDebugger.jsx
+++ b/frontend/src/components/RuntimeDebugger.jsx
@@ -4,6 +4,7 @@ import {
TextField,
Link,
Button,
+ ButtonGroup,
CircularProgress,
Select,
MenuList,
@@ -13,6 +14,7 @@ import {
Autocomplete,
Tooltip,
Typography,
+ IconButton,
} from '@mui/material';
import { toast } from "react-toastify"
@@ -31,6 +33,8 @@ import {
import {
OpenInNew as OpenInNewIcon,
PlayArrow as PlayArrowIcon,
+ Insights as InsightsIcon,
+ Replay as ReplayIcon,
} from '@mui/icons-material';
import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid'
@@ -56,6 +60,7 @@ const RuntimeDebugger = (props) => {
const [rowCursor, setCursor] = useState("")
const [rowsPerPage, setRowsPerPage] = useState(10)
const [resultRows, setResultRows] = useState([])
+ const [selectedWorkflowExecutions, setSelectedWorkflowExecutions] = useState([])
const [workflows, setWorkflows] = useState([
{"id": "", "name": "All Workflows",}
])
@@ -198,6 +203,9 @@ const RuntimeDebugger = (props) => {
} else if (source === "subflow" || source.length === 36) {
foundSource =
source = "subflow"
+ } else if (source === "rerun" || source.length === 36) {
+ foundSource =
+ source = "rerun of a previous run"
} else {
source = "manual"
}
@@ -278,20 +286,31 @@ const RuntimeDebugger = (props) => {
width: 75,
renderCell: (params) => {
var foundItems = 0
- var extraItems = 0
+ var foundSkipped = 0
if (params.row.results !== null && params.row.results !== undefined) {
for (let key in params.row.results) {
if (params.row.results[key].status === "SUCCESS") {
foundItems += 1
}
+
+ if (params.row.results[key].status === "SKIPPED") {
+ foundSkipped += 1
+ }
}
}
+ var foundError = ""
+ if (foundItems + foundSkipped < params.row.workflow.actions.length && params.row.status === "FINISHED") {
+ foundError = "Workflow is done, but all nodes are not finished. This most likely indicates a problem with the workflow"
+ }
+
return (
- {
- }}>
- {foundItems}
-
+
+ 0 ? "rgba(244,0,0,0.6)" : "inherit"}} onClick={() => {
+ }}>
+ {foundItems}
+
+
)
},
},
@@ -301,7 +320,6 @@ const RuntimeDebugger = (props) => {
width: 75,
renderCell: (params) => {
var foundItems = 0
- var extraItems = 0
if (params.row.results !== null && params.row.results !== undefined) {
for (let key in params.row.results) {
if (params.row.results[key].status === "SKIPPED") {
@@ -353,14 +371,81 @@ const RuntimeDebugger = (props) => {
{
field: 'id',
headerName: 'Explore',
- width: 65,
+ width: 100,
renderCell: (params) => {
- const parsedResult = params.row.result === null || params.row.result === undefined || params.row.result === "" ? null : params.row.result
- const hasError = parsedResult !== null && parsedResult !== undefined && parsedResult !== "" ? parsedResult.includes("{%") && parsedResult.includes("%}") : false
+ const parsedResult = params.row.result === null || params.row.result === undefined || params.row.result === "" ? "" : params.row.result
+
+ var errorReason = ""
+ var hasError = parsedResult !== null && parsedResult !== undefined && parsedResult !== "" ? parsedResult.includes("{%") && parsedResult.includes("%}") : false
+
+ if (hasError) {
+ errorReason = "Liquid parsing error"
+ }
+
+ // if success: false
+ // if node == FAILURE or ABORTED
+ if (parsedResult.includes(`\"success\": false`)) {
+ errorReason = "success: false in last result"
+ hasError = true
+ }
+
+ if (!hasError && parsedResult.includes(`\"status\":`)) {
+ // Look for any status that is 300 or higher
+ const statusSplit = parsedResult.split(`\"status\":`)
+ if (statusSplit.length > 1) {
+ var foundStatus = statusSplit[1].trim()
+ // Check if pattern is \d,
+ if (foundStatus.includes(",")) {
+ const foundStatusSplit = foundStatus.split(",")
+
+ if (foundStatusSplit.length > 1) {
+ foundStatus = foundStatusSplit[0].trim()
+ // Check if it's a number
+ }
+ } else {
+ foundStatus = ""
+ }
+
+ if (!isNaN(foundStatus) && foundStatus >= 300) {
+ errorReason = "Status code: "+foundStatus
+ hasError = true
+ }
+ }
+ }
+
+ if (!hasError) {
+ // Find last node that isn't skipped and check status
+ var lastresult = {}
+ for (var key in params.row.results) {
+ const result = params.row.results[key]
+ if (result.status === "SKIPPED") {
+ continue
+ }
+
+ if (result.completed_at === undefined || result.completed_at === null) {
+ continue
+ }
+
+ if (result.completed_at >= lastresult.completed_at) {
+ lastresult = result
+ }
+ }
+
+ if (lastresult.id !== undefined && lastresult.status !== "SUCCESS" && lastresult.status !== "SKIPPED") {
+ errorReason = "Bad status for last node: "+lastresult.status
+ hasError = true
+ }
+ }
+
+ if (!hasError && params.row.notifications_created !== null && params.row.notifications_created !== undefined && params.row.notifications_created !== 0) {
+ hasError = true
+ errorReason = "Generated notifications: "+params.row.notifications_created
+ }
return (
+
+ Workflow result: {errorReason}
{params.row.result !== null && params.row.result !== undefined && params.row.result !== "" ?
params.row.result
:
@@ -368,10 +453,19 @@ const RuntimeDebugger = (props) => {
}
}>
-
+
-
+
+ {
+ window.open(`${globalUrl}/api/v1/workflows/search/${params.row.id}`, "_blank")
+ }}
+ disabled={!userdata.support}
+ >
+
+
)
@@ -383,7 +477,6 @@ const RuntimeDebugger = (props) => {
// Check if the user is currently focusing a texxtfield or not
// If they are, don't submit the search
if (document.activeElement.tagName === "INPUT") {
- console.log("User is focusing a textfield, not submitting search")
return
}
@@ -403,6 +496,29 @@ const RuntimeDebugger = (props) => {
setEndTime(date)
}
+ const abortExecution = (workflowId, executionId) => {
+ fetch(`${globalUrl}/api/v1/workflows/${workflowId}/executions/${executionId}/abort`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for ABORT EXECUTION :O!");
+ }
+
+ return response.json();
+ })
+ .catch((error) => {
+ toast.error("Error aborting execution: "+error.toString())
+ });
+ };
+
const handleWorkflowSelectionUpdate = (e, isUserinput) => {
if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) {
console.log("Returning as there's no id")
@@ -417,9 +533,106 @@ const RuntimeDebugger = (props) => {
submitSearch(e.target.value.id, status, startTime, endTime, rowCursor, rowsPerPage)
}
+ const executeWorkflow = (execution) => {
+ const data = {
+ execution_argument: execution.execution_argument,
+ start: execution.start,
+ execution_source: "rerun",
+ };
+
+ fetch(`${globalUrl}/api/v1/workflows/${execution.workflow.id}/execute?start=${execution.start}`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ body: JSON.stringify(data),
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for WORKFLOW EXECUTION :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (!responseJson.success) {
+ toast("Error executing workflow: "+responseJson.error)
+ } else {
+ console.log("Executed workflow: ", responseJson)
+ }
+ })
+ .catch((error) => {
+ toast("Failed to execute workflow: "+error.toString())
+ });
+ }
+
return (
-
Workflow Run Debugger
+
+
+
Workflow Run Debugger
+ {selectedWorkflowExecutions.length > 0 ?
+
+
+
+
+
+
+
+
+
+ : null}
+
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx
index b7cfea17..6bde408c 100755
--- a/frontend/src/views/AngularWorkflow.jsx
+++ b/frontend/src/views/AngularWorkflow.jsx
@@ -12074,7 +12074,7 @@ const AngularWorkflow = (defaultprops) => {
workflow.triggers[selectedTriggerIndex].parameters[0].value
}
color="primary"
- placeholder="defaultValue"
+ placeholder="10"
onBlur={(e) => {
setTriggerCronWrapper(e.target.value);
}}
@@ -15628,7 +15628,8 @@ const AngularWorkflow = (defaultprops) => {
marginBottom: "auto",
}}
>
- {data.action.label.replaceAll("_", " ")}
+ {data.action.label === undefined || data.action.label === null || data.action.label === "" ? data.action.label : data.action.label.replaceAll("_", " ")}
+