From 80782d8270a9a861b2db4ccdaf7881cb57829650 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 19 Feb 2025 01:29:40 +0100 Subject: [PATCH] Onprem X Cloud sync commit. Large changes to the navbar mechanisms --- backend/go-app/go.mod | 2 +- frontend/src/components/EditWorkflow.jsx | 65 ++++- frontend/src/components/EnvironmentTab.jsx | 31 ++- frontend/src/components/Files.jsx | 2 +- frontend/src/components/LeftSideBar.jsx | 43 ++- frontend/src/components/ParsedAction.jsx | 111 +++++--- .../src/components/ShuffleCodeEditor1.jsx | 19 +- .../src/components/WorkflowTemplatePopup.jsx | 2 +- .../src/components/WorkflowTemplatePopup2.jsx | 39 ++- frontend/src/defaultCytoscapeStyle.jsx | 32 +++ frontend/src/theme.jsx | 56 +++- frontend/src/views/AngularWorkflow.jsx | 137 ++++++--- frontend/src/views/ApiExplorerWrapper.jsx | 2 +- frontend/src/views/AppCreator.jsx | 2 +- frontend/src/views/Docs.jsx | 2 +- frontend/src/views/Usecases2.jsx | 262 +++++++++++++----- functions/onprem/orborus/go.mod | 2 +- functions/onprem/worker/go.mod | 2 +- 18 files changed, 603 insertions(+), 208 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index ec7db684..34c45a2a 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -18,7 +18,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.7.92 + github.com/shuffle/shuffle-shared v0.7.95 golang.org/x/crypto v0.32.0 google.golang.org/api v0.176.1 google.golang.org/grpc v1.68.1 diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index cf51720b..df437f77 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -88,6 +88,8 @@ const EditWorkflow = (props) => { const [inputMarkdown, setInputMarkdown] = React.useState(workflow?.form_control?.input_markdown !== undefined && workflow?.form_control?.input_markdown !== null ? workflow?.form_control?.input_markdown : "") const [scrollDone, setScrollDone] = React.useState(false) const [selectedYieldActions, setSelectedYieldActions] = React.useState(workflow?.form_control?.output_yields !== undefined && workflow?.form_control?.output_yields !== null ? JSON.parse(JSON.stringify(workflow?.form_control?.output_yields)) : []) + const [selectedCleanupActions, setSelectedCleanupActions] = React.useState(workflow?.form_control?.cleanup_actions !== undefined && workflow?.form_control?.cleanup_actions !== null ? JSON.parse(JSON.stringify(workflow?.form_control?.cleanup_actions)) : []) + const [formWidth, setFormWidth] = React.useState(boxWidth === undefined || boxWidth === null ? 500 : boxWidth) const classes = useStyles(); @@ -323,6 +325,7 @@ const EditWorkflow = (props) => { innerWorkflow.form_control.input_markdown = inputMarkdown innerWorkflow.form_control.output_yields = selectedYieldActions innerWorkflow.form_control.form_width = formWidth + innerWorkflow.form_control.cleanup_actions = selectedCleanupActions innerWorkflow.name = name innerWorkflow.description = description @@ -568,7 +571,7 @@ const EditWorkflow = (props) => { - MSSP & Distribution controls + Multi-Tenancy, Backups & Security @@ -678,7 +681,7 @@ const EditWorkflow = (props) => { } - + Git Backup Repository @@ -824,6 +827,60 @@ const EditWorkflow = (props) => { +
+ + Result cleanup ({selectedCleanupActions.length === 0 ? "No cleanup yet" : selectedCleanupActions.length === 1 ? "Cleaning up 1 node" : `Cleaning up ${selectedCleanupActions.length} nodes`}) + + + + Beta Feature: When a workflow run is done, the data from the selected actions will be removed by replacing it with a default value. This is useful for cleaning up sensitive data, or data that is no longer needed. This is done after a workflow run is finished or aborted, and is not reversible. Data will remain in the workflow run result (last node value) even if the action result itself is cleaned up. + + + + + +
+ @@ -1023,11 +1080,11 @@ const EditWorkflow = (props) => {
- Output Control ({selectedYieldActions.length === 0 ? "No Returns" : selectedYieldActions.length === 1 ? "Returning 1 node" : `Returning ${selectedYieldActions.length} nodes`}) + Form Output Control ({selectedYieldActions.length === 0 ? "No Returns" : selectedYieldActions.length === 1 ? "Returning 1 node" : `Returning ${selectedYieldActions.length} nodes`}) - When running this workflow, the output will be shown as a Markdown object by default, with JSON objects being rendered. By adding nodes below, they will be shown while the workflow is running as soon as they get a result. Failing/Skipped nodes are not shown. This makes it possible to track progress for more complex usecases. + When running this workflow as a form, the output will be shown as a Markdown object by default, with JSON objects being rendered. By adding nodes below, they will be shown while the workflow is running as soon as they get a result. Failing/Skipped nodes are not shown. This makes it possible to track progress for more complex usecases. diff --git a/frontend/src/components/EnvironmentTab.jsx b/frontend/src/components/EnvironmentTab.jsx index be7c2c0d..22a05f18 100644 --- a/frontend/src/components/EnvironmentTab.jsx +++ b/frontend/src/components/EnvironmentTab.jsx @@ -1064,17 +1064,26 @@ const EnvironmentTab = memo((props) => { }} style={{minWidth:120, display: "table-cell",}} primary={ - IP / label: {environment?.running_ip?.split(":")[0]}. May stay running up to a minute after stopping Orborus. - : - "Cloud is automatically configured. Reachout to support@shuffler.io if you have any questions." - } placement="top"> + + {environment.Type !== "cloud" + ? environment.running_ip === undefined || + environment.running_ip === null || + environment.running_ip.length === 0 + ? + "Not running. Click to get the start command that can be ran on your server." + : + IP / label: {environment?.running_ip?.split(":")[0]}. May stay running up to a minute after stopping Orborus. + : + "Cloud is automatically configured. Reachout to support@shuffler.io if you have any questions." + } + +
+
+ + Last checkin: {environment?.checkin !== undefined && environment.checkin !== null && environment?.checkin > 0 ? new Date(environment?.checkin * 1000).toLocaleString() : "Never"} + + } placement="top"> { } const isDistributed = file?.suborg_distribution?.length > 0 ? true : false; const filenamesplit = file.filename.split(".") - const iseditable = file.filesize < 2000000 && file.status === "active" && allowedFileTypes.includes(filenamesplit[filenamesplit.length-1]) + const iseditable = file.filesize < 2000000 && file.status === "active" && (allowedFileTypes.includes(filenamesplit[filenamesplit.length-1]) || !file?.filename.includes(".")) return ( { - Version: 2.0.0-rc6 + Version: 2.0.0-rc7 @@ -812,14 +813,36 @@ useEffect(() => { setExpandLeftNav(false) } }} - > - - Shuffle Logo - + > + + + Shuffle Logo + + { : "#C8C8C8" }} > - Shuffle Agent + Hybrid Locations diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 10d506ac..85840e8d 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -688,7 +688,7 @@ const ParsedAction = (props) => { if (newActionList.find((item) => item.type === "Shuffle DB") === undefined) { let cacheKey = { type: "Shuffle DB", - name: "Shuffle DB", + name: "Shuffle Datastore", value: "$shuffle_cache", highlight: "shuffle_cache", autocomplete: "shuffle_cache", @@ -753,44 +753,78 @@ const ParsedAction = (props) => { if (parents.length > 1) { const labels = []; for (let parentNode of parents) { - if (parentNode.label !== "Runtime Argument" && !labels.includes(parentNode.label)) { - labels.push(parentNode.label); - let exampleData = parentNode.example ?? ""; - if (!exampleData && workflowExecutions.length > 0) { - for (let exec of workflowExecutions) { - const foundResult = exec.results?.find(result => result.action.id === parentNode.id); - if (foundResult) { - const valid = validateJson(foundResult.result); - if (valid.valid && valid.result.success !== false) { - exampleData = valid.result; - break; - } + if (parentNode.label === "Runtime Argument" || labels.includes(parentNode.label)) { + continue + } + + labels.push(parentNode.label); + let exampleData = parentNode.example ?? ""; + if (parentNode?.app_name === "http") { + exampleData = "" + } + + if (workflowExecutions.length > 0) { + for (let exec of workflowExecutions) { + const foundResult = exec.results?.find(result => result?.action?.id === parentNode?.id); + if (foundResult) { + const valid = validateJson(foundResult.result); + if (valid.valid && valid.result.success !== false) { + exampleData = valid.result + break } } } - - if (parentNode.label === undefined) { - parentNode.label = "" - } - - newActionList.push({ - type: "action", - id: parentNode.id, - name: parentNode.label, - autocomplete: parentNode.label.split(" ").join("_"), - example: exampleData, - }); - - parentActionList.push({ - type: "action", - id: parentNode.id, - name: parentNode.label, - autocomplete: parentNode.label.split(" ").join("_"), - example: exampleData, - }); - - } + + if (exampleData === "" && apps !== undefined && apps !== null && apps?.length > 0) { + // Check apps if it exists, then if it + const foundApp = apps?.find(app => app?.id === parentNode?.app_id) + if (foundApp !== undefined && foundApp !== null) { + if (foundApp?.generated === true || foundApp?.name === "http") { + const validationData = validateJson(`{ + "status": 200, + "body": { + "example": "json", + "values": "json" + }, + "url": "https://example.com", + "headers": { + "Content-Type": "application/json", + "Example-Header": "two" + }, + "cookies": { + "example": "session", + "__session": "sessionid" + }, + "success": true + }`) + + if (validationData.valid) { + exampleData = validationData.result + } + } + } + } + + if (parentNode.label === undefined) { + parentNode.label = "" + } + + newActionList.push({ + type: "action", + id: parentNode.id, + name: parentNode.label, + autocomplete: parentNode.label.split(" ").join("_"), + example: exampleData, + }); + + parentActionList.push({ + type: "action", + id: parentNode.id, + name: parentNode.label, + autocomplete: parentNode.label.split(" ").join("_"), + example: exampleData, + }); } } } @@ -840,8 +874,8 @@ const ParsedAction = (props) => { return { ...param, value: paramvalue, error: message } }); - setSelectedActionParameters(newParameters); - setActionlist(newActionList); + setSelectedActionParameters(newParameters) + setActionlist(newActionList) }, [workflow.execution_variables, paramUpdate, workflow.workflow_variables, workflowExecutions, workflow, selectedAction, listCache, getParents, setNewSelectedAction]); useEffect(() => { @@ -2575,7 +2609,8 @@ const ParsedAction = (props) => {
*/} - {setNewSelectedAction !== undefined ? ( + {isAgent ? null : + setNewSelectedAction !== undefined ? ( 0)} diff --git a/frontend/src/components/ShuffleCodeEditor1.jsx b/frontend/src/components/ShuffleCodeEditor1.jsx index f355fe60..895c42b8 100644 --- a/frontend/src/components/ShuffleCodeEditor1.jsx +++ b/frontend/src/components/ShuffleCodeEditor1.jsx @@ -276,6 +276,8 @@ const CodeEditor = (props) => { parsedPaths = GetParsedPaths(actionlist[i].value, ""); } } else { + //console.log("EXAMPLE: ", actionlist[i]) + // Handle regular action results if (typeof actionlist[i].example === "object") { parsedPaths = GetParsedPaths(actionlist[i].example, ""); @@ -1228,7 +1230,7 @@ const CodeEditor = (props) => { } const editorLoad = (editor) => { - console.log("EDITOR: ", editor) + //console.log("EDITOR: ", editor) editor.completers = [customCompleter] } @@ -1526,7 +1528,7 @@ const CodeEditor = (props) => { Source Data
- Drag the data you want into the text editor + Drag the data you want into the text editor! PS: Only support users can see this test-section! {actionlist?.map((innerdata) => { @@ -2246,8 +2248,8 @@ const CodeEditor = (props) => { {selectedAction.name === "execute_python" || selectedAction.name === "execute_bash" ? "Code to run" : triggerId ? - `Output : ${triggerName?.replaceAll("_", " ").slice(0, 1).toUpperCase() + triggerName?.replaceAll("_", " ").slice(1)}(${triggerField})` : - `Output : ${appName?.replaceAll("_", " ").slice(0, 1).toUpperCase() + appName?.replaceAll("_", " ").slice(1)}(${fieldName})` + `Output: ${triggerName?.replaceAll("_", " ").slice(0, 1).toUpperCase() + triggerName?.replaceAll("_", " ").slice(1)} (${triggerField})` : + `Output: ${appName?.replaceAll("_", " ").slice(0, 1).toUpperCase() + appName?.replaceAll("_", " ").slice(1)} (${fieldName})` } } @@ -2429,7 +2431,10 @@ const CodeEditor = (props) => { variant="outlined" color="secondary" onClick={() => { - navigate("") + if (isFileEditor !== true) { + navigate("") + } + setExpansionModalOpen(false); }} > @@ -2453,7 +2458,9 @@ const CodeEditor = (props) => { } */ - navigate("") + if (isFileEditor !== true) { + navigate("") + } // Take localcodedata through the Shuffle JSON parser just in case // This is to make it so we don't need to handle these fixes on the // backend by itself diff --git a/frontend/src/components/WorkflowTemplatePopup.jsx b/frontend/src/components/WorkflowTemplatePopup.jsx index 74459d59..3f02f706 100644 --- a/frontend/src/components/WorkflowTemplatePopup.jsx +++ b/frontend/src/components/WorkflowTemplatePopup.jsx @@ -678,7 +678,7 @@ const WorkflowTemplatePopup = (props) => { } {img2 !== undefined && img2 !== "" && dstapp !== undefined && dstapp !== "" ? -
+
diff --git a/frontend/src/components/WorkflowTemplatePopup2.jsx b/frontend/src/components/WorkflowTemplatePopup2.jsx index 3696e026..13810251 100644 --- a/frontend/src/components/WorkflowTemplatePopup2.jsx +++ b/frontend/src/components/WorkflowTemplatePopup2.jsx @@ -50,12 +50,13 @@ const WorkflowTemplatePopup = (props) => { setIsClicked, inputWorkflowId, inputWorkflow, + onClose, } = props; const [isActive, setIsActive] = useState(workflowBuilt === true || (workflowBuilt !== undefined && workflowBuilt !== null && workflowBuilt?.length > 0) || (inputWorkflow !== undefined && inputWorkflow !== null && inputWorkflow.id !== undefined && inputWorkflow.id !== null && inputWorkflow.id !== "") ? true : false) const [isHovered, setIsHovered] = useState(false); - const [modalOpen, setModalOpen] = useState(isModalOpenDefault === true ? true : false) + const [modalOpen, setModalOpen] = useState(isModalOpenDefault === true); const [errorMessage, setErrorMessage] = useState(""); const [workflowLoading, setWorkflowLoading] = useState(false) const [showLoginButton, setShowLoginButton] = useState(false); @@ -173,6 +174,12 @@ const WorkflowTemplatePopup = (props) => { } }, [configurationFinished, workflow]) + useEffect(() => { + if (isModalOpenDefault === true) { + setModalOpen(true); + } + }, [isModalOpenDefault]); + const imageSize = 32 const defaultBorder = "1px solid rgba(255,255,255,0.6)" const imagestyleWrapper = { @@ -516,6 +523,18 @@ const WorkflowTemplatePopup = (props) => { return false } + const handleClose = () => { + setModalOpen(false); + + if (onClose) { + onClose(); + } + + if (setIsClicked !== undefined) { + setIsClicked(false); + } + } + const ModalView = () => { if (modalOpen === false) { return null @@ -528,13 +547,7 @@ const WorkflowTemplatePopup = (props) => { { - setModalOpen(false); - - if (setIsClicked !== undefined) { - setIsClicked(false) - } - }} + onClose={handleClose} PaperProps={{ style: { backgroundColor: "black", @@ -550,17 +563,15 @@ const WorkflowTemplatePopup = (props) => { style={{ zIndex: 5000, position: "absolute", - top: 14, - right: 14, + top: 110, + right: 110, color: "white", }} - onClick={() => { - setModalOpen(false); - }} + onClick={handleClose} > - + Configure Workflow diff --git a/frontend/src/defaultCytoscapeStyle.jsx b/frontend/src/defaultCytoscapeStyle.jsx index 243a6916..d7a6b68e 100644 --- a/frontend/src/defaultCytoscapeStyle.jsx +++ b/frontend/src/defaultCytoscapeStyle.jsx @@ -157,6 +157,38 @@ const data = [ "background-gradient-stop-colors": "data(fillGradient)", }, }, + { + selector: `node[app_id="shuffle_agent"]`, + css: { + "height": "74px", + "width": "222px", + "background-image": "data(large_image)", + "label": function(element) { + var elementname = element.data("label") + if (elementname === null || elementname === undefined) { + return "" + } + + if (elementname.length > 15) { + elementname = elementname.substring(0, 15) + ".." + } + + return elementname + }, + "background-width": "65px", + "background-height": "65px", + "background-position-x": "20px", + //"background-position-x": "center", // Crashes + "background-repeat": "no-repeat", + + "font-size": "14px", + "text-halign": "center", + "text-valign": "center", + "text-margin-x": "-140px", + "text-margin-y": "0px", + + }, + }, { selector: `node[app_name="Testing"]`, css: { diff --git a/frontend/src/theme.jsx b/frontend/src/theme.jsx index 84b2e7fa..ae5fcbf9 100644 --- a/frontend/src/theme.jsx +++ b/frontend/src/theme.jsx @@ -83,6 +83,11 @@ const theme = createTheme(adaptV4Theme({ typography: { fontFamily: `"Roboto", "Helvetica", "Arial", "inter", sans-serif`, useNextVariants: true, + fontWeightLight: 300, + fontWeightRegular: 400, + fontWeightMedium: 500, + fontWeightSemiBold: 600, + fontWeightBold: 700, h1: { fontSize: 40, }, @@ -104,18 +109,45 @@ const theme = createTheme(adaptV4Theme({ }, }, MuiCssBaseline: { - MuiCssBaseline: { - styleOverrides: ` - @font-face { - font-family: 'roboto'; - font-style: normal; - font-display: swap; - font-weight: 300; - src: local('roboto'), local('roboto'), format('truetype'); - unicodeRange: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF; - } - `, - }, + MuiCssBaseline: { + styleOverrides: ` + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-display: swap; + font-weight: 300; + src: local('Roboto Light'), local('Roboto-Light'); + } + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'); + } + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-display: swap; + font-weight: 500; + src: local('Roboto Medium'), local('Roboto-Medium'); + } + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-display: swap; + font-weight: 600; + src: local('Roboto SemiBold'), local('Roboto-SemiBold'); + } + @font-face { + font-family: 'Roboto'; + font-style: normal; + font-display: swap; + font-weight: 700; + src: local('Roboto Bold'), local('Roboto-Bold'); + } + `, + }, }, }, })); diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 83003797..e77185f4 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1243,25 +1243,28 @@ const AngularWorkflow = (defaultprops) => { useEffect(() => { if (selectedTriggerIndex === undefined || selectedTriggerIndex === null || selectedTriggerIndex < 0) { - console.log("Failed in trigger selection: ", selectedTrigger) return } + + //console.log("Failed in trigger selection: ", selectedTriggerIndex, "Trigger: ", selectedTrigger) var found = null - /* try { for (var key in workflows) { const curworkflow = workflows[key] const curtrigger = curworkflow?.triggers[selectedTriggerIndex] if (curtrigger === undefined || curtrigger === null) { + console.log("Failed in trigger selection (1): ", curworkflow) continue } if (curtrigger?.parameters === undefined || curtrigger?.parameters === null || curtrigger?.parameters.length === 0) { + console.log("Failed in trigger selection (2): ", curworkflow) continue } if (curtrigger?.parameters[0] === undefined || curtrigger?.parameters[0] === null || curtrigger?.parameters[0].value === undefined || curtrigger?.parameters[0].value === null) { + console.log("Failed in trigger selection (3): ", curworkflow) continue } @@ -1275,10 +1278,9 @@ const AngularWorkflow = (defaultprops) => { setSubworkflow(found) } } catch (e) { - console.log("Failed in trigger selection: ", e) - return + console.log("Failed in trigger selection (4): ", e) + //return } - */ if (found) { const startNode = found.actions?.find((action) => action.id === workflow?.triggers[selectedTriggerIndex]?.parameters[3]?.value) @@ -2725,6 +2727,11 @@ const AngularWorkflow = (defaultprops) => { } const monitorUpdates = () => { + if (cy === undefined || cy === null) { + console.log("No cy found to verify startnode.") + return true + } + var firstnode = cy.getElementById(workflow.start); if (firstnode.length === 0) { var found = false; @@ -2732,7 +2739,7 @@ const AngularWorkflow = (defaultprops) => { if (workflow.actions[actionkey].isStartNode) { console.log("Updating startnode"); workflow.start = workflow.actions[actionkey].id; - firstnode = cy.getElementById(workflow.actions[actionkey].id); + firstnode = cy.getElementById(workflow.actions[actionkey].id); found = true; break; } @@ -2822,10 +2829,12 @@ const AngularWorkflow = (defaultprops) => { } } - var curelements = cy.elements(); - for (let i = 0; i < curelements.length; i++) { - curelements[i].addClass("not-executing-highlight"); - } + if (cy !== undefined && cy !== null) { + var curelements = cy.elements(); + for (let i = 0; i < curelements.length; i++) { + curelements[i].addClass("not-executing-highlight"); + } + } var headers = { "Content-Type": "application/json", @@ -2836,6 +2845,11 @@ const AngularWorkflow = (defaultprops) => { headers["Org-Id"] = workflow.org_id } + if (workflow?.id === undefined || workflow?.id === null || workflow?.id?.length === 0) { + console.log("No workflow id found during execution") + workflow.id = props.match.params.key + } + const data = { execution_argument: executionArgument, start: startNode }; // fetch(`${globalUrl}/api/v1/workflows/${props.match.params.key}/execute`, fetch(`${globalUrl}/api/v1/workflows/${workflow.id}/execute`, @@ -4116,10 +4130,14 @@ const AngularWorkflow = (defaultprops) => { var execFound = new URLSearchParams(cursearch).get("execution_id"); var sessionToken = new URLSearchParams(cursearch).get("session_token"); if (execFound === null && sessionToken === null) { - toast(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds..`) + + toast.error(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds. If you recently deleted this workflow, speak with support@shuffler.io to recover it from a revision.`, { + autoClose: 10000, + }) + setTimeout(() => { window.location.pathname = "/workflows"; - }, 2000); + }, 2500); } else if (sessionToken !== null && workflow_id === "3abdfb21-b40f-4e50-b855-ac0d62f83cbe") { toast(`Injecting session token and reloading workflow..`) @@ -4396,7 +4414,11 @@ const AngularWorkflow = (defaultprops) => { console.log("Node not found: ", target_id) } - cy.fit(null, 400); + try { + cy.fit(null, 400); + } catch (e) { + console.log("Error in fitting (1): ", e) + } cy.on("add", "node", (e) => onNodeAdded(e)); cy.on("add", "edge", (e) => onEdgeAdded(e)); } else { @@ -4529,12 +4551,12 @@ const AngularWorkflow = (defaultprops) => { setSelectedApp({}); setSelectedComment({}) setSelectedEdge({}) - //setSelectedActionEnvironment({}) setTriggerAuthentication({}) setLocalFirstrequest(true) setSelectedTrigger({}); setSelectedTriggerIndex(-1) + setSubworkflow({}) setUpdate(Math.random()) // Can be used for right side view @@ -4635,7 +4657,12 @@ const AngularWorkflow = (defaultprops) => { .then(() => { console.log("DONE: ", workflow_id); getWorkflow(workflow_id.value, nodedata); - cy.fit(null, 300); + + try { + cy.fit(null, 300); + } catch (e) { + console.log("Error in fitting (2): ", e) + } }); } }; @@ -6856,10 +6883,15 @@ const AngularWorkflow = (defaultprops) => { var found = false; for (let branchkey in workflow.branches) { if (workflow.branches[branchkey].destination_id === edge.source && workflow.branches[branchkey].source_id === edge.target) { - toast("A branch in the opposite direction already exists") - event.target.remove() - found = true - break + + // Find the branch as well + const foundbranch = cy.getElementById(workflow.branches[branchkey].id) + if (foundbranch !== undefined && foundbranch !== null && foundbranch.data() !== undefined && foundbranch.data() !== null) { + toast("A branch in the opposite direction already exists") + event.target.remove() + found = true + break + } } if (workflow.branches[branchkey].destination_id === edge.target && workflow.branches[branchkey].source_id === edge.source) { @@ -7657,6 +7689,10 @@ const AngularWorkflow = (defaultprops) => { //if (nodedata.id === selectedAction.id || nodedata.id === selectedTrigger.id) { // return //} + // + if (nodedata.name === "switch" || nodedata.app_id === "shuffle_agent") { + return + } var parsedStyle = { "border-width": "1px", @@ -7883,8 +7919,14 @@ const AngularWorkflow = (defaultprops) => { var parentNode = cy.$("#" + event.target.data("id")); if (parentNode.data("isButton") || parentNode.data("buttonId")) return; - const px = parentNode.position("x") - 65; - const py = parentNode.position("y") - 5; + var xDiff = 0 + var yDiff = 0 + if (parentNode.data("app_id") === "shuffle_agent") { + xDiff = 70 + } + + const px = parentNode.position("x") - 65 - xDiff + const py = parentNode.position("y") - 5 - yDiff const circleId = (newNodeId = uuidv4()); parentNode.data("circleId", circleId); @@ -8205,8 +8247,14 @@ const AngularWorkflow = (defaultprops) => { var parentNode = cy.$("#" + event.target.data("id")); if (parentNode.data("isButton") || parentNode.data("buttonId")) return; - const px = parentNode.position("x") + 100; - const py = parentNode.position("y") + 35; + var xDiff = 0 + var yDiff = 0 + if (parentNode.data("app_id") === "shuffle_agent") { + xDiff = 70 + } + + const px = parentNode.position("x") + 100 - xDiff; + const py = parentNode.position("y") + 35 - yDiff; const circleId = (newNodeId = uuidv4()); parentNode.data("circleId", circleId); @@ -8240,8 +8288,14 @@ const AngularWorkflow = (defaultprops) => { var parentNode = cy.$("#" + event.target.data("id")); if (parentNode.data("isButton") || parentNode.data("buttonId")) return; - const px = parentNode.position("x") - 65; - const py = parentNode.position("y") + 35; + var xDiff = 0 + var yDiff = 0 + if (parentNode.data("app_id") === "shuffle_agent") { + xDiff = 70 + } + + const px = parentNode.position("x") - 65 - xDiff; + const py = parentNode.position("y") + 35 - yDiff; const circleId = (newNodeId = uuidv4()); parentNode.data("circleId", circleId); @@ -8400,7 +8454,10 @@ const AngularWorkflow = (defaultprops) => { } else { addCopyButton(event); - addStartnodeButton(event); + + if (nodedata.app_id !== "shuffle_agent") { + addStartnodeButton(event); + } } // autocomplete @@ -8416,7 +8473,7 @@ const AngularWorkflow = (defaultprops) => { } } - if (nodedata.name === "switch") { + if (nodedata.name === "switch" || nodedata.app_id === "shuffle_agent") { return } @@ -9105,7 +9162,12 @@ const AngularWorkflow = (defaultprops) => { // Reset view for cytoscape if (cy !== undefined && cy !== null) { cy.add(insertedNodes) - cy.fit(null, 250) + + try { + cy.fit(null, 250) + } catch (error) { + console.log("Error fitting cytoscape (3): ", error) + } } else { setElements(insertedNodes) } @@ -9314,7 +9376,9 @@ const AngularWorkflow = (defaultprops) => { .then((responseJson) => { if (responseJson === null) { //console.log("No revisions found") - return + + //toast.warning("No revisions found") + return } if (responseJson.success === false) { @@ -9502,7 +9566,11 @@ const AngularWorkflow = (defaultprops) => { } // preview: true, - cy.fit(null, 400) + try { + cy.fit(null, 400) + } catch (error) { + console.log("Error fitting cytoscape (4): ", error) + } cy.on("boxselect", "node", (e) => { if (e.target.data("isButton") || e.target.data("isDescriptor") || e.target.data("isSuggestion")) { @@ -12210,7 +12278,7 @@ const AngularWorkflow = (defaultprops) => { // Add Shuffle DB with cache keys if available let cacheKey = { type: "Shuffle DB", - name: "Shuffle DB", + name: "Shuffle Datastore", value: "$shuffle_cache", highlight: "shuffle_cache", autocomplete: "shuffle_cache", @@ -17643,7 +17711,7 @@ const AngularWorkflow = (defaultprops) => { id="suborg-changer" style={{ color: "rgba(255,255,255,0.7)", }} > - Select an Org + Select an Org ({originalWorkflow?.suborg_distribution?.length})