Fixed subflow selection with autocomplete mechanism

This commit is contained in:
frikky
2021-12-21 01:28:29 +01:00
parent 76b156750d
commit b9d30419fa
6 changed files with 321 additions and 206 deletions
+1 -1
View File
@@ -1606,7 +1606,7 @@ class AppBase:
appendresult += char appendresult += char
actionname_lower = "exec" actionname_lower = "exec"
elif actionname_lower.startswith("shuffle_cache "): elif actionname_lower.startswith("shuffle_cache ") or actionname_lower.startswith("shuffle_db "):
actionname_lower = "shuffle_cache" actionname_lower = "shuffle_cache"
actionname_lower = actionname_lower.replace(" ", "_", -1) actionname_lower = actionname_lower.replace(" ", "_", -1)
+1 -1
View File
@@ -412,7 +412,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
} }
//log.Printf("Actionresult unmarshal: %s", string(body)) //log.Printf("Actionresult unmarshal: %s", string(body))
log.Printf("[DEBUG] Got workflow result from %s of length %d. \n\nBody: %s\n\n", request.RemoteAddr, len(body), string(body)) log.Printf("[DEBUG] Got workflow result from %s of length %d.", request.RemoteAddr, len(body))
err = shuffle.ValidateNewWorkerExecution(body) err = shuffle.ValidateNewWorkerExecution(body)
if err == nil { if err == nil {
resp.WriteHeader(200) resp.WriteHeader(200)
+2 -1
View File
@@ -1000,6 +1000,7 @@ const ParsedAction = (props) => {
color="secondary" color="secondary"
style={{justifyContent: "flex-start", textAlign: "left", textTransform: "none", width: "100%",}} style={{justifyContent: "flex-start", textAlign: "left", textTransform: "none", width: "100%",}}
fullWidth fullWidth
disabled={selectedAction.description === undefined || selectedAction.description === null || selectedAction.description.length === 0}
onClick={() => { onClick={() => {
setHiddenDescription(!hiddenDescription) setHiddenDescription(!hiddenDescription)
}} }}
@@ -2654,7 +2655,7 @@ const ParsedAction = (props) => {
option === undefined || option === undefined ||
option === null || option === null ||
option.name === undefined || option.name === undefined ||
option.name === undefined option.name === null
) { ) {
return null; return null;
} }
+287 -203
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useLayoutEffect } from "react"; import React, { useState, useEffect, useLayoutEffect } from "react";
import { useInterval } from "react-powerhooks"; import { useInterval } from "react-powerhooks";
import { useTheme } from "@material-ui/core/styles"; import { makeStyles, useTheme } from "@material-ui/core/styles";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@@ -80,6 +80,7 @@ import {
AddComment as AddCommentIcon, AddComment as AddCommentIcon,
} from "@material-ui/icons"; } from "@material-ui/icons";
import Autocomplete from "@material-ui/lab/Autocomplete";
import * as cytoscape from "cytoscape"; import * as cytoscape from "cytoscape";
import * as edgehandles from "cytoscape-edgehandles"; import * as edgehandles from "cytoscape-edgehandles";
import * as clipboard from "cytoscape-clipboard"; import * as clipboard from "cytoscape-clipboard";
@@ -178,6 +179,32 @@ function removeParam(key, sourceURL) {
return rtn; return rtn;
} }
const useStyles = makeStyles({
notchedOutline: {
borderColor: "#f85a3e !important",
},
root: {
"& .MuiAutocomplete-listbox": {
border: "2px solid #f85a3e",
color: "white",
fontSize: 18,
"& li:nth-child(even)": {
backgroundColor: "#CCC",
},
"& li:nth-child(odd)": {
backgroundColor: "#FFF",
},
},
},
inputRoot: {
color: "white",
// This matches the specificity of the default styles at https://github.com/mui-org/material-ui/blob/v4.11.3/packages/material-ui-lab/src/Autocomplete/Autocomplete.js#L90
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: "#f86a3e",
},
},
});
const splitter = "|~|"; const splitter = "|~|";
const svgSize = 24; const svgSize = 24;
//const referenceUrl = "https://shuffler.io/functions/webhooks/" //const referenceUrl = "https://shuffler.io/functions/webhooks/"
@@ -320,6 +347,7 @@ const AngularWorkflow = (props) => {
const appBarSize = isCloud ? 75 : 60; const appBarSize = isCloud ? 75 : 60;
const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"]; const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"];
const unloadText = "Are you sure you want to leave without saving (CTRL+S)?"; const unloadText = "Are you sure you want to leave without saving (CTRL+S)?";
const classes = useStyles();
const [elements, setElements] = useState([]); const [elements, setElements] = useState([]);
@@ -5617,7 +5645,7 @@ const AngularWorkflow = (props) => {
actionlist.push({ actionlist.push({
type: "Shuffle DB", type: "Shuffle DB",
name: "Shuffle DB", name: "Shuffle DB",
value: "shuffle_cache", value: "$shuffle_cache",
highlight: "shuffle_cache", highlight: "shuffle_cache",
autocomplete: "shuffle_cache", autocomplete: "shuffle_cache",
example: "tmp", example: "tmp",
@@ -7119,6 +7147,14 @@ const AngularWorkflow = (props) => {
autocomplete: "exec", autocomplete: "exec",
example: "hello", example: "hello",
}) })
actionlist.push({
type: "Shuffle Database",
name: "Shuffle Database",
value: "$shuffle_cache",
highlight: "shuffle_db",
autocomplete: "shuffle_cache",
example: "hello",
})
if ( if (
workflow.workflow_variables !== null && workflow.workflow_variables !== null &&
workflow.workflow_variables !== undefined && workflow.workflow_variables !== undefined &&
@@ -7537,6 +7573,9 @@ const AngularWorkflow = (props) => {
value: "false", value: "false",
}; };
/*
// API-key has been replaced by auth key for the execution.
// Parents can now automatically execute children without auth from a user, as long as the subflow in question is owned by the same org and the subflow is actually referencing it during checkin.
console.log("SETTINGS: ", userSettings); console.log("SETTINGS: ", userSettings);
if ( if (
userSettings !== undefined && userSettings !== undefined &&
@@ -7550,8 +7589,120 @@ const AngularWorkflow = (props) => {
value: userSettings.apikey, value: userSettings.apikey,
}; };
} }
*/
} }
const handleSubflowStartnodeSelection = (e) => {
setSubworkflowStartnode(e.target.value);
const branchId = uuidv4();
const newbranch = {
source_id: workflow.triggers[selectedTriggerIndex].id,
destination_id: e.target.value.id,
source: workflow.triggers[selectedTriggerIndex].id,
target: e.target.value.id,
has_errors: false,
id: branchId,
_id: branchId,
label: "Subflow",
decorator: true,
};
if (workflow.visual_branches !== undefined) {
if (workflow.visual_branches === null) {
workflow.visual_branches = [newbranch];
} else if (workflow.visual_branches.length === 0) {
workflow.visual_branches.push(newbranch);
} else {
const foundIndex = workflow.visual_branches.findIndex(
(branch) => branch.source_id === newbranch.source_id
);
if (foundIndex !== -1) {
const currentEdge = cy.getElementById(
workflow.visual_branches[foundIndex].id
);
if (
currentEdge !== undefined &&
currentEdge !== null
) {
currentEdge.remove();
}
}
workflow.visual_branches.splice(foundIndex, 1);
workflow.visual_branches.push(newbranch);
}
}
if (workflow.id === subworkflow.id) {
const cybranch = {
group: "edges",
source: newbranch.source_id,
target: newbranch.destination_id,
id: branchId,
data: newbranch,
};
cy.add(cybranch);
}
console.log("Value to be set: ", e.target.value);
try {
workflow.triggers[
selectedTriggerIndex
].parameters[3].value = e.target.value.id;
} catch {
workflow.triggers[selectedTriggerIndex].parameters[3] =
{
name: "startnode",
value: e.target.value.id,
};
}
setWorkflow(workflow);
}
const handleWorkflowSelectionUpdate = (e) => {
setUpdate(Math.random());
workflow.triggers[
selectedTriggerIndex
].parameters[0].value = e.target.value.id;
setSubworkflow(e.target.value);
// Sets the startnode
if (e.target.value.id !== workflow.id) {
console.log("WORKFLOW: ", e.target.value);
const startnode = e.target.value.actions.find(
(action) => action.id === e.target.value.start
);
if (startnode !== undefined && startnode !== null) {
console.log("STARTNODE: ", startnode);
setSubworkflowStartnode(startnode);
try {
workflow.triggers[
selectedTriggerIndex
].parameters[3].value = startnode.id;
} catch {
workflow.triggers[
selectedTriggerIndex
].parameters[3] = {
name: "startnode",
value: startnode.id,
};
}
setWorkflow(workflow);
}
console.log("STARTNODE: ", startnode);
} else {
console.log("WORKFLOW: ", workflow);
}
setWorkflow(workflow);
}
return ( return (
<div style={appApiViewStyle}> <div style={appApiViewStyle}>
<div <div
@@ -7650,95 +7801,81 @@ const AngularWorkflow = (props) => {
display: "flex", display: "flex",
}} }}
> >
<div
style={{
width: "17px",
height: "17px",
borderRadius: 17 / 2,
backgroundColor: "#f85a3e",
marginRight: "10px",
}}
/>
<div style={{ flex: "10" }}> <div style={{ flex: "10" }}>
<b>Select a workflow to execute </b> <b>Select a workflow to execute </b>
</div> </div>
</div> </div>
{workflows === undefined || {workflows === undefined ||
workflows === null || workflows === null ||
workflows.length === 0 ? null : ( workflows.length === 0 ? null : (
<Select <Autocomplete
value={subworkflow} id="subflow_search"
SelectDisplayProps={{ autoHighlight
style: { value={subworkflow}
marginLeft: 10, classes={{ inputRoot: classes.inputRoot }}
}, ListboxProps={{
}} style: {
fullWidth backgroundColor: theme.palette.inputColor,
onChange={(e) => { color: "white",
setUpdate(Math.random()); },
workflow.triggers[ }}
selectedTriggerIndex getOptionLabel={(option) => {
].parameters[0].value = e.target.value.id; if (
setSubworkflow(e.target.value); option === undefined ||
option === null ||
option.name === undefined ||
option.name === null
) {
return null;
}
// Sets the startnode const newname = (
if (e.target.value.id !== workflow.id) { option.name.charAt(0).toUpperCase() + option.name.substring(1)
console.log("WORKFLOW: ", e.target.value); ).replaceAll("_", " ");
return newname;
}}
options={workflows}
fullWidth
style={{
backgroundColor: theme.palette.inputColor,
height: 50,
borderRadius: theme.palette.borderRadius,
}}
onChange={(event, newValue) => {
handleWorkflowSelectionUpdate({ target: { value: newValue} })
}}
renderOption={(data) => {
if (data.id === workflow.id) {
data = workflow;
}
const startnode = e.target.value.actions.find( //key={index}
(action) => action.id === e.target.value.start return (
); <MenuItem
if (startnode !== undefined && startnode !== null) { style={{
console.log("STARTNODE: ", startnode); backgroundColor: theme.palette.inputColor,
setSubworkflowStartnode(startnode); color: data.id === workflow.id ? "red" : "white",
}}
try { value={data}
workflow.triggers[ >
selectedTriggerIndex {data.name}
].parameters[3].value = startnode.id; </MenuItem>
} catch { )
workflow.triggers[ }}
selectedTriggerIndex renderInput={(params) => {
].parameters[3] = { return (
name: "startnode", <TextField
value: startnode.id, style={{
}; backgroundColor: theme.palette.inputColor,
} borderRadius: theme.palette.borderRadius,
}}
setWorkflow(workflow); {...params}
} label="Find your workflow"
console.log("STARTNODE: ", startnode); variant="outlined"
} else { />
console.log("WORKFLOW: ", workflow); );
} }}
/>
setWorkflow(workflow);
}}
style={{
backgroundColor: inputColor,
color: "white",
height: 50,
}}
>
{workflows.map((data, index) => {
if (data.id === workflow.id) {
data = workflow;
}
return (
<MenuItem
key={index}
style={{
backgroundColor: inputColor,
color: data.id === workflow.id ? "red" : "white",
}}
value={data}
>
{data.name}
</MenuItem>
);
})}
</Select>
)} )}
{workflow.triggers[selectedTriggerIndex].parameters[0].value {workflow.triggers[selectedTriggerIndex].parameters[0].value
.length === 0 ? null : workflow.triggers[selectedTriggerIndex] .length === 0 ? null : workflow.triggers[selectedTriggerIndex]
@@ -7773,113 +7910,63 @@ const AngularWorkflow = (props) => {
display: "flex", display: "flex",
}} }}
> >
<div
style={{
width: "17px",
height: "17px",
borderRadius: 17 / 2,
backgroundColor: "#f85a3e",
marginRight: "10px",
}}
/>
<div style={{ flex: "10" }}> <div style={{ flex: "10" }}>
<b>Select the Startnode</b> <b>Select the Startnode</b>
</div> </div>
</div> </div>
<Select <Autocomplete
defaultValue={""} id="subflow_node_search"
autoHighlight
value={subworkflowStartnode} value={subworkflowStartnode}
SelectDisplayProps={{ classes={{ inputRoot: classes.inputRoot }}
style: { ListboxProps={{
marginLeft: 10, style: {
}, backgroundColor: theme.palette.inputColor,
}} color: "white",
fullWidth },
onChange={(e) => { }}
setSubworkflowStartnode(e.target.value); getOptionSelected={(option, value) => option.id === value.id}
getOptionLabel={(option) => {
if (
option === undefined ||
option === null ||
option.label === undefined ||
option.label === null
) {
return "TMP";
}
const branchId = uuidv4(); const newname = (
const newbranch = { option.label.charAt(0).toUpperCase() + option.label.substring(1)
source_id: workflow.triggers[selectedTriggerIndex].id, ).replaceAll("_", " ");
destination_id: e.target.value.id, return newname;
source: workflow.triggers[selectedTriggerIndex].id, }}
target: e.target.value.id, options={subworkflow.actions}
has_errors: false, fullWidth
id: branchId, style={{
_id: branchId, backgroundColor: theme.palette.inputColor,
label: "Subflow", height: 50,
decorator: true, borderRadius: theme.palette.borderRadius,
}; }}
onChange={(event, newValue) => {
if (workflow.visual_branches !== undefined) { handleSubflowStartnodeSelection({ target: { value: newValue} })
if (workflow.visual_branches === null) { }}
workflow.visual_branches = [newbranch]; renderOption={(action) => {
} else if (workflow.visual_branches.length === 0) { const isParent = getParents(selectedTrigger).find(
workflow.visual_branches.push(newbranch);
} else {
const foundIndex = workflow.visual_branches.findIndex(
(branch) => branch.source_id === newbranch.source_id
);
if (foundIndex !== -1) {
const currentEdge = cy.getElementById(
workflow.visual_branches[foundIndex].id
);
if (
currentEdge !== undefined &&
currentEdge !== null
) {
currentEdge.remove();
}
}
workflow.visual_branches.splice(foundIndex, 1);
workflow.visual_branches.push(newbranch);
}
}
if (workflow.id === subworkflow.id) {
const cybranch = {
group: "edges",
source: newbranch.source_id,
target: newbranch.destination_id,
id: branchId,
data: newbranch,
};
cy.add(cybranch);
}
console.log("Value to be set: ", e.target.value);
try {
workflow.triggers[
selectedTriggerIndex
].parameters[3].value = e.target.value.id;
} catch {
workflow.triggers[selectedTriggerIndex].parameters[3] =
{
name: "startnode",
value: e.target.value.id,
};
}
setWorkflow(workflow);
}}
style={{
backgroundColor: inputColor,
color: "white",
height: 50,
}}
>
{subworkflow.actions.map((action, index) => {
const isParent = getParents(selectedTrigger).find(
(parent) => parent.id === action.id (parent) => parent.id === action.id
); )
return ( return (
<MenuItem <MenuItem
onMouseOver={() => {
console.log("Mouse in: ", action.id)
}}
onMouseOut={() => {
console.log("Mouse out: ", action.id)
}}
disabled={isCloud && isParent} disabled={isCloud && isParent}
key={index}
style={{ style={{
backgroundColor: inputColor, backgroundColor: theme.palette.inputColor,
color: isParent ? "red" : "white", color: isParent ? "red" : "white",
}} }}
value={action} value={action}
@@ -7887,8 +7974,21 @@ const AngularWorkflow = (props) => {
{action.label} {action.label}
</MenuItem> </MenuItem>
); );
})} }}
</Select> renderInput={(params) => {
return (
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
{...params}
label="Find your start-node"
variant="outlined"
/>
);
}}
/>
</span> </span>
)} )}
<div <div
@@ -7898,22 +7998,13 @@ const AngularWorkflow = (props) => {
display: "flex", display: "flex",
}} }}
> >
<div
style={{
width: "17px",
height: "17px",
borderRadius: 17 / 2,
backgroundColor: "#f85a3e",
marginRight: "10px",
}}
/>
<div style={{ flex: "10" }}> <div style={{ flex: "10" }}>
<b>Execution Argument</b> <b>Execution Argument</b>
</div> </div>
</div> </div>
<TextField <TextField
style={{ style={{
backgroundColor: inputColor, backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius, borderRadius: theme.palette.borderRadius,
}} }}
InputProps={{ InputProps={{
@@ -7962,6 +8053,7 @@ const AngularWorkflow = (props) => {
data={workflow.triggers[selectedTriggerIndex]} data={workflow.triggers[selectedTriggerIndex]}
/> />
) : null} ) : null}
{/*
<div <div
style={{ style={{
marginTop: "20px", marginTop: "20px",
@@ -7969,22 +8061,13 @@ const AngularWorkflow = (props) => {
display: "flex", display: "flex",
}} }}
> >
<div
style={{
width: "17px",
height: "17px",
borderRadius: 17 / 2,
backgroundColor: "#f85a3e",
marginRight: "10px",
}}
/>
<div style={{ flex: "10" }}> <div style={{ flex: "10" }}>
<b>API-key </b> <b>API-key </b>
</div> </div>
</div> </div>
<TextField <TextField
style={{ style={{
backgroundColor: inputColor, backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius, borderRadius: theme.palette.borderRadius,
}} }}
InputProps={{ InputProps={{
@@ -8008,6 +8091,7 @@ const AngularWorkflow = (props) => {
setWorkflow(workflow); setWorkflow(workflow);
}} }}
/> />
*/}
</div> </div>
</div> </div>
</div> </div>
@@ -0,0 +1,25 @@
version: '3'
services:
orborus:
image: ghcr.io/frikky/shuffle-orborus:nightly
container_name: shuffle-orborus
hostname: shuffle-orborus
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- SHUFFLE_APP_SDK_VERSION=nightly
- SHUFFLE_WORKER_VERSION=nightly
- ORG_ID=Shuffle
- ENVIRONMENT_NAME=Shuffle
- BASE_URL=http://192.168.86.39:5001
- DOCKER_API_VERSION=1.40
- SHUFFLE_SCALE_REPLICAS=5
- SHUFFLE_SWARM_CONFIG=run
restart: unless-stopped
networks:
- shuffle-executions
networks:
shuffle-executions:
driver: overlay
external: true
+5
View File
@@ -557,6 +557,11 @@ func handleSubworkflowExecution(client *http.Client, workflowExecution shuffle.W
} }
} }
if apikey == "" {
log.Printf("[DEBUG][%s] Replacing apikey with parent auth", workflowExecution.ExecutionId)
apikey = workflowExecution.Authorization
}
//handleSubworkflowExecution(workflowExecution, action) //handleSubworkflowExecution(workflowExecution, action)
status := "SUCCESS" status := "SUCCESS"
baseResult := `{"success": true}` baseResult := `{"success": true}`