{
- }}
- variant="outlined"
- color="primary"
- />
- )
- })
- : null */}
+ {data.image !== undefined && data.image !== null && data.image.length > 0 ?
+
+ :
+ null
+ }
+
+ {data.description}
+
{data.read === false ? (
- Notifications are made by Shuffle to help you discover issues or
- improvements.
+ Notifications generated made by Shuffle to help you discover issues or
+ improvements.
+ Learn more
{notifications.map((data, index) => {
diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx
index 53f40b79..558fb983 100755
--- a/frontend/src/components/ParsedAction.jsx
+++ b/frontend/src/components/ParsedAction.jsx
@@ -170,6 +170,7 @@ const ParsedAction = (props) => {
setEditorData,
setcodedata,
+ setAiQueryModalOpen,
} = props;
const classes = useStyles();
@@ -2913,14 +2914,17 @@ const ParsedAction = (props) => {
}}
disabled={autoCompleting}
onClick={() => {
- // aiSubmit(aiMsg, undefined, undefined, newSelectedAction)
- aiSubmit("Fill based on previous values", undefined, undefined, selectedAction)
+ //if (setAiQueryModalOpen !== undefined) {
+ // setAiQueryModalOpen(true)
+ //} else {
+ aiSubmit("Fill based on previous values", undefined, undefined, selectedAction)
+ //}
setAutocompleting(true)
}}
>
{autoCompleting ?
diff --git a/frontend/src/components/RuntimeDebugger.jsx b/frontend/src/components/RuntimeDebugger.jsx
index 47462d2b..ff2706f6 100644
--- a/frontend/src/components/RuntimeDebugger.jsx
+++ b/frontend/src/components/RuntimeDebugger.jsx
@@ -46,11 +46,6 @@ const RuntimeDebugger = (props) => {
const classes = useStyles();
- //const [workflowId, setWorkflowId] = useState("");
- //const [status, setStatus] = useState("FINISHED");
- //const [endTime, setEndTime] = useState(dayjs().subtract(0, 'day'))
- //const [startTime, setStartTime] = useState(dayjs().subtract(30, 'day'))
-
const [workflowId, setWorkflowId] = useState("")
const [status, setStatus] = useState("")
const [endTime, setEndTime] = useState("")
@@ -75,7 +70,7 @@ const RuntimeDebugger = (props) => {
const submitSearch = (workflowId, status, startTime, endTime, cursor, limit) => {
- setResultRows([])
+ //setResultRows([])
setSearchLoading(true)
const fetchData = {
workflow_id: workflowId,
@@ -107,22 +102,29 @@ const RuntimeDebugger = (props) => {
//data.runs[key].endTimestamp = data.runs[key].ended_at.toISOString().slice(0, 19).replace('T', ' ')
const startTimestamp = new Date(data.runs[key].started_at*1000)
data.runs[key].startTimestamp = startTimestamp.toISOString().slice(0, 19).replace('T', ' ')
+
const endTimestamp = new Date(data.runs[key].completed_at*1000)
data.runs[key].endTimestamp = endTimestamp.toISOString().slice(0, 19).replace('T', ' ')
+ if (data.runs[key].completed_at === 0 || data.runs[key].completed_at === null) {
+ data.runs[key].endTimestamp = ""
+ }
}
// Add 20 empty rows to the end of the resultRows array
// This is to make sure that the scrollbar is always visible
setResultRows(data.runs)
+ } else {
+ toast("No results found. Keeping old runs")
}
} else {
- console.error("Search error: ", data.reason)
+ toast("Failed to search for runs. Please try again.")
}
})
.catch((error) => {
setSearchLoading(false)
console.error("Error:", error);
+ toast("Failed to search for runs. Please try again (2)")
})
}
@@ -315,27 +317,63 @@ const RuntimeDebugger = (props) => {
)
},
},
- { field: 'startTimestamp', headerName: 'Start time (UTC)', width: 160, },
+ { field: 'startTimestamp', headerName: 'Start time (UTC)', width: 160,
+ renderCell: (params) => {
+ const hasError = params.row.completed_at-params.row.started_at > 300
+
+ return (
+
+ {
+ console.log("Zoom in on end timestamp is this one: ", params.row.endTimestamp)
+ //setEndTimestamp(params.row.endTimestamp)
+
+ // Make a new Date() from params.row.startTimestamp and set it in the endTime
+ const newEndTime = new Date(params.row.startTimestamp)
+ if (newEndTime !== null && newEndTime !== undefined && newEndTime !== "" && newEndTime !== "Invalid Date") {
+ // Translate newEndTime to UTC no matter what timezone we are in. Based it on local()
+ // Plus 1 minute to make sure it comes in
+ setEndTime(dayjs(newEndTime.setMinutes(newEndTime.getMinutes()+1)))
+
+ // Use dayjs to translate it into something useful
+
+ // Remove 5 minutes from it and set startTime
+ //newEndTime.setMinutes(newEndTime.getMinutes()-5)
+ //setStartTime(dayjs(newEndTime))
+ }
+ }}>
+ {params.row.startTimestamp}
+
+
+ )
+ }
+ },
{ field: 'endTimestamp', headerName: 'End time (UTC)', width: 160, },
{
field: 'id',
headerName: 'Explore',
width: 65,
- renderCell: (params) => (
-
- {params.row.result !== null && params.row.result !== undefined && params.row.result !== "" ?
- params.row.result
- :
- null
- }
-
- } >
-
-
-
-
- ),
+ 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
+
+ return (
+
+ {params.row.result !== null && params.row.result !== undefined && params.row.result !== "" ?
+ params.row.result
+ :
+ null
+ }
+
+ }>
+
+
+
+
+
+
+ )
+ }
},
]
@@ -522,6 +560,7 @@ const RuntimeDebugger = (props) => {
minWidth: 240,
maxWidth: 240,
}}
+ ampm={false}
label="Search from"
format="YYYY-MM-DD HH:mm:ss"
value={startTime}
@@ -535,6 +574,7 @@ const RuntimeDebugger = (props) => {
minWidth: 240,
maxWidth: 240,
}}
+ ampm={false}
label="Search until"
format="YYYY-MM-DD HH:mm:ss"
value={endTime}
diff --git a/frontend/src/components/ShuffleCodeEditor.jsx b/frontend/src/components/ShuffleCodeEditor.jsx
index 91db5f49..3053083c 100644
--- a/frontend/src/components/ShuffleCodeEditor.jsx
+++ b/frontend/src/components/ShuffleCodeEditor.jsx
@@ -550,7 +550,6 @@ const CodeEditor = (props) => {
var code_lines = localcodedata.split('\n')
for (var i = 0; i < code_lines.length; i++){
var current_code_line = code_lines[i]
- console.log("Codeline: ", current_code_line)
var variable_occurence = current_code_line.match(/[\\]{0,1}[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g)
@@ -612,8 +611,6 @@ const CodeEditor = (props) => {
const fixedVariable = fixVariable(variable_occurence[occ])
var correctVariable = availableVariables.includes(fixedVariable)
if(!correctVariable) {
- console.log("Line: ", i, "ch: ", dollar_occurence[occ])
-
value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: rgb(248, 106, 62, 0.9); padding-top: 2px; padding-bottom: 2px; color: white"})
} else {
value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: #8b8e26; padding-top: 2px; padding-bottom: 2px; color: white"})
@@ -664,6 +661,23 @@ const CodeEditor = (props) => {
setlocalcodedata(updatedCode)
}
+ const fixStringInput = (new_input) => {
+ // Newline fixes
+ new_input = new_input.replace(/\r\n/g, "\\n")
+ new_input = new_input.replace(/\n/g, "\\n")
+
+ // Quote fixes
+ new_input = new_input.replace(/\\"/g, '"')
+ new_input = new_input.replace(/"/g, '\\"')
+
+ new_input = new_input.replace(/\\'/g, "'")
+ new_input = new_input.replace(/'/g, "\\'")
+
+
+ return new_input
+ }
+
+
const expectedOutput = (input) => {
//const found = input.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g)
@@ -687,20 +701,26 @@ const CodeEditor = (props) => {
valuefound = true
+ console.log("Here. Checking if we got an example?")
try {
if (typeof actionlist[j].example === "object") {
+
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
} else if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) {
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
} else {
- input = input.replace(found[i], actionlist[j].example, -1)
+ console.log("This?")
+
+ const newExample = fixStringInput(actionlist[j].example)
+ input = input.replace(found[i], newExample, -1)
}
} catch (e) {
input = input.replace(found[i], actionlist[j].example, -1)
}
}
+
//if (!valuefound) {
// console.log("Couldn't find value "+fixedVariable)
//}
@@ -736,7 +756,10 @@ const CodeEditor = (props) => {
new_input = JSON.stringify(new_input)
} else {
if (typeof new_input === "string") {
- new_input = new_input
+ // Check if it contains any newlines, and replace them with raw newlines
+ new_input = fixStringInput(new_input)
+
+ // Replace quotes with nothing
} else {
console.log("NO TYPE? ", typeof new_input)
try {
@@ -747,7 +770,6 @@ const CodeEditor = (props) => {
}
}
- //console.log("FOUND2: ", fixedVariable, actionlist[j].example)
input = input.replace(fixedVariable, new_input, -1)
input = input.replace(found[i], new_input, -1)
@@ -1430,7 +1452,6 @@ const CodeEditor = (props) => {
highlight_variables(value)
}}
onChange={(value, viewUpdate) => {
- console.log("Value: ", value, viewUpdate)
setlocalcodedata(value)
expectedOutput(value)
diff --git a/frontend/src/defaultCytoscapeStyle.jsx b/frontend/src/defaultCytoscapeStyle.jsx
index 01c389f8..7e7132fd 100644
--- a/frontend/src/defaultCytoscapeStyle.jsx
+++ b/frontend/src/defaultCytoscapeStyle.jsx
@@ -4,11 +4,10 @@ const data = [
css: {
label: "data(label)",
"text-valign": "center",
- "font-family":
- "Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif",
+ "font-family": "Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif",
"font-weight": "lighter",
- "margin-right": "10px",
"font-size": "18px",
+ "margin-right": "10px",
width: "80px",
height: "80px",
color: "white",
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx
index 65038a5b..d596ffe2 100755
--- a/frontend/src/views/AngularWorkflow.jsx
+++ b/frontend/src/views/AngularWorkflow.jsx
@@ -112,6 +112,7 @@ import {
AutoFixHigh as AutoFixHighIcon,
Polyline as PolylineIcon,
QueryStats as QueryStatsIcon,
+ AutoAwesome as AutoAwesomeIcon,
} from "@mui/icons-material";
@@ -438,6 +439,7 @@ const AngularWorkflow = (defaultprops) => {
const [appAuthentication, setAppAuthentication] = React.useState(undefined);
const [variablesModalOpen, setVariablesModalOpen] = React.useState(false);
+ const [aiQueryModalOpen, setAiQueryModalOpen] = React.useState(false)
const [executionVariablesModalOpen, setExecutionVariablesModalOpen] =
React.useState(false);
const [authenticationModalOpen, setAuthenticationModalOpen] = React.useState(false);
@@ -592,17 +594,22 @@ const AngularWorkflow = (defaultprops) => {
const [elements, setElements] = useState([]);
const [loopRunning, setLoopRunning] = useState(false)
+ var loopRunning2 = loopRunning
const stop = () => {
setLoopRunning(false)
+ loopRunning2 = false
}
const start = () => {
setLoopRunning(true)
+ loopRunning2 = true
}
useEffect(() => {
- //console.log("In useeffect for loopRunning: ", loopRunning)
- if (loopRunning) {
+ // Current variable + future state controlled
+ // This is so that the loop can stop itself as well
+ console.log("In useeffect for loopRunning: ", loopRunning, loopRunning2)
+ if (loopRunning && loopRunning2) {
const intervalId = setInterval(() => {
if (!loopRunning) {
clearInterval(intervalId);
@@ -983,19 +990,21 @@ const AngularWorkflow = (defaultprops) => {
return response.json();
})
.then((responseJson) => {
- if (responseJson !== undefined && responseJson !== null && responseJson.executions !== undefined && responseJson.executions !== null && responseJson.executions.length > 0) {
+ console.log("GOT A RESPONSE??")
+ if (responseJson !== undefined && responseJson !== null && responseJson.executions !== undefined && responseJson.executions !== null) {
// - means it's opposite
const newkeys = sortByKey(responseJson.executions, "-started_at");
setWorkflowExecutions(newkeys);
const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
-
var tmpView = new URLSearchParams(cursearch).get("execution_id");
if (execution_id !== undefined && execution_id !== null && execution_id.length > 0 && (tmpView === undefined || tmpView === null || tmpView.length === 0)) {
tmpView = execution_id;
}
+ console.log("EXECUTION ID: ", tmpView)
+
// Compare with currently selected item
if (tmpView !== undefined && tmpView !== null && tmpView.length > 0) {
// Don't clean up if it's already open
@@ -1046,7 +1055,21 @@ const AngularWorkflow = (defaultprops) => {
}, 5000);
}
}
- }
+ } else {
+ const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
+ var tmpView = new URLSearchParams(cursearch).get("execution_id");
+ console.log("Alertnative execution id check: ", tmpView)
+
+ if (tmpView === undefined || tmpView === null || tmpView.length === 0) {
+ const execution_id = tmpView;
+ setExecutionModalView(1);
+ setExecutionRequest({
+ execution_id: execution_id,
+ });
+
+ start()
+ }
+ }
})
.catch((error) => {
//toast(error.toString());
@@ -1067,8 +1090,14 @@ const AngularWorkflow = (defaultprops) => {
})
.then((response) => {
if (response.status !== 200) {
- console.log("Status not 200 for stream results :O!");
stop();
+ setExecutionModalView(0);
+ toast("Failed loading the workflow run")
+ console.log("Status not 200 for stream results :O!");
+
+ //const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
+ //const newitem = removeParam("execution_id", cursearch);
+ //navigate(curpath + newitem)
}
return response.json();
@@ -1247,10 +1276,11 @@ const AngularWorkflow = (defaultprops) => {
// Controls the colors and direction of execution results.
// Style is in defaultCytoscapeStyle.js
const handleUpdateResults = (responseJson, executionRequest) => {
- if (responseJson === undefined || responseJson === null || responseJson.success === false) {
- return
- }
- //console.log(responseJson)
+ if (responseJson === undefined || responseJson === null || responseJson.success === false) {
+ stop()
+ return
+ }
+//console.log(responseJson)
// Loop nodes and find results
// Update on every interval? idk
@@ -1263,7 +1293,8 @@ const AngularWorkflow = (defaultprops) => {
//console.log("Updating data!")
setExecutionData(responseJson)
} else {
- if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING") {
+ if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING" || responseJson.status === "FINISHED") {
+ console.log("DONE!")
stop()
}
@@ -1321,6 +1352,7 @@ const AngularWorkflow = (defaultprops) => {
})
};
+ var streamDisabled2 = false
const sendStreamRequest = (body) => {
//console.log("Stream not activated yet.")
if (!isCloud) {
@@ -1356,6 +1388,9 @@ const AngularWorkflow = (defaultprops) => {
.then((response) => {
setSavingState(0);
if (response.status !== 200) {
+
+ setStreamDisabled(true)
+ streamDisabled2 = true
//console.log("Status not 200 for stream :O!");
}
@@ -1367,6 +1402,7 @@ const AngularWorkflow = (defaultprops) => {
.catch((error) => {
console.log("Stream send error: ", error.toString())
setStreamDisabled(true)
+ streamDisabled2 = true
})
}
@@ -2537,6 +2573,17 @@ const AngularWorkflow = (defaultprops) => {
try {
var chunkJson = JSON.parse(chunk)
+ if (chunkJson.success === false) {
+ console.log("Chunk failed: ", chunkJson)
+
+ if (!streamDisabled) {
+ setStreamDisabled(true)
+ streamDisabled2 = true
+ }
+ return
+ }
+
+
if (chunkJson.item !== undefined && chunkJson.item !== null && chunkJson.item !== "") {
if (chunkJson.item === "node") {
if (chunkJson.type === "move") {
@@ -2561,6 +2608,13 @@ const AngularWorkflow = (defaultprops) => {
}
} catch (e) {
console.log("Chunk JSON error: ", e)
+
+ if (!streamDisabled) {
+ setStreamDisabled(true)
+ streamDisabled2 = true
+ }
+
+ return
}
//data.push(chunk)
@@ -2619,7 +2673,7 @@ const AngularWorkflow = (defaultprops) => {
const streamUrl = "https://stream.shuffler.io"
const url = `${streamUrl}/api/v1/workflows/${workflowId}/stream`
while (true) {
- if (streamDisabled) {
+ if (streamDisabled === true || streamDisabled2 === true) {
break
}
@@ -3021,7 +3075,7 @@ const AngularWorkflow = (defaultprops) => {
sendStreamRequest({
"item": "node",
"type": "unselect",
- "userid": userdata.id,
+ "id": workflow.id,
})
//}, 150)
};
@@ -4071,7 +4125,6 @@ const AngularWorkflow = (defaultprops) => {
"item": "node",
"type": "select",
"id": data.id,
- "userid": userdata.id,
"location": {
"x": event.target.position("x"),
"y": event.target.position("y"),
@@ -5985,10 +6038,10 @@ const AngularWorkflow = (defaultprops) => {
const xParsed = destinationnodePosition.x - sourcenodePosition.x
const yParsed = destinationnodePosition.y - sourcenodePosition.y
- const z = Math.sqrt(xParsed * xParsed + yParsed * yParsed);
- const costheta = xParsed / z;
- const alpha = 0.25;
- var controlPointDistance = [-alpha * yParsed * costheta, alpha * yParsed * costheta];
+ const z = Math.sqrt(xParsed * xParsed + yParsed * yParsed)
+ const costheta = xParsed / z
+ const alpha = 0.3
+ var controlPointDistance = [-alpha * yParsed * costheta, alpha * yParsed * costheta]
var controlPointWeight = [alpha, 1 - alpha]
//'control-point-weight': ['0.33', '0.66'],
@@ -6151,8 +6204,18 @@ const AngularWorkflow = (defaultprops) => {
const foundtriggers = inputworkflow.triggers.map((trigger) => {
const node = {};
node.position = trigger.position;
- node.data = trigger;
+ if (trigger.large_image === undefined || trigger.large_image === null || trigger.large_image.length === 0) {
+
+ // Search triggers array for it where the name is matching and set image
+ var foundTrigger = triggers.find((t) => t.name === trigger.name)
+ if (foundTrigger !== undefined && foundTrigger !== null) {
+ console.log("Autofilled missing trigger image")
+ trigger.large_image = foundTrigger.large_image
+ }
+ }
+
+ node.data = trigger;
node.data._id = trigger["id"];
node.data.id = trigger["id"];
node.data.type = "TRIGGER";
@@ -6394,13 +6457,13 @@ const AngularWorkflow = (defaultprops) => {
sendStreamRequest({
"item": "workflow",
"type": "enter",
- "userid": userdata.id,
+ "id": workflow.id,
})
}
const fetchRecommendations = (inputWorkflow) => {
- //console.log("Disabled recommendations")
- //return
+ console.log("Disabled recommendations as they were too inaccurate")
+ return
const parsedWorkflow = JSON.parse(JSON.stringify(inputWorkflow))
@@ -6500,6 +6563,11 @@ const AngularWorkflow = (defaultprops) => {
return response.json();
})
.then((responseJson) => {
+ if (responseJson === null) {
+ console.log("No revisions found")
+ return
+ }
+
if (responseJson.success === false) {
console.log("Error getting workflow revisions: ", responseJson)
return
@@ -6553,13 +6621,16 @@ const AngularWorkflow = (defaultprops) => {
}
// App length necessary cus of cy initialization
- if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0 && workflowRecommendations !== undefined) {
+ // Not using recommendations, so skipping this for now
+ //if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0 && workflowRecommendations !== undefined) {
+ if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
setGraphSetup(true);
setupGraph(workflow);
console.log("In graph setup")
// 2nd load - configures cytoscape
- } else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && appAuthentication !== undefined && workflowRecommendations !== undefined) {
+ //} else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && appAuthentication !== undefined && workflowRecommendations !== undefined) {
+ } else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && appAuthentication !== undefined) {
console.log("In POST graph setup!")
@@ -9086,6 +9157,33 @@ const AngularWorkflow = (defaultprops) => {
backgroundColor: theme.palette.inputColor,
};
+ const aiQueryModal =
+
+
+
const conditionsModal = (