From ceca675140757bafaac873e6e1ec40617d84ecd7 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Fri, 7 Jun 2024 06:55:21 +0000 Subject: [PATCH 01/46] fix: Table rendering --- frontend/src/index.css | 6 ++---- frontend/src/views/Docs.jsx | 12 ++++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index 9b1fc96c..3ab58ef1 100755 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -18,11 +18,9 @@ code { margin-right: -13px; } -.toc:hover { - background-color: gray; - color: white; +table th, table td { + border: 1px solid; } - /* .cm-string{ z-index: -1; } diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx index 565f051f..b040a11f 100755 --- a/frontend/src/views/Docs.jsx +++ b/frontend/src/views/Docs.jsx @@ -1,15 +1,13 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from "react" - import { toast } from 'react-toastify'; import Markdown from 'react-markdown' - import theme from '../theme.jsx'; import ReactJson from "react-json-view"; import { isMobile } from "react-device-detect"; import { BrowserView, MobileView } from "react-device-detect"; import { useParams, useNavigate, Link } from "react-router-dom"; import { validateJson, GetIconInfo } from "../views/Workflows.jsx"; - +import remarkGfm from 'remark-gfm' import { Grid, TextField, @@ -26,7 +24,6 @@ import { ListItemButton, ListItemText } from "@mui/material"; - import { Link as LinkIcon, Edit as EditIcon, @@ -36,6 +33,7 @@ import { } from "@mui/icons-material"; import { fontGrid } from "@mui/material/styles/cssUtils.js"; import { active } from "d3"; +import style from "./../index.css"; const Body = { //maxWidth: 1000, @@ -158,6 +156,7 @@ export const Img = (props) => { return {props.alt}; } + export const CodeHandler = (props) => { const propvalue = props.value !== undefined && props.value !== null ? props.value : props.children !== undefined && props.children !== null && props.children.length > 0 ? props.children[0] : "" @@ -369,8 +368,7 @@ const Docs = (defaultprops) => { hash = hash.split('?')[0] } if (hash) { - console.log("HASH: ", hash) - const element = document.getElementById(hash) + const element = document.getElementById(hash.toLowerCase()) if (element) { element.scrollIntoView({ behavior: "instant", @@ -1022,8 +1020,10 @@ const Docs = (defaultprops) => { Date: Thu, 13 Jun 2024 18:49:18 +0200 Subject: [PATCH 02/46] Bunch of fixes for MSSP feature buildout --- frontend/src/components/BillingStats.jsx | 3 + frontend/src/components/ConfigureWorkflow.jsx | 5 - frontend/src/components/EditWorkflow.jsx | 185 +- frontend/src/components/NewHeader.jsx | 23 +- frontend/src/components/OrgHeaderexpanded.jsx | 1856 +++++++++-------- frontend/src/components/ParsedAction.jsx | 106 +- .../src/components/ShuffleCodeEditor1.jsx | 4 +- frontend/src/defaultCytoscapeStyle.jsx | 5 + frontend/src/views/Admin.jsx | 459 ++-- frontend/src/views/AngularWorkflow.jsx | 1102 ++++++++-- frontend/src/views/Docs.jsx | 1 + frontend/src/views/Workflows.jsx | 224 +- functions/onprem/worker/worker.go | 158 +- 13 files changed, 2759 insertions(+), 1372 deletions(-) diff --git a/frontend/src/components/BillingStats.jsx b/frontend/src/components/BillingStats.jsx index ac18d39b..519c7470 100644 --- a/frontend/src/components/BillingStats.jsx +++ b/frontend/src/components/BillingStats.jsx @@ -134,6 +134,9 @@ const AppStats = (defaultprops) => { Accept: "application/json", }, credentials: "include", + }).catch((error) => { + console.log("Error getting workflow stats: " + error); + return workflow }) if (response.status !== 200) { diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index 6a7b55b7..6c8ae0ee 100755 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -428,7 +428,6 @@ const ConfigureWorkflow = (props) => { console.log("Found webhook: ", trigger) if (trigger.app_association !== undefined && trigger.app_association.name !== null && trigger.app_association.name !== "") { - console.log("Actions: ", newactions) const findapp = trigger.app_association.name.toLowerCase() const foundindex = newactions.findIndex(action => action.app_name.toLowerCase() === findapp) @@ -449,9 +448,6 @@ const ConfigureWorkflow = (props) => { newactions[foundindex].show_steps = true - console.log("CHANGED ACTION: ", newactions[foundindex]) - //console.log("Index: ", newactions[foundindex]) - continue } } @@ -1321,7 +1317,6 @@ const ConfigureWorkflow = (props) => { } if (step.type === "authenticate") { - console.log("AUTH STEP: ", step) if (data.must_authenticate === true ) { filled = false } else { diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index 9ac7fabe..4b4aa3d5 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -78,7 +78,6 @@ const EditWorkflow = (props) => { const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "") const [dueDate, setDueDate] = React.useState(workflow.due_date !== undefined && workflow.due_date !== null && workflow.due_date !== 0 ? dayjs(workflow.due_date*1000) : dayjs().subtract(1, 'day')) - console.log("WORKFLOW: ", workflow) const [inputQuestions, setInputQuestions] = React.useState(workflow.input_questions !== undefined && workflow.input_questions !== null ? JSON.parse(JSON.stringify(workflow.input_questions)) : []) const classes = useStyles(); @@ -235,7 +234,7 @@ const EditWorkflow = (props) => { -
+
{/*
@@ -565,14 +564,22 @@ const EditWorkflow = (props) => { fullWidth /> - - MSSP Suborg Distribution (beta - contact support@shuffler.io) + + MSSP Suborg Distribution (beta - contact support@shuffler.io for more info) {userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ? userdata.orgs.filter(org => org.creator_org === userdata.active_org.id).length === 0 ? - - You can only distribute to suborgs from a parent org. - + userdata.active_org.creator_org === undefined || userdata.active_org.creator_org === null || userdata.active_org.creator_org === "" ? + + Your organization does not have any suborgs yet. Please make one, then try again. + + : + + {innerWorkflow.parentorg_workflow !== undefined && innerWorkflow.parentorg_workflow !== null && innerWorkflow.parentorg_workflow.length > 0 ? This workflow is distributed from your parent workflow (you may not have access). : null} +
+
+ You can only distribute to suborgs from a parent org. +
: {/* diff --git a/frontend/src/components/ShuffleCodeEditor1.jsx b/frontend/src/components/ShuffleCodeEditor1.jsx index a23de3e5..a6058704 100644 --- a/frontend/src/components/ShuffleCodeEditor1.jsx +++ b/frontend/src/components/ShuffleCodeEditor1.jsx @@ -627,8 +627,8 @@ const CodeEditor = (props) => { newMarkers.push({ startRow: i, startCol: startCh, - endRow: i+1, - endCol: endCh+1, + endRow: i, + endCol: endCh, className: correctVariable ? "good-marker" : "bad-marker", type: "text", }) diff --git a/frontend/src/defaultCytoscapeStyle.jsx b/frontend/src/defaultCytoscapeStyle.jsx index d36c8e8d..6f2455b3 100644 --- a/frontend/src/defaultCytoscapeStyle.jsx +++ b/frontend/src/defaultCytoscapeStyle.jsx @@ -133,6 +133,11 @@ const data = [ "background-gradient-stop-colors": "data(fillGradient)", }, }, + { + selector: `node[?parent_controlled]`, + css: { + }, + }, { selector: `node[app_name="Testing"]`, css: { diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index b074e404..f0f2dfd5 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -166,7 +166,9 @@ const Admin = (props) => { //console.log("Selected: ", selectedOrganization) const [appAuthenticationGroupModalOpen , setAppAuthenticationGroupModalOpen] = React.useState(false); const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]); + const [appAuthenticationGroupId, setAppAuthenticationGroupId] = React.useState(""); const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState(""); + const [appAuthenticationGroupEnvironment, setAppAuthenticationGroupEnvironment] = React.useState(""); const [appAuthenticationGroupDescription, setAppAuthenticationGroupDescription] = React.useState(""); const [appAuthenticationGroups, setAppAuthenticationGroups] = React.useState([]); const [organizationFeatures, setOrganizationFeatures] = React.useState({}); @@ -431,23 +433,62 @@ const Admin = (props) => { }); }; - const createAppAuthenticationGroup = (name, description, appAuthIds) => { + const deleteAppAuthenticationGroup = (appAuthGroupId) => { + const url = `${globalUrl}/api/v1/authentication/group/${appAuthGroupId}` + fetch(url, { + method: "DELETE", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for deleting app auth group"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + toast("Failed to delete app authentication group"); + } else { + toast("App authentication group deleted") + getAppAuthenticationGroups() + } + }) + .catch((error) => { + toast(error.toString()) + }) + } + + const createAppAuthenticationGroup = (name, environment, description, appAuthIds) => { + // Makes list of ids into a full-on list of auth, but just with the ID + // The backend fills in the rest + console.log("INput auth: ", appAuthIds) let app_auths = appAuthIds.map((appAuthId) => { return { id: appAuthId }; }) - fetch(globalUrl + "/api/v1/apps/authentication/group", { + var parsedAppGroup = { + label: name, + environment: environment, + description: description, + app_auths: app_auths + } + + if (appAuthenticationGroupId !== undefined && appAuthenticationGroupId !== null && appAuthenticationGroupId !== "") { + parsedAppGroup.id = appAuthenticationGroupId + } + + fetch(globalUrl + "/api/v1/authentication/group", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, credentials: "include", - body: JSON.stringify({ - label: name, - description: description, - app_auths: app_auths - }), + body: JSON.stringify(parsedAppGroup), }) .then((response) => { if (response.status !== 200) { @@ -457,8 +498,15 @@ const Admin = (props) => { return response.json(); }) .then((responseJson) => { - // getAppAuthenticationGroups(); - toast("App authentication group created"); + if (responseJson.success === false) { + toast("Failed to create. Please try again, or contact support@shuffler.io") + } else { + // Close the modal + setAppAuthenticationGroupModalOpen(false) + + toast("App authentication group created") + getAppAuthenticationGroups() + } }) .catch((error) => { toast(error.toString()); @@ -805,7 +853,7 @@ If you're interested, please let me know a time that works for you, or set up a .then((responseJson) => { setWebHooks(responseJson.webhooks || []); // Handling the case where the result is null or undefined setAllSchedules(responseJson.schedules || []); - setPipelines(responseJson.pipelines || []); + // setPipelines(responseJson.pipelines || []); }) .catch((error) => { // toast(error.toString()); @@ -990,13 +1038,11 @@ If you're interested, please let me know a time that works for you, or set up a } const data = { - command: pipeline.command, name: pipeline.name, type: state, environment: pipeline.environment, workflow_id: pipeline.workflow_id, trigger_id: pipeline.trigger_id, - start_node: pipeline.start_node, }; if (state === "start") toast("starting the pipeline"); @@ -1027,7 +1073,6 @@ If you're interested, please let me know a time that works for you, or set up a if (state === "start") toast("Successfully created pipeline"); else toast("Sucessfully stopped the pipeline"); } - setTimeout(handleGetAllTriggers, 1000); }) .catch((error) => { //toast(error.toString()); @@ -2093,10 +2138,10 @@ If you're interested, please let me know a time that works for you, or set up a }; const getAppAuthenticationGroups = () => { - console.log("DEBUG: Skipping app auth group loading") - return + //console.log("DEBUG: Skipping app auth group loading") + //return - fetch(globalUrl + "/api/v1/apps/authentication/group", { + fetch(globalUrl + "/api/v1/authentication/group", { method: "GET", headers: { "Content-Type": "application/json", @@ -4941,7 +4986,7 @@ If you're interested, please let me know a time that works for you, or set up a
+ + + + +
+ {/* Show a check box list of all app authentications to add to the auth group */} +
+ {authentication.map((data, index) => { + var checked = data.checked + if (checked === undefined || checked === null) { + checked = false + } + + if (appsForAppAuthGroup.includes(data.id)) { + checked = true + } + + return ( +
+ +
+ + { + handleAppAuthGroupCheckbox(data) + }} + name={data.label} + disabled={data.app.id in appsForAppAuthGroup} + /> +
+ + } + label={data.label} + /> +
+ ) + })} +
+
+ + )} @@ -5311,7 +5412,7 @@ If you're interested, please let me know a time that works for you, or set up a

App Authentication

- Control the authentication options for individual apps. + Control the authentication options for individual apps. App Groups are farther down on this page.  
- {/*
+ + +
-

App Authentication Groups

- +

App Authentication Groups

+ Groups of authentication options for subflows.{" "}
- + + + + - - {data.app_auths.map((appAuth, index) => ( - - {appAuth.app.name} - - ))} + {data.app_auths.map((appAuth, index) => { + if (appAuth.app.large_image === undefined || appAuth.app.large_image === null || appAuth.app.large_image === "") { + const foundImage = authentication.find((auth) => auth.app.id === appAuth.app.id) + if (foundImage !== undefined) { + appAuth.app.large_image = foundImage.app.large_image + + appAuth.app.name = foundImage.app.name + } + } + + const tooltip = `${appAuth.app.name.replaceAll("_", " ")} (authname: ${appAuth.label})` + + return ( + + {appAuth.app.name} + + ) + })}
} style={{ minWidth: 250, maxWidth: 250 }} @@ -5676,16 +5813,22 @@ If you're interested, please let me know a time that works for you, or set up a
{ + setAppAuthenticationGroupId(data.id) + + setAppAuthenticationGroupName(data.label) + setAppAuthenticationGroupDescription(data.description) + + setAppsForAppAuthGroup(data.app_auths.map((appAuth) => appAuth.id)) + setAppAuthenticationGroupEnvironment(data.environment) + setAppAuthenticationGroupModalOpen(true) }} - disabled={true} > { - // deleteAppAuthenticationGroup(data); + deleteAppAuthenticationGroup(data.id) }} - disabled={true} > @@ -5700,20 +5843,10 @@ If you're interested, please let me know a time that works for you, or set up a )} -
-
*/} - +
+ ) : null; const getLogs = async (ip, userId) => { diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 492e8ad9..f10ffae8 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -451,6 +451,9 @@ const AngularWorkflow = (defaultprops) => { const [lastExecution, setLastExecution] = React.useState(""); const [configureWorkflowModalOpen, setConfigureWorkflowModalOpen] = React.useState(false); + const [authgroupModalOpen, setAuthgroupModalOpen] = React.useState(false); + const [authGroups, setAuthGroups] = React.useState([]) + const curpath = typeof window === "undefined" || window.location === undefined ? "" : window.location.pathname; @@ -524,12 +527,13 @@ const AngularWorkflow = (defaultprops) => { const [workflowRecommendations, setWorkflowRecommendations] = React.useState(undefined); const [showErrors, setShowErrors] = React.useState(true); const [highlightedApp, setHighlightedApp] = React.useState("") - const [listCache, setListCache] = React.useState([]); - const [selectedOption, setSelectedOption] = React.useState(""); const [tenzirConfigModalOpen, setTenzirConfigModalOpen] = React.useState(false); + const [distributedFromParent, setDistributedFromParent] = React.useState("") + const [suborgWorkflows, setSuborgWorkflows] = React.useState([]) + const [suggestionBox, setSuggestionBox] = React.useState({ "position": { "top": 500, @@ -539,6 +543,12 @@ const AngularWorkflow = (defaultprops) => { "attachedTo": "", }) + useEffect(() => { + if (!firstrequest && isLoaded && isLoggedIn && editWorkflowModalOpen === false) { + saveWorkflow(workflow) + } + }, [editWorkflowModalOpen]) + // New for generated stuff const releaseToConnectLabel = "Release to Connect" const integrationApps = [{ @@ -828,6 +838,75 @@ const AngularWorkflow = (defaultprops) => { loopRunning2 = true } + useEffect(() => { + if (workflow.id !== undefined && workflow.id !== null && workflow.id.length > 0 && workflow.id === originalWorkflow.id) { + setOriginalWorkflow(workflow) + } + + // Special multi-workflow edgecase handler for events + if (distributedFromParent === "" && suborgWorkflows === []) { + } else { + if (cy !== undefined) { + cy.removeListener("select"); + cy.removeListener("unselect"); + cy.removeListener("add"); + cy.removeListener("remove"); + cy.removeListener("mouseover"); + cy.removeListener("mouseout"); + cy.removeListener("drag"); + cy.removeListener("free"); + cy.removeListener("cxttap"); + + setTimeout(() => { + setupGraph(workflow) + + cy.on("select", "node", (e) => { + onNodeSelect(e, appAuthentication); + }); + cy.on("select", "edge", (e) => onEdgeSelect(e)); + + cy.on("unselect", (e) => onUnselect(e)); + + cy.on("add", "node", (e) => onNodeAdded(e)); + cy.on("add", "edge", (e) => onEdgeAdded(e)); + cy.on("remove", "node", (e) => onNodeRemoved(e)); + cy.on("remove", "edge", (e) => onEdgeRemoved(e)); + + cy.on("mouseover", "edge", (e) => onEdgeHover(e)); + cy.on("mouseout", "edge", (e) => onEdgeHoverOut(e)); + cy.on("mouseover", "node", (e) => onNodeHover(e)); + cy.on("mouseout", "node", (e) => onNodeHoverOut(e)); + + // Handles dragging + cy.on("drag", "node", (e) => onNodeDrag(e, selectedAction)); + cy.on("free", "node", (e) => onNodeDragStop(e, selectedAction)); + + cy.on("cxttap", "node", (e) => onCtxTap(e)); + + cy.edgehandles({ + handleNodes: (el) => { + if (el.isNode() && + el.data("buttonType") != "ACTIONSUGGESTION" && + !el.data("isButton") && + !el.data("isDescriptor") && + !el.data("isSuggestion") && + el.data("type") !== "COMMENT") { + return true + } + + return false + }, + preview: false, + toggleOffOnLeave: true, + loopAllowed: function (node) { + return false; + }, + }) + }, 50) + } + } + }, [workflow]) + useEffect(() => { // Current variable + future state controlled // This is so that the loop can stop itself as well @@ -958,7 +1037,7 @@ const AngularWorkflow = (defaultprops) => { }; const getAvailableWorkflows = (trigger_index) => { - fetch(globalUrl + "/api/v1/workflows", { + fetch(globalUrl + "/api/v1/workflows?subflow=true", { method: "GET", headers: { "Content-Type": "application/json", @@ -1307,7 +1386,7 @@ const AngularWorkflow = (defaultprops) => { if (response.status !== 200) { stop(); setExecutionModalView(0); - toast("Failed loading the workflow run") + //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; @@ -1675,12 +1754,18 @@ const AngularWorkflow = (defaultprops) => { }) } - const saveWorkflow = (curworkflow, executionArgument, startNode) => { + const saveWorkflow = (curworkflow, executionArgument, startNode, duplicationOrg) => { var success = false; if (isCloud && !isLoggedIn) { console.log("Should redirect to register with redirect.") - window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle` + + setTimeout(() => { + toast("You may not have access to this workflow.") + //window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle` + window.location.href = `/workflows` + }, 2500) + return } @@ -1910,13 +1995,19 @@ const AngularWorkflow = (defaultprops) => { useworkflow.id = props.match.params.key } + var headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } + + if (useworkflow.org_id !== undefined && useworkflow.org_id !== null && useworkflow.org_id.length > 0) { + headers["Org-Id"] = useworkflow.org_id + } + setLastSaved(true); fetch(`${globalUrl}/api/v1/workflows/${useworkflow.id}`, { method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + headers: headers, body: JSON.stringify(useworkflow), credentials: "include", }) @@ -1924,11 +2015,20 @@ const AngularWorkflow = (defaultprops) => { setSavingState(0); if (response.status !== 200) { console.log("Status not 200 for setting workflows :O!"); - } + } else { + if (distributedFromParent === "" && suborgWorkflows === []) { + } else { + getChildWorkflows(useworkflow.id) + } + } return response.json(); }) .then((responseJson) => { + if (duplicationOrg !== undefined && duplicationOrg !== null && duplicationOrg.length > 0) { + duplicateParentWorkflow(useworkflow, duplicationOrg, true) + } + if (executionArgument !== undefined && startNode !== undefined) { //console.log("Running execution AFTER saving"); executeWorkflow(executionArgument, startNode, true); @@ -1993,8 +2093,11 @@ const AngularWorkflow = (defaultprops) => { console.log("Save workflow error: ", error.toString()); }); - setOriginalWorkflow(useworkflow) - return success; + if (originalWorkflow.id === undefined || originalWorkflow.id === null || originalWorkflow.id.length === 0 || useworkflow.id === originalWorkflow.id) { + setOriginalWorkflow(useworkflow) + } + + return success }; const monitorUpdates = () => { @@ -2087,14 +2190,20 @@ const AngularWorkflow = (defaultprops) => { curelements[i].addClass("not-executing-highlight"); } + var headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } + + if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) { + headers["Org-Id"] = workflow.org_id + } + const data = { execution_argument: executionArgument, start: startNode }; fetch(`${globalUrl}/api/v1/workflows/${props.match.params.key}/execute`, { method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, + headers: headers, credentials: "include", body: JSON.stringify(data), } @@ -2167,6 +2276,37 @@ const AngularWorkflow = (defaultprops) => { // This can be used to only show prioritzed ones later // Right now, it can prioritize authenticated ones //"Testing", + // + // + + const getAuthGroups = () => { + fetch(globalUrl + "/api/v1/authentication/groups", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for app auth :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + setAuthGroups(responseJson.data) + } else { + console.log("AppAuth group loading error: " + responseJson.reason); + } + }) + .catch((error) => { + setAuthGroups([]); + console.log("AppAuth group loading error: " + error.toString()); + }) + } const getAppAuthentication = (reset, updateAction, closeMenu) => { fetch(globalUrl + "/api/v1/apps/authentication", { @@ -2177,127 +2317,130 @@ const AngularWorkflow = (defaultprops) => { }, credentials: "include", }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for app auth :O!"); + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for app auth :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + var shouldClose = false + if (responseJson.success) { + getAuthGroups() + + var newauth = []; + for (let authkey in responseJson.data) { + if (responseJson.data[authkey].defined === false) { + continue; + } + + newauth.push(responseJson.data[authkey]); + } + + setAppAuthentication(newauth); + + if (cy !== undefined) { + // Remove the old listener for select, run with new one + cy.removeListener("select"); + + cy.on("select", "node", (e) => onNodeSelect(e, newauth)); + cy.on("select", "edge", (e) => onEdgeSelect(e)); + } + + if (updateAction === true) { + if (selectedApp.authentication.required) { + // Setup auth here :) + var appUpdates = false; + const authenticationOptions = []; + + var tmpAuth = JSON.parse(JSON.stringify(newauth)); + var latest = 0; + for (let authkey in tmpAuth) { + var item = tmpAuth[authkey]; + + //console.log("Got auth: ", item); + + const newfields = {}; + for (let filterkey in item.fields) { + newfields[item.fields[filterkey].key] = item.fields[filterkey].value; } - return response.json(); - }) - .then((responseJson) => { - var shouldClose = false - if (responseJson.success) { - var newauth = []; - for (let authkey in responseJson.data) { - if (responseJson.data[authkey].defined === false) { - continue; - } + item.fields = newfields; - newauth.push(responseJson.data[authkey]); - } + const appname = selectedApp.name.toLowerCase().replaceAll(" ", "_", -1) + const itemname = item.app.name.toLowerCase().replaceAll(" ", "_", -1) + if (itemname === appname) { + authenticationOptions.push(item); - setAppAuthentication(newauth); + // Always becoming the last one + if (item.edited > latest) { + latest = item.edited; + selectedAction.selectedAuthentication = item; - if (cy !== undefined) { - // Remove the old listener for select, run with new one - cy.removeListener("select"); - - cy.on("select", "node", (e) => onNodeSelect(e, newauth)); - cy.on("select", "edge", (e) => onEdgeSelect(e)); - } - - if (updateAction === true) { - if (selectedApp.authentication.required) { - // Setup auth here :) - var appUpdates = false; - const authenticationOptions = []; - - var tmpAuth = JSON.parse(JSON.stringify(newauth)); - var latest = 0; - for (let authkey in tmpAuth) { - var item = tmpAuth[authkey]; - - //console.log("Got auth: ", item); - - const newfields = {}; - for (let filterkey in item.fields) { - newfields[item.fields[filterkey].key] = item.fields[filterkey].value; - } - - item.fields = newfields; - - const appname = selectedApp.name.toLowerCase().replaceAll(" ", "_", -1) - const itemname = item.app.name.toLowerCase().replaceAll(" ", "_", -1) - if (itemname === appname) { - authenticationOptions.push(item); - - // Always becoming the last one - if (item.edited > latest) { - latest = item.edited; - selectedAction.selectedAuthentication = item; - - for (let actionkey in workflow.actions) { - const actionAppname = workflow.actions[actionkey].app_name.toLowerCase().replaceAll(" ", "_", -1) - if (actionAppname === appname) { - workflow.actions[actionkey].selectedAuthentication = item; - workflow.actions[actionkey].authentication_id = item.id; - appUpdates = true; - } - } - } else { - //console.log("Not newer: ", item.edited, " vs ", latest) - } - } else { - //console.log("Appname is wrong: ", appname, " vs ", itemname) - } - } - - selectedAction.authentication = authenticationOptions; - if ( - selectedAction.selectedAuthentication === null || - selectedAction.selectedAuthentication === undefined || - selectedAction.selectedAuthentication.length === "" - ) { - selectedAction.selectedAuthentication = {}; - } - - if (appUpdates === true) { - console.log("Closing auth modal: Success") - - setAuthenticationModalOpen(false); - setSelectedAction(selectedAction); - setWorkflow(workflow); - saveWorkflow(workflow); - - toast("Added and updated authentication!"); - shouldClose = true - } else { - console.log("Closing auth modal? FAIL") - - toast("Failed to find new authentication. See details in Oauth2 popup window where auth was attempted."); - shouldClose = false - } - } else { - toast("No authentication to update"); - } - } else { - shouldClose = true - } - } else { - setAppAuthentication([]); - shouldClose = true - } - - // Auto-closing if changes were made - if (closeMenu === true && shouldClose === true) { - setAuthenticationModalOpen(false); + for (let actionkey in workflow.actions) { + const actionAppname = workflow.actions[actionkey].app_name.toLowerCase().replaceAll(" ", "_", -1) + if (actionAppname === appname) { + workflow.actions[actionkey].selectedAuthentication = item; + workflow.actions[actionkey].authentication_id = item.id; + appUpdates = true; + } } - }) - .catch((error) => { - setAppAuthentication([]); - //toast("Auth loading error: " + error.toString()); - console.log("AppAuth error: " + error.toString()); - }); + } else { + //console.log("Not newer: ", item.edited, " vs ", latest) + } + } else { + //console.log("Appname is wrong: ", appname, " vs ", itemname) + } + } + + selectedAction.authentication = authenticationOptions; + if ( + selectedAction.selectedAuthentication === null || + selectedAction.selectedAuthentication === undefined || + selectedAction.selectedAuthentication.length === "" + ) { + selectedAction.selectedAuthentication = {}; + } + + if (appUpdates === true) { + console.log("Closing auth modal: Success") + + setAuthenticationModalOpen(false); + setSelectedAction(selectedAction); + setWorkflow(workflow); + saveWorkflow(workflow); + + toast("Added and updated authentication!"); + shouldClose = true + } else { + console.log("Closing auth modal? FAIL") + + toast("Failed to find new authentication. See details in Oauth2 popup window where auth was attempted."); + shouldClose = false + } + } else { + toast("No authentication to update"); + } + } else { + shouldClose = true + } + + } else { + setAppAuthentication([]); + shouldClose = true + } + + // Auto-closing if changes were made + if (closeMenu === true && shouldClose === true) { + setAuthenticationModalOpen(false); + } + }) + .catch((error) => { + setAppAuthentication([]); + //toast("Auth loading error: " + error.toString()); + console.log("AppAuth error: " + error.toString()); + }); }; const getApps = () => { @@ -3082,6 +3225,36 @@ const AngularWorkflow = (defaultprops) => { return apps }; + const getChildWorkflows = (parentWorkflowId) => { + if (workflow.suborg_distribution === undefined || workflow.suborg_distribution === null || workflow.suborg_distribution.length === 0) { + return + } + + fetch(`${globalUrl}/api/v1/workflows/${parentWorkflowId}/child_workflows`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success !== false) { + setSuborgWorkflows(responseJson) + } + }) + .catch((error) => { + console.log("Get child workflows error: ", error); + }) + } + const getWorkflow = (workflow_id, sourcenode) => { fetch(`${globalUrl}/api/v1/workflows/${workflow_id}`, { method: "GET", @@ -3125,9 +3298,24 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { // Load as JSON + // + // //console.log("Got workflow TXT: ", responseText) //const responseJson = JSON.parse(responseText) //console.log("Got workflow JSON: ", responseJson) + if (responseJson.id !== undefined && responseJson.id !== null && responseJson.id.length > 0 && responseJson.id !== workflow_id) { + toast("Workflow ID mismatch. Redirecting to your workflow") + navigate(`/workflows/${responseJson.id}`) + } + + if (responseJson.parentorg_workflow !== undefined && responseJson.parentorg_workflow !== null && responseJson.parentorg_workflow !== "") { + setDistributedFromParent(responseJson.parentorg_workflow) + } + + + if (responseJson.childorg_workflow_ids !== undefined && responseJson.childorg_workflow_ids !== null && responseJson.childorg_workflow_ids.length > 0) { + getChildWorkflows(responseJson.id) + } // Not sure why this is necessary. if (responseJson.isValid === undefined) { @@ -3350,7 +3538,7 @@ const AngularWorkflow = (defaultprops) => { cy.on("add", "node", (e) => onNodeAdded(e)); cy.on("add", "edge", (e) => onEdgeAdded(e)); } else { - setOriginalWorkflow(responseJson); + setOriginalWorkflow(responseJson) setWorkflow(responseJson); setWorkflowDone(true); @@ -3704,14 +3892,11 @@ const AngularWorkflow = (defaultprops) => { } if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") { - console.log("Found triggers. Add!") - if (!found) { console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions) // Find how many executions it has var executions = 0 const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase())) - console.log("Matches: ", matchingExecutions.length) const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436" const decoratorNode = { position: { @@ -4220,7 +4405,7 @@ const AngularWorkflow = (defaultprops) => { return } - // Inject HTML at a fixed location + // Inject HTML at a fixed location? //const newHtml = "

Do you want to add this suggestion?

" // Find mouse cursor position on screen @@ -4415,13 +4600,24 @@ const AngularWorkflow = (defaultprops) => { return; } else if (data.isDescriptor) { - console.log("Can't select descriptor"); + // Find parent + event.target.unselect(); + + if (data.attachedTo !== undefined && data.attachedTo !== null && data.attachedTo.length > 0) { + const parentNode = cy.getElementById(data.attachedTo) + if (parentNode !== null && parentNode !== undefined) { + setTimeout(() => { + parentNode.select() + }, 100) + } + } + + //console.log("Can't select descriptor"); if (data.isTrigger) { console.log("But maybe we can select trigger descriptor? Maybe open execution tab?") setExecutionModalOpen(true) } - event.target.unselect(); return; } @@ -5701,9 +5897,6 @@ const AngularWorkflow = (defaultprops) => { } setWorkflow(workflow); - //if (data.type === "TRIGGER") { - // saveWorkflow(workflow); - //} } //var previouskey = 0 @@ -5983,7 +6176,15 @@ const AngularWorkflow = (defaultprops) => { workflow.id !== null && workflow.id.length > 0 ) { - window.location.pathname = "/workflows/" + props.match.params.key; + + // Check if + if (distributedFromParent === "" && suborgWorkflows === []) { + toast.info("Redirecting as the workflow ID does not match the URL") + + setTimeout(() => { + window.location.pathname = "/workflows/" + props.match.params.key; + }, 2500) + } } const animationDuration = 150; @@ -6171,7 +6372,6 @@ const AngularWorkflow = (defaultprops) => { }; const addActionSuggestions = (nodedata, event) => { - console.log("App Action suggestions being added") if (nodedata.type !== "ACTION") { return } @@ -6187,10 +6387,37 @@ const AngularWorkflow = (defaultprops) => { const parentlabel = parentNode.data("label").toLowerCase().replace(" ", "_") const parentname = parentNode.data("app_name").toLowerCase().replace(" ", "_") if (!parentlabel.startsWith(parentname)) { - console.log("Bad startname to start with: ", parentname, parentlabel) return } + // Check if action has changed + const parentAppId = parentNode.data("app_id") + const parentActionname = parentNode.data("name") + for (var appkey in apps) { + const curapp = apps[appkey] + + if (curapp.id !== parentAppId) { + continue + } + + if (curapp.actions === undefined || curapp.actions === null || curapp.actions.length === 0) { + continue + } + + var startIndex = curapp.actions.findIndex((action) => action.category_label !== undefined && action.category_label !== null && action.category_label.length > 0) + if (startIndex === -1) { + startIndex = 0 + } + + if (curapp.actions[startIndex].name !== parentActionname) { + return + } + + break + } + + //const parentAction = parentNode.data("name") + const iconInfo = { icon: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z", iconColor: buttonColor, @@ -6204,8 +6431,7 @@ const AngularWorkflow = (defaultprops) => { // 2. Loop the apps' actions // 3. Find actions based on category label IF it exists - console.log("Fidning app match for: ", parentname) - var added = 0 + var addedLabels = [] for (let appKey in apps) { const curapp = apps[appKey] if (curapp.name.toLowerCase().replace(" ", "_") !== parentname) { @@ -6216,7 +6442,6 @@ const AngularWorkflow = (defaultprops) => { continue } - console.log("Found matching: ", curapp.name, parentname, curapp.actions.length) for (let actionKey in curapp.actions) { const curaction = curapp.actions[actionKey] @@ -6226,7 +6451,13 @@ const AngularWorkflow = (defaultprops) => { } if (curaction.category_label !== undefined && curaction.category_label !== null && curaction.category_label.length > 0) { - console.log("IN NODE ADD") + if (addedLabels.includes(curaction.category_label[0])) { + continue + } + + if (curaction.category_label[0].replaceAll("_", " ").toLowerCase() === "no label") { + continue + } cy.add({ group: "nodes", @@ -6240,12 +6471,13 @@ const AngularWorkflow = (defaultprops) => { }, position: { x: px, - y: py + (added * 50), + y: py + (addedLabels.length * 50), }, + locked: true, }) - added += 1 - if (added >= 3) { + addedLabels.push(curaction.category_label[0]) + if (addedLabels.length >= 2) { break } } @@ -6555,7 +6787,6 @@ const AngularWorkflow = (defaultprops) => { } if (!found) { - console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions) // Find how many executions it has var executions = 0 const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase())) @@ -7087,6 +7318,11 @@ const AngularWorkflow = (defaultprops) => { } */ + var parentcontrolled = false + if (branch.parent_controlled !== undefined && branch.parent_controlled !== null && branch.parent_controlled === true) { + parentcontrolled = true + } + edge.data = { id: branch.id, _id: branch.id, @@ -7096,6 +7332,7 @@ const AngularWorkflow = (defaultprops) => { conditions: conditions, hasErrors: branch.has_errors, decorator: false, + parent_controlled: parentcontrolled, }; // This is an attempt at prettier edges. The numbers are weird to work with. @@ -7125,7 +7362,6 @@ const AngularWorkflow = (defaultprops) => { return edge; }); - console.log("VISUAL BRANCHES: ", inputworkflow.visual_branches) if (inputworkflow.visual_branches !== undefined && inputworkflow.visual_branches !== null && inputworkflow.visual_branches.length > 0) { const visualedges = inputworkflow.visual_branches.map((branch, index) => { const edge = {}; @@ -7187,8 +7423,6 @@ const AngularWorkflow = (defaultprops) => { } else { setElements(insertedNodes); } - - console.log("Setupgraph done 2!") } const removeNode = (nodeId) => { @@ -7350,7 +7584,6 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (responseJson.success !== false) { - console.log("Usecases: ", usecases) setUsecases(responseJson) } else { } @@ -7402,6 +7635,7 @@ const AngularWorkflow = (defaultprops) => { if (firstrequest) { setFirstrequest(false); getWorkflow(props.match.params.key, {}); + getChildWorkflows(props.match.params.key) getRevisionHistory(props.match.params.key) getApps() fetchUsecases() @@ -7502,7 +7736,7 @@ const AngularWorkflow = (defaultprops) => { loopAllowed: function (node) { return false; }, - }); + }) //cy.edgehandles({ // preview: false, @@ -7606,7 +7840,7 @@ const AngularWorkflow = (defaultprops) => { trigger.status = "stopped"; setSelectedTrigger(trigger); setWorkflow(workflow); - saveWorkflow(workflow); + saveWorkflow(workflow) }) .catch((error) => { console.log("Stop schedule error: ", error.toString()) @@ -7690,8 +7924,8 @@ const AngularWorkflow = (defaultprops) => { return; } - var mappedStartnode = "" - const alledges = cy.edges().jsons() + var mappedStartnode = "" + const alledges = cy.edges().jsons() if (alledges !== undefined && alledges !== null && alledges.length > 0) { for (let edgekey in alledges) { const tmp = alledges[edgekey] @@ -10153,6 +10387,142 @@ const AngularWorkflow = (defaultprops) => { } } + const handleAppAuthGroupCheckbox = (data) => { + console.log("CHECKED: ", data) + + if (workflow.auth_groups === undefined || workflow.auth_groups === null) { + workflow.auth_groups = [] + } + + const foundIndex = workflow.auth_groups.findIndex(auth => auth === data.id) + if (foundIndex >= 0) { + workflow.auth_groups.splice(foundIndex, 1) + } else { + workflow.auth_groups.push(data.id) + } + + setWorkflow(workflow) + console.log("WF: ", workflow) + setUpdate(Math.random()) + } + + const authgroupModal = + { + }} + > + + { + e.preventDefault(); + setAuthgroupModalOpen(false) + }} + > + + + + + Authgroup Selection + + + + Authgroups are a way to control how a workflow runs. If chosen, the workflow will use the groups on the nodes that have them selected. If three are chosen, the workflows runs three times. This is an experimental MSSP feature to handle multiple environments. + + + + + {authGroups.map((data, index) => { + var checked = false + if (workflow.auth_groups !== undefined && workflow.auth_groups !== null) { + checked = workflow.auth_groups.includes(data.id) + } + + return ( +
{ + handleAppAuthGroupCheckbox(data) + }} + > + + + + {data.label} + + + + {data.environment} + + + {data.app_auths.map((appAuth, index) => { + if (appAuth.app.large_image === undefined || appAuth.app.large_image === null || appAuth.app.large_image === "") { + const foundImage = appAuthentication.find((auth) => auth.app.id === appAuth.app.id) + if (foundImage !== undefined) { + appAuth.app.large_image = foundImage.app.large_image + + appAuth.app.name = foundImage.app.name + } + } + + const tooltip = `${appAuth.app.name.replaceAll("_", " ")} (authname: ${appAuth.label})` + + return ( + + {appAuth.app.name} + + ) + })} + +
+ ) + })} + + + +
+
+ const executionArgumentModal = { // value: auth.id, // }); setSelectedAuth(auth.id); - }; - - console.log("TRANSFORMED AUTH DATA: ", transformedAuthData); + } return (
@@ -12643,9 +13011,11 @@ const AngularWorkflow = (defaultprops) => {
)} + {workflows === undefined || workflows === null || workflows.length === 0 ? null : ( + { - const cytoscapeViewWidths = isMobile ? 50 : 850; + const cytoscapeViewWidths = isMobile ? 50 : 950; const bottomBarStyle = { position: "fixed", right: isMobile ? 20 : 20, @@ -14980,7 +15350,8 @@ const AngularWorkflow = (defaultprops) => { }}>{workflow.name} - {isCorrectOrg ? null : + {!distributedFromParent ? + isCorrectOrg ? null : Warning: Change { }} >Active Organization to edit this Workflow. + : + + Warning: This workflow is controlled by your parent org and may not be editable. + } + + {originalWorkflow.suborg_distribution === undefined || originalWorkflow.suborg_distribution === null || originalWorkflow.suborg_distribution.length === 0 || originalWorkflow.suborg_distribution.includes("none") ? null : + + + + View Suborg workflow + + + + } + + + {authGroups !== undefined && authGroups !== null && authGroups.length > 0 ? + + + + + + : null} +
{parentWorkflows.slice(0,5).map((wf, index) => { @@ -15346,7 +15940,7 @@ const AngularWorkflow = (defaultprops) => { ) } - const shownErrors = !isMobile && workflow.errors !== undefined && workflow.errors !== null && workflow.errors.length > 0 && showErrors && (!workflow.public || userdata.support === true) ? + const shownErrors = !distributedFromParent && !isMobile && workflow.errors !== undefined && workflow.errors !== null && workflow.errors.length > 0 && showErrors && (!workflow.public || userdata.support === true) ?
{ } } + /* if (( event.ctrlKey || event.metaKey ) && event.shiftKey) { console.log("Shift key pressed") if (!workflow.public && executionModalOpen) { @@ -15544,6 +16139,7 @@ const AngularWorkflow = (defaultprops) => { setExecutionModalView(0); } } + */ }; document.addEventListener('keydown', handleKeyDown); @@ -15594,6 +16190,106 @@ const AngularWorkflow = (defaultprops) => { ) } + // Used for handling suborg workflow distribution management + const updateCurrentWorkflow = (inputworkflow) => { + setLastSaved(false) + setSelectedAction({}); + setSelectedApp({}) + setWorkflow(inputworkflow) + + // Update props match key + if (inputworkflow.parentorg_workflow !== undefined && inputworkflow.parentorg_workflow !== null && inputworkflow.parentorg_workflow !== "") { + setDistributedFromParent(inputworkflow.parentorg_workflow) + } else { + setDistributedFromParent("") + } + + if (cy !== undefined) { + cy.removeListener("select"); + cy.removeListener("unselect"); + + cy.removeListener("add"); + cy.removeListener("remove"); + + cy.removeListener("mouseover"); + cy.removeListener("mouseout"); + + cy.removeListener("drag"); + cy.removeListener("free"); + cy.removeListener("cxttap"); + + setElements([]) + + // Remove all edges + cy.edges().remove() + cy.nodes().remove() + } + + // Remove all cytoscape triggers first? + /* + setTimeout(() => { + setupGraph(inputworkflow) + + cy.on("select", "node", (e) => { + onNodeSelect(e, appAuthentication); + }); + cy.on("select", "edge", (e) => onEdgeSelect(e)); + + cy.on("unselect", (e) => onUnselect(e)); + + cy.on("add", "node", (e) => onNodeAdded(e)); + cy.on("add", "edge", (e) => onEdgeAdded(e)); + cy.on("remove", "node", (e) => onNodeRemoved(e)); + cy.on("remove", "edge", (e) => onEdgeRemoved(e)); + + cy.on("mouseover", "edge", (e) => onEdgeHover(e)); + cy.on("mouseout", "edge", (e) => onEdgeHoverOut(e)); + cy.on("mouseover", "node", (e) => onNodeHover(e)); + cy.on("mouseout", "node", (e) => onNodeHoverOut(e)); + + // Handles dragging + cy.on("drag", "node", (e) => onNodeDrag(e, selectedAction)); + cy.on("free", "node", (e) => onNodeDragStop(e, selectedAction)); + + cy.on("cxttap", "node", (e) => onCtxTap(e)); + }, 25) + */ + + } + + // Uses Org-Id referencing header to create a workflow while getting it in realtime + // This further ensures the user needs access to GET the workflow properly + const duplicateParentWorkflow = (inputWorkflow, org_id, setWorkflow) => { + fetch(`${globalUrl}/api/v1/workflows/${inputWorkflow.id}`, { + method: "GET", + headers: { + "Org-Id": org_id, + "Content-Type": "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status === 200) { + getChildWorkflows(inputWorkflow.id) + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + //toast("Failed to duplicate workflow") + } else { + //toast("Successfully duplicated workflow. Reloading child workflows.") + if (setWorkflow === true) { + updateCurrentWorkflow(responseJson) + } + } + }) + .catch((error) => { + console.log("Dupe workflow for suborg error: ", error.toString()) + }) + } + const BottomCytoscapeBar = () => { if (workflow.id === undefined || workflow.id === null || (!workflow.public && apps.length === 0)) { return null; @@ -15616,11 +16312,11 @@ const AngularWorkflow = (defaultprops) => { ) : ( - - ); return ( @@ -15669,6 +16364,7 @@ const AngularWorkflow = (defaultprops) => { /> )} + {/*userdata.avatar === creatorProfile.github_avatar ? null :*/} @@ -15903,6 +16599,8 @@ const AngularWorkflow = (defaultprops) => { + +
); @@ -16021,6 +16719,7 @@ const AngularWorkflow = (defaultprops) => { aiSubmit={aiSubmit} listCache={listCache} + authGroups={authGroups} apps={apps} expansionModalOpen={codeEditorModalOpen} setExpansionModalOpen={setCodeEditorModalOpen} @@ -16600,7 +17299,8 @@ const AngularWorkflow = (defaultprops) => { // This is the playbutton at 150x150 const defaultImage = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACOCAMAAADkWgEmAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAWlBMVEX4Wj69TDgmKCvkVTwlJyskJiokJikkJSkjJSn4Ykf+6+f5h3L////8xLr5alH/9fT7nYz4Wz/919H5cVn/+vr8qpv4XUL94d35e2X//v38t6v4YUbkVDy8SzcVIzHLAAAAAWJLR0QMgbNRYwAAAAlwSFlzAAARsAAAEbAByCf1VAAAAAd0SU1FB+QGGgsvBZ/GkmwAAAFKSURBVHja7dlrTgMxDEXhFgpTiukL2vLc/zbZQH5N7MmReu4KPmlGN4m9WgGzfhgtaOZxM1rQztNoQDvPowHtTKMB7WxHA2TJkiVLlixIZMmSRYgsWbIIkSVLFiGyZMkiRNZirBcma/eKZEW87ZGsOBxPRFbE+R3Jio/LlciKuH0iWfH1/UNkRSR3RRYruSvyWKldkcjK7IpUVl5X5LLSuiKbldQV6aycrihgZXRFCau/K2pY3V1RxersijJWX1cUsnq6opLV0RW1rNldUc2a2RXlrHldsQBrTlfcLwv5EZm/PLIgkHXKPHyQRzXzYoO8BjIvzcgnBvJBxny+Ih/7zNEIcpDEHLshh5TIkS5zAI5cFzCXK8hVFHNxh1xzQpfC0BV6XWTJkkWILFmyCJElSxYhsmTJIkSWLFmEyJIlixBZsmQB8stk/U3/Yb49pVcDMg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDYtMjZUMTE6NDc6MDUrMDI6MDD8QCPmAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA2LTI2VDExOjQ3OjA1KzAyOjAwjR2bWgAAAABJRU5ErkJggg=="; + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACOCAMAAADkWgEmAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAWlBMVEX4Wj69TDgmKCvkVTwlJyskJiokJikkJSkjJSn4Ykf+6+f5h3L////8xLr5alH/9fT7nYz4Wz/919H5cVn/+vr8qpv4XUL94d35e2X//v38t6v4YUbkVDy8SzcVIzHLAAAAAWJLR0QMgbNRYwAAAAlwSFlzAAARsAAAEbAByCf1VAAAAAd0SU1FB+QGGgsvBZ/GkmwAAAFKSURBVHja7dlrTgMxDEXhFgpTiukL2vLc/zbZQH5N7MmReu4KPmlGN4m9WgGzfhgtaOZxM1rQztNoQDvPowHtTKMB7WxHA2TJkiVLlixIZMmSRYgsWbIIkSVLFiGyZMkiRNZirBcma/eKZEW87ZGsOBxPRFbE+R3Jio/LlciKuH0iWfH1/UNkRSR3RRYruSvyWKldkcjK7IpUVl5X5LLSuiKbldQV6aycrihgZXRFCau/K2pY3V1RxersijJWX1cUsnq6opLV0RW1rNldUc2a2RXlrHldsQBrTlfcLwv5EZm/PLIgkHXKPHyQRzXzYoO8BjIvzcgnBvJBxny+Ih/7zNEIcpDEHLshh5TIkS5zAI5cFzCXK8hVFHNxh1xzQpfC0BV6XWTJkkWILFmyCJElSxYhsmTJIkSWLFmEyJIlixBZsmQB8stk/U3/Yb49pVcDMg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDYtMjZUMTE6NDc6MDUrMDI6MDD8QCPmAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA2LTI2VDExOjQ3OjA1KzAyOjAwjR2bWgAAAABJRU5ErkJggg==" + const size = 40; const borderRadius = 5 if (execution.execution_source === undefined || execution.execution_source === null || execution.execution_source.length === 0) { @@ -16617,7 +17317,27 @@ const AngularWorkflow = (defaultprops) => { ) } - if (execution.execution_source === "webhook") { + if (execution.execution_source === "authgroups") { + const iconMargin = 7 + return ( +
+ +
+ ) + + } else if (execution.execution_source === "webhook") { return ( {"webhook"} { /> 0 ? ` Authgroup: ${data.authgroup}` : '')} + placement="left" >
{ {data.workflow.actions !== null ? (
{ marginBottom: "auto", }} > - {successActions} / {skippedActions > 0 ? skippedActions : {skippedActions}} / {calculatedResult} + {successActions} + {skippedActions > 0 ? skippedActions : {skippedActions}} = {calculatedResult}
) : null} @@ -17453,7 +18173,7 @@ const AngularWorkflow = (defaultprops) => { ) : null}
- {executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 && executionData.workflow.actions[0].environment !== "Cloud" ? + {executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ?
Env      @@ -17481,16 +18201,29 @@ const AngularWorkflow = (defaultprops) => { {executionData.execution_source !== undefined && executionData.execution_source !== null && executionData.execution_source.length > 0 && - executionData.execution_source !== "default" ? ( + executionData.execution_source !== "default" || + (executionData.authgroup !== undefined && executionData.authgroup !== null && executionData.authgroup.length > 0) ? (
Source    - {executionData.execution_parent !== null && + + + {executionData.execution_source === "authgroups" || (executionData.authgroup !== undefined && executionData.authgroup !== null && executionData.authgroup.length > 0) ? + + Auth Group '{executionData.authgroup !== undefined && executionData.authgroup !== null && executionData.authgroup.length > 0 ? `${executionData.authgroup}` : null}' + + : + executionData.execution_parent !== null && executionData.execution_parent !== undefined && executionData.execution_parent.length > 0 ? ( - executionData.execution_source === props.match.params.key ? ( + executionData.execution_source === props.match.params.key ? { @@ -17502,7 +18235,7 @@ const AngularWorkflow = (defaultprops) => { > Parent Execution - ) : ( + : { Parent Workflow ) - ) : ( + : executionData.execution_source === "questions" || executionData.execution_source === "web" ? { : executionData.execution_source - )} + }
) : null} @@ -20657,6 +21390,7 @@ const AngularWorkflow = (defaultprops) => { {authenticationModal} {tenzirConfigModal} {/*editWorkflowModal*/} + {authgroupModal} {executionArgumentModal} {configureWorkflowModal} {/*usecaseSlidein*/} @@ -20768,7 +21502,7 @@ const AngularWorkflow = (defaultprops) => {
) : (
- + Loading Workflow & Apps... diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx index 565f051f..3ba402cb 100755 --- a/frontend/src/views/Docs.jsx +++ b/frontend/src/views/Docs.jsx @@ -46,6 +46,7 @@ const Body = { height: "100%", color: "white", position: "relative", + paddingTop: 40, //textAlign: "center", }; diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 5c47c76b..99d4fca1 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -74,6 +74,7 @@ import { ArrowLeft as ArrowLeftIcon, ArrowRight as ArrowRightIcon, QueryStats as QueryStatsIcon, + Visibility as VisibilityIcon, } from "@mui/icons-material"; import { DataGrid, GridToolbar } from "@mui/x-data-grid"; @@ -614,6 +615,7 @@ const Workflows = (props) => { const [videoViewOpen, setVideoViewOpen] = React.useState(false) const [gettingStartedItems, setGettingStartedItems] = React.useState([]) const [selectedWorkflowIndexes, setSelectedWorkflowIndexes] = React.useState([]) + const [highlightIds, setHighlightIds] = React.useState([]) const [apps, setApps] = React.useState([]); @@ -1213,7 +1215,24 @@ const Workflows = (props) => { } setFirstLoad(false) - }, 100) + }, 250) + + /* + setTimeout(() => { + var timeout = 0 + for (var key in newarray) { + const wf = newarray[key] + if (wf.actions === undefined || wf.actions === null || wf.actions.length === 0) { + setTimeout(() => { + sideloadWorkflow(wf.id, false) + }, timeout) + + timeout += 1000 + } + + } + }, 1000) + */ } else { if (isLoggedIn) { @@ -1637,20 +1656,24 @@ const Workflows = (props) => { }); }; - const copyWorkflow = (data) => { - data = JSON.parse(JSON.stringify(data)); - toast("Copying workflow " + data.name); - data.id = ""; - data.name = data.name + "_copy"; - data = deduplicateIds(data, true); + const duplicateWorkflow = (data) => { + //data = JSON.parse(JSON.stringify(data)); + toast("Copying workflow '" + data.name + "'. The new workflow will load in and be highlighted."); + //data.id = ""; + //data.name = data.name + "_copy"; + //data = deduplicateIds(data, true); - fetch(globalUrl + "/api/v1/workflows", { + const duplicateData = { + name: data.name + "_copy", + } + + fetch(`${globalUrl}/api/v1/workflows/${data.id}/duplicate`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, - body: JSON.stringify(data), + body: JSON.stringify(duplicateData), credentials: "include", }) .then((response) => { @@ -1660,7 +1683,20 @@ const Workflows = (props) => { } return response.json(); }) - .then(() => { + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + toast("Failed copying workflow: " + responseJson.reason) + } else { + toast("Failed copying workflow") + } + + return + } + + if (responseJson.id !== undefined) { + setHighlightIds([responseJson.id]) + } setTimeout(() => { getAvailableWorkflows(); }, 1000); @@ -1689,15 +1725,68 @@ const Workflows = (props) => { }) } - const sideloadWorkflow = (id, openEdit) => { + const exportSingleWorkflow = (data, setOpen) => { + setExportModalOpen(true) + + if (data.triggers !== null && data.triggers !== undefined) { + var newSubflows = []; + for (var key in data.triggers) { + const trigger = data.triggers[key]; + + if ( + trigger.parameters !== null && + trigger.parameters !== undefined + ) { + for (var subkey in trigger.parameters) { + const param = trigger.parameters[subkey]; + if ( + param.name === "workflow" && + param.value !== data.id && + !newSubflows.includes(param.value) + ) { + newSubflows.push(param.value); + } + } + } + } + + var parsedworkflows = []; + for (var key in newSubflows) { + const foundWorkflow = workflows.find( + (workflow) => workflow.id === newSubflows[key] + ); + if (foundWorkflow !== undefined && foundWorkflow !== null) { + parsedworkflows.push(foundWorkflow); + } + } + + if (parsedworkflows.length > 0) { + console.log( + "Appending subflows during export: ", + parsedworkflows.length + ); + data.subflows = parsedworkflows; + } + } + + setExportData(data) + setOpen(false) + } + + const sideloadWorkflow = (id, action, setOpen) => { + const storagewf = localStorage.getItem("workflows") const storageWorkflows = JSON.parse(storagewf) if (storageWorkflows === null || storageWorkflows === undefined || storageWorkflows.length === 0) { } else { for (var i = 0; i < storageWorkflows.length; i++) { if (storageWorkflows[i].id === id) { - if (storageWorkflows[i].image !== "") { - return + if (storageWorkflows[i].image !== "" && storageWorkflows[i].image !== undefined && storageWorkflows[i].image !== null) { + + if (action === undefined || action === null || action === "") { + console.log("RETURNING") + return + } } } } @@ -1718,8 +1807,16 @@ const Workflows = (props) => { return response.json() }) .then((responseJson) => { - if (openEdit) { - setEditing(responseJson) + if (responseJson.success !== false && responseJson.id !== undefined) { + if (action === "edit") { + setEditing(responseJson) + } else if (action === "publish") { + setPublishModalOpen(true) + setSelectedWorkflow(responseJson) + } else if (action === "export") { + exportSingleWorkflow(responseJson, setOpen) + } + } for (var i = 0; i < storageWorkflows.length; i++) { @@ -1730,8 +1827,9 @@ const Workflows = (props) => { } } - setWorkflows(storageWorkflows) - //setFilteredWorkflows(storageWorkflows) + //setWorkflows(storageWorkflows) + setFilteredWorkflows(storageWorkflows) + //setUpdate(Math.random()) }) .catch((error) => { console.log(error.toString()) @@ -1878,7 +1976,9 @@ const Workflows = (props) => { const appGroup = getWorkflowAppgroup(data) const [triggers, subflows] = getWorkflowMeta(data) - const isDistributed = data.suborg_distribution !== undefined && data.suborg_distribution !== null && data.suborg_distribution.includes(userdata.active_org.id) + const hasSuborgs = data.suborg_distribution !== undefined && data.suborg_distribution !== null && data.suborg_distribution.length > 0 + const isDistributed = (data.parentorg_workflow !== undefined && data.parentorg_workflow !== null && data.parentorg_workflow.length > 0) //|| (data.org_id !== userdata.active_org.id && data.org_id !== undefined && data.org_id !== null && data.org_id.length > 0) + const workflowMenuButtons = ( { setAnchorEl(null); }} > + {isDistributed ? + { + navigate(`/workflows/${data.id}`) + }} + > + + Explore Workflow + + : null} { event.stopPropagation() if (data.actions !== undefined && data.actions !== null && data.actions.length > 0 && data.image !== "") { @@ -1899,7 +2011,9 @@ const Workflows = (props) => { } else { //toast("Need to side-load workflow to be edited properly") - sideloadWorkflow(data.id, true) + sideloadWorkflow(data.id, "edit") + + toast.info("Loading full workflow for editing. Please wait...") } }} key={"change"} @@ -1909,9 +2023,11 @@ const Workflows = (props) => { { - setSelectedWorkflow(data); - setPublishModalOpen(true); + sideloadWorkflow(data.id, "publish") + + toast.info("Loading full workflow for publishing. Please wait...") }} key={"publish"} > @@ -1920,9 +2036,10 @@ const Workflows = (props) => { { - copyWorkflow(data); - setOpen(false); + duplicateWorkflow(data) + setOpen(false) }} key={"duplicate"} > @@ -1931,52 +2048,11 @@ const Workflows = (props) => { { - setExportModalOpen(true); + sideloadWorkflow(data.id, "export", setOpen) - if (data.triggers !== null && data.triggers !== undefined) { - var newSubflows = []; - for (var key in data.triggers) { - const trigger = data.triggers[key]; - - if ( - trigger.parameters !== null && - trigger.parameters !== undefined - ) { - for (var subkey in trigger.parameters) { - const param = trigger.parameters[subkey]; - if ( - param.name === "workflow" && - param.value !== data.id && - !newSubflows.includes(param.value) - ) { - newSubflows.push(param.value); - } - } - } - } - - var parsedworkflows = []; - for (var key in newSubflows) { - const foundWorkflow = workflows.find( - (workflow) => workflow.id === newSubflows[key] - ); - if (foundWorkflow !== undefined && foundWorkflow !== null) { - parsedworkflows.push(foundWorkflow); - } - } - - if (parsedworkflows.length > 0) { - console.log( - "Appending subflows during export: ", - parsedworkflows.length - ); - data.subflows = parsedworkflows; - } - } - - setExportData(data); - setOpen(false); + toast.info("Loading full workflow to be exported. Please wait...") }} key={"export"} > @@ -1985,6 +2061,7 @@ const Workflows = (props) => { { setDeleteModalOpen(true); setSelectedWorkflowId(data.id); @@ -2076,7 +2153,7 @@ const Workflows = (props) => { } return ( -
+
{selectedCategory !== "" ? @@ -2462,6 +2539,8 @@ const Workflows = (props) => { } const reader = new FileReader(); + var workflowids = [] + // Waits for the read reader.addEventListener("load", (event) => { var data = reader.result; @@ -2497,7 +2576,8 @@ const Workflows = (props) => { data.org_id = userdata.active_org.id data.org = [] data.execution_org = {} - + + workflowids.push(data.id) // Actually create it setNewWorkflow( @@ -2528,6 +2608,10 @@ const Workflows = (props) => { } setLoadWorkflowsModalOpen(false); + + if (workflowids.length > 0) { + setHighlightIds(workflowids) + } }; const getWorkflowMeta = (data) => { @@ -3682,7 +3766,7 @@ const Workflows = (props) => { workflowDelay += 75 } else { return ( - + ) diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index ace7c908..1aa83b26 100644 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -1897,87 +1897,87 @@ func buildEnvVars(envMap map[string]string) []corev1.EnvVar { } func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { -if request.Body == nil { - log.Printf("[WARNING] (2) No body in request for workflowqueue") - resp.WriteHeader(http.StatusBadRequest) - return -} - -defer request.Body.Close() -body, err := ioutil.ReadAll(request.Body) -if err != nil { - log.Printf("[WARNING] (3) Failed reading body for workflowqueue") - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return -} - -var actionResult shuffle.ActionResult -err = json.Unmarshal(body, &actionResult) -if err != nil { - log.Printf("[ERROR] Failed shuffle.ActionResult unmarshaling (2): %s", err) - //resp.WriteHeader(401) - //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - //return -} - -if len(actionResult.ExecutionId) == 0 { - log.Printf("[ERROR] No workflow execution id in action result. Data: %s", string(body)) - resp.WriteHeader(400) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflow execution id in action result"}`))) - return -} - -// 1. Get the shuffle.WorkflowExecution(ExecutionId) from the database -// 2. if shuffle.ActionResult.Authentication != shuffle.WorkflowExecution.Authentication -> exit -// 3. Add to and update actionResult in workflowExecution -// 4. Push to db -// IF FAIL: Set executionstatus: abort or cancel -ctx := context.Background() -workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId) -if err != nil { - log.Printf("[ERROR][%s] Failed getting execution (workflowqueue) %s: %s", actionResult.ExecutionId, actionResult.ExecutionId, err) - resp.WriteHeader(500) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist locally."}`, actionResult.ExecutionId))) - return -} - -if workflowExecution.Authorization != actionResult.Authorization { - log.Printf("[ERROR][%s] Bad authorization key when updating node (workflowQueue). Want: %s, Have: %s", actionResult.ExecutionId, workflowExecution.Authorization, actionResult.Authorization) - resp.WriteHeader(403) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key"}`))) - return -} - -if workflowExecution.Status == "FINISHED" { - log.Printf("[DEBUG][%s] Workflowexecution is already FINISHED. No further action can be taken", workflowExecution.ExecutionId) - resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is already finished because it has status %s. Lastnode: %s"}`, workflowExecution.Status, workflowExecution.LastNode))) - return -} - -if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" { - log.Printf("[WARNING][%s] Workflowexecution already has status %s. No further action can be taken", workflowExecution.ExecutionId, workflowExecution.Status) - resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is aborted because of %s with result %s and status %s"}`, workflowExecution.LastNode, workflowExecution.Result, workflowExecution.Status))) - return -} - -retries := 0 -retry, retriesok := request.URL.Query()["retries"] -if retriesok && len(retry) > 0 { - val, err := strconv.Atoi(retry[0]) - if err == nil { - retries = val + if request.Body == nil { + log.Printf("[WARNING] (2) No body in request for workflowqueue") + resp.WriteHeader(http.StatusBadRequest) + return } -} -log.Printf("[DEBUG][%s] Action: Received, Label: '%s', Action: '%s', Status: %s, Run status: %s, Extra=Retry:%d", workflowExecution.ExecutionId, actionResult.Action.Label, actionResult.Action.AppName, actionResult.Status, workflowExecution.Status, retries) + defer request.Body.Close() + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("[WARNING] (3) Failed reading body for workflowqueue") + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } -//results = append(results, actionResult) -//log.Printf("[INFO][%s] Time to execute %s (%s) with app %s:%s, function %s, env %s with %d parameters.", workflowExecution.ExecutionId, action.ID, action.Label, action.AppName, action.AppVersion, action.Name, action.Environment, len(action.Parameters)) -//log.Printf("[DEBUG][%s] In workflowQueue with transaction", workflowExecution.ExecutionId) -runWorkflowExecutionTransaction(ctx, 0, workflowExecution.ExecutionId, actionResult, resp) + var actionResult shuffle.ActionResult + err = json.Unmarshal(body, &actionResult) + if err != nil { + log.Printf("[ERROR] Failed shuffle.ActionResult unmarshaling (2): %s", err) + //resp.WriteHeader(401) + //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + //return + } + + if len(actionResult.ExecutionId) == 0 { + log.Printf("[ERROR] No workflow execution id in action result. Data: %s", string(body)) + resp.WriteHeader(400) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflow execution id in action result"}`))) + return + } + + // 1. Get the shuffle.WorkflowExecution(ExecutionId) from the database + // 2. if shuffle.ActionResult.Authentication != shuffle.WorkflowExecution.Authentication -> exit + // 3. Add to and update actionResult in workflowExecution + // 4. Push to db + // IF FAIL: Set executionstatus: abort or cancel + ctx := context.Background() + workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId) + if err != nil { + log.Printf("[ERROR][%s] Failed getting execution (workflowqueue) %s: %s", actionResult.ExecutionId, actionResult.ExecutionId, err) + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist locally."}`, actionResult.ExecutionId))) + return + } + + if workflowExecution.Authorization != actionResult.Authorization { + log.Printf("[ERROR][%s] Bad authorization key when updating node (workflowQueue). Want: %s, Have: %s", actionResult.ExecutionId, workflowExecution.Authorization, actionResult.Authorization) + resp.WriteHeader(403) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key"}`))) + return + } + + if workflowExecution.Status == "FINISHED" { + log.Printf("[DEBUG][%s] Workflowexecution is already FINISHED. No further action can be taken", workflowExecution.ExecutionId) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is already finished because it has status %s. Lastnode: %s"}`, workflowExecution.Status, workflowExecution.LastNode))) + return + } + + if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" { + log.Printf("[WARNING][%s] Workflowexecution already has status %s. No further action can be taken", workflowExecution.ExecutionId, workflowExecution.Status) + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is aborted because of %s with result %s and status %s"}`, workflowExecution.LastNode, workflowExecution.Result, workflowExecution.Status))) + return + } + + retries := 0 + retry, retriesok := request.URL.Query()["retries"] + if retriesok && len(retry) > 0 { + val, err := strconv.Atoi(retry[0]) + if err == nil { + retries = val + } + } + + log.Printf("[DEBUG][%s] Action: Received, Label: '%s', Action: '%s', Status: %s, Run status: %s, Extra=Retry:%d", workflowExecution.ExecutionId, actionResult.Action.Label, actionResult.Action.AppName, actionResult.Status, workflowExecution.Status, retries) + + //results = append(results, actionResult) + //log.Printf("[INFO][%s] Time to execute %s (%s) with app %s:%s, function %s, env %s with %d parameters.", workflowExecution.ExecutionId, action.ID, action.Label, action.AppName, action.AppVersion, action.Name, action.Environment, len(action.Parameters)) + //log.Printf("[DEBUG][%s] In workflowQueue with transaction", workflowExecution.ExecutionId) + runWorkflowExecutionTransaction(ctx, 0, workflowExecution.ExecutionId, actionResult, resp) } // Will make sure transactions are always ran for an execution. This is recursive if it fails. Allowed to fail up to 5 times @@ -2090,7 +2090,7 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl */ } } - + if setExecution || workflowExecution.Status == "FINISHED" || workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" { log.Printf("[DEBUG][%s] Running setexec with status %s and %d/%d results", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)) //result(s)", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Results)) From 3f0ed868cca0ac5c9470d76edc2729cdfa012705 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 17 Jun 2024 01:39:52 +0200 Subject: [PATCH 03/46] MSSP multi tenant workflow features ayy --- backend/go-app/go.mod | 2 +- backend/go-app/go.sum | 4 + backend/go-app/walkoff.go | 2 + frontend/src/components/Files.jsx | 150 ++-- frontend/src/components/NewHeader.jsx | 8 +- frontend/src/components/ParsedAction.jsx | 16 +- frontend/src/components/Priorities.jsx | 20 +- .../src/components/ShuffleCodeEditor1.jsx | 14 +- frontend/src/views/Admin.jsx | 654 +++++++++++------- frontend/src/views/AngularWorkflow.jsx | 430 ++++++++++-- frontend/src/views/Apps.jsx | 21 +- frontend/src/views/Docs.jsx | 8 +- frontend/src/views/SettingsPage.jsx | 88 ++- frontend/src/views/Workflows.jsx | 11 +- 14 files changed, 1064 insertions(+), 364 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 659f6364..5191a1d0 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -20,7 +20,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.6.40 + github.com/shuffle/shuffle-shared v0.6.46 golang.org/x/crypto v0.22.0 google.golang.org/api v0.176.1 google.golang.org/grpc v1.63.2 diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum index 4c0d4206..13000a5c 100644 --- a/backend/go-app/go.sum +++ b/backend/go-app/go.sum @@ -303,6 +303,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -443,6 +445,8 @@ github.com/shuffle/shuffle-shared v0.6.27 h1:q4qZD6bGZFIvZ5Y10unGr3N3rZ7OryWyvva github.com/shuffle/shuffle-shared v0.6.27/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= github.com/shuffle/shuffle-shared v0.6.31 h1:MK1SW1pwjIP7hznq+mMlTPM1R3LIOfi1/bUL5xFSg/8= github.com/shuffle/shuffle-shared v0.6.31/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= +github.com/shuffle/shuffle-shared v0.6.46 h1:v/IXc+4V8DCWflGKGgaI37uuGnsylMjqKRylwNoXVrA= +github.com/shuffle/shuffle-shared v0.6.46/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 3d417472..8ea1550a 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -3382,6 +3382,8 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) { return } + debugUrl := fmt.Sprintf("/workflows/%s?execution_id=%s", workflowExecution.Workflow.ID, workflowExecution.ExecutionId) + resp.Header().Add("X-Debug-Url", debugUrl) workflowExecution.Priority = 11 environments, err := shuffle.GetEnvironments(ctx, user.ActiveOrg.Id) diff --git a/frontend/src/components/Files.jsx b/frontend/src/components/Files.jsx index 692d2e62..4ad5292a 100644 --- a/frontend/src/components/Files.jsx +++ b/frontend/src/components/Files.jsx @@ -44,9 +44,9 @@ const Files = (props) => { const { globalUrl, userdata, serverside, selectedOrganization, isCloud,isSelectedFiles } = props; const [files, setFiles] = React.useState([]); - const [selectedNamespace, setSelectedNamespace] = React.useState("default"); + const [selectedCategory, setSelectedCategory] = React.useState("default"); const [openFileId, setOpenFileId] = React.useState(false); - const [fileNamespaces, setFileNamespaces] = React.useState([]); + const [fileCategories, setFileCategories] = React.useState([]); const [fileContent, setFileContent] = React.useState(""); const [openEditor, setOpenEditor] = React.useState(false); const [renderTextBox, setRenderTextBox] = React.useState(false); @@ -57,6 +57,7 @@ const Files = (props) => { const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/shuffle/standards") const [downloadBranch, setDownloadBranch] = React.useState("main"); const [downloadFolder, setDownloadFolder] = React.useState("translation_standards"); + const [contentLoading, setContentLoading] = React.useState(false) //const alert = useAlert(); const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log", "eml", "msg", "md", "xml", "sh", "bat", "ps1", "psm1", "psd1", "ps1xml", "pssc", "psc1", "response"] @@ -67,8 +68,8 @@ const Files = (props) => { console.log('do validate') console.log("new namespace name->",event.target.value); - fileNamespaces.push(event.target.value); - setSelectedNamespace(event.target.value); + fileCategories.push(event.target.value); + setSelectedCategory(event.target.value); setRenderTextBox(false); } @@ -107,12 +108,13 @@ const Files = (props) => { const getFiles = (namespace) => { var parsedurl = `${globalUrl}/api/v1/files` - if (namespace === undefined || namespace === "default") { + + if (namespace === undefined || namespace === null || namespace === "default") { } else if (namespace !== undefined && namespace !== null && namespace !== "") { parsedurl = `${globalUrl}/api/v1/files/namespaces/${namespace}?ids=true` - } else if (selectedNamespace !== undefined && selectedNamespace !== null && selectedNamespace !== "default" && selectedNamespace !== "") { - parsedurl = `${globalUrl}/api/v1/files/namespaces/${selectedNamespace}?ids=true` + } else if (selectedCategory !== undefined && selectedCategory !== null && selectedCategory !== "default" && selectedCategory !== "") { + parsedurl = `${globalUrl}/api/v1/files/namespaces/${selectedCategory}?ids=true` } fetch(parsedurl, { @@ -149,9 +151,11 @@ const Files = (props) => { setFiles([]); } - if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileNamespaces.length === 0 || responseJson.namespaces.length > fileNamespaces.length)) { - setFileNamespaces(responseJson.namespaces); - } + if (namespace === undefined || namespace === null || namespace === "default") { + if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileCategories.length === 0 || responseJson.namespaces.length > fileCategories.length)) { + setFileCategories(responseJson.namespaces) + } + } }) .catch((error) => { toast(error.toString()); @@ -159,7 +163,21 @@ const Files = (props) => { }; useEffect(() => { - getFiles(selectedNamespace) + getFiles("default") + + setTimeout(() => { + var category = selectedCategory + if (window.location.search.includes("category=")) { + const urlParams = new URLSearchParams(window.location.search) + category = urlParams.get("category") + } + + if (category !== undefined && category !== null && category.length > 0 && category !== "default") { + setSelectedCategory(category) + } + + getFiles(category) + }, 1000) }, []); const importStandardsFromUrl = (url, folder) => { @@ -393,7 +411,7 @@ const Files = (props) => { toast("Failed to delete file: " + responseJson.reason); } setTimeout(() => { - getFiles(); + getFiles(selectedCategory) }, 1500); }) .catch((error) => { @@ -402,6 +420,8 @@ const Files = (props) => { }; const readFileData = (file) => { + setContentLoading(true) + fetch(globalUrl + "/api/v1/files/" + file.id + "/content", { method: "GET", headers: { @@ -411,6 +431,7 @@ const Files = (props) => { credentials: "include", }) .then((response) => { + setContentLoading(false) if (response.status !== 200) { console.log("Status not 200 for file :O!"); return ""; @@ -428,12 +449,12 @@ const Files = (props) => { return respdata }) .then((responseData) => { - - setFileContent(responseData); - //console.log("filecontent state ",fileContent); + setFileContent(responseData); + //console.log("filecontent state ",fileContent); }) .catch((error) => { - toast(error.toString()); + setContentLoading(false) + toast(error.toString()) }); }; @@ -505,12 +526,12 @@ const Files = (props) => { }; if ( - selectedNamespace !== undefined && - selectedNamespace !== null && - selectedNamespace.length > 0 && - selectedNamespace !== "default" + selectedCategory !== undefined && + selectedCategory !== null && + selectedCategory.length > 0 && + selectedCategory !== "default" ) { - data.namespace = selectedNamespace; + data.namespace = selectedCategory; } fetch(globalUrl + "/api/v1/files/create", { @@ -689,9 +710,9 @@ const Files = (props) => { - {fileNamespaces !== undefined && - fileNamespaces !== null && - fileNamespaces.length > 1 ? ( + {fileCategories !== undefined && + fileCategories !== null && + fileCategories.length > 1 ? ( File Category { + if (e.target.value === "") { + return + } + + if (e.target.value === selectedEnvironment) { + return + } + + toast.info("Updating environment KMS runs on to " + e.target.value) + data.environment = e.target.value + const envIndex = environments.findIndex((env) => env.Name === e.target.value) + if (envIndex === -1) { + toast.error("Environment not found") + return + } + + environments[envIndex].environment = e.target.value + setEnvironments(environments) + setShowEnvironmentDropdown(false) + + saveAuthentication(data) + }} + > + {environments.map((env, index) => { + if (env.archived === true) { + return null + } + + return ( + + {env.default === true ? "Default - " : ""}{env.Name} + + ) + })} + + + : null} +
+ } + style={{ + minWidth: 225, + maxWidth: 225, + overflow: "hidden", + }} + /> + + {/* + + */} + + {/* + + */} + { + return data.key; + }) + .join(", ") + } + style={{ + minWidth: 125, + maxWidth: 125, + overflow: "auto", + marginRight: 10, + }} + /> + + + { + updateAppAuthentication(data); + }} + disabled={ + data.org_id !== selectedOrganization.id ? true : false + } + > + + + {data.defined ? ( + + { + editAuthenticationConfig(data.id); + }} + > + + + + ) : ( + + {}} + disabled={ + data.org_id !== selectedOrganization.id + ? true + : false + } + > + + + + )} + { + deleteAuthentication(data); + }} + > + + + + + {selectedOrganization.id !== undefined && + data.org_id !== selectedOrganization.id ? ( + + + + ) : ( + + { + changeDistribution(data, !isDistributed); + }} + /> + + )} + + + ) + } + const authenticationView = curTab === 2 ? ( @@ -5361,6 +5721,10 @@ If you're interested, please let me know a time that works for you, or set up a
{authentication.map((data, index) => { var checked = data.checked + if (data.label !== undefined && data.label !== null && data.label.toLowerCase() === "kms shuffle storage") { + return null + } + if (checked === undefined || checked === null) { checked = false } @@ -5476,11 +5840,6 @@ If you're interested, please let me know a time that works for you, or set up a {authentication === undefined || authentication === null ? null : authentication.map((data, index) => { - var bgColor = "#27292d"; - if (index % 2 === 0) { - bgColor = "#1f2023"; - } - //console.log("Auth data: ", data) if (data.type === "oauth2") { data.fields = [ @@ -5503,187 +5862,14 @@ If you're interested, please let me know a time that works for you, or set up a ]; } - const isDistributed = - data.suborg_distributed === true ? true : false; - return ( - - - style={{ minWidth: 75, maxWidth: 75 }} - /> - - - {/* - - */} - - {/* - - */} - { - return data.key; - }) - .join(", ") - } - style={{ - minWidth: 125, - maxWidth: 125, - overflow: "auto", - marginRight: 10, - }} - /> - - - { - updateAppAuthentication(data); - }} - disabled={ - data.org_id !== selectedOrganization.id ? true : false - } - > - - - {data.defined ? ( - - { - editAuthenticationConfig(data.id); - }} - > - - - - ) : ( - - {}} - disabled={ - data.org_id !== selectedOrganization.id - ? true - : false - } - > - - - - )} - { - deleteAuthentication(data); - }} - > - - - - - {selectedOrganization.id !== undefined && - data.org_id !== selectedOrganization.id ? ( - - - - ) : ( - - { - changeDistribution(data, !isDistributed); - }} - /> - - )} - - - ); + + ) + + })}
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index f10ffae8..021cc0d6 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -88,6 +88,7 @@ import { Error as ErrorIcon, Warning as WarningIcon, ArrowLeft as ArrowLeftIcon, + ArrowRight as ArrowRightIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, FormatListNumbered as FormatListNumberedIcon, @@ -123,6 +124,8 @@ import { Add as AddIcon, ErrorOutline as ErrorOutlineIcon, + ArrowForward as ArrowForwardIcon, + } from "@mui/icons-material"; //import * as cytoscape from "cytoscape"; @@ -378,7 +381,7 @@ const svgSize = 24; const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const AngularWorkflow = (defaultprops) => { - const { globalUrl, setCookie, isLoggedIn, isLoaded, userdata, data_id } = defaultprops; + const { globalUrl, setCookie, isLoggedIn, isLoaded, userdata, data_id, ReactGA, } = defaultprops; const referenceUrl = globalUrl + "/api/v1/hooks/"; //const alert = useAlert() let navigate = useNavigate(); @@ -499,6 +502,7 @@ const AngularWorkflow = (defaultprops) => { const [selectedApp, setSelectedApp] = React.useState({}); const [selectedAction, setSelectedAction] = React.useState({}); const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({}); + const [selectedMeta, setSelectedMeta] = React.useState(undefined); // Disabled streaming for now const [streamDisabled, setStreamDisabled] = React.useState(true) @@ -950,6 +954,10 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (responseJson.success === true) { + if (responseJson.meta !== undefined && responseJson.meta !== null && Object.getOwnPropertyNames(responseJson.meta).length > 0) { + setSelectedMeta(responseJson.meta) + } + if (responseJson.reason !== undefined && responseJson.reason !== undefined && responseJson.reason.length > 0) { if (!responseJson.reason.includes("404: Not Found") && responseJson.reason.length > 25) { // Translate into markdown ![]() @@ -8018,10 +8026,10 @@ const AngularWorkflow = (defaultprops) => { backgroundColor: theme.palette.surfaceColor, cursor: "pointer", display: "flex", - }; + } - const VariableItem = (props) => { - const { variable, index, type } = props; + const VariableItem = (props) => { + const { variable, index, type } = props; const [open, setOpen] = React.useState(false); const [anchorEl, setAnchorEl] = React.useState(null); @@ -8792,7 +8800,25 @@ const AngularWorkflow = (defaultprops) => { ? "cloud" : environments[defaultEnvironmentIndex] === undefined ? "cloud" - : environments[defaultEnvironmentIndex].Name; + : environments[defaultEnvironmentIndex].Name + + // Basic automatic auth mapping + var authId = "" + if (appAuthentication !== undefined && appAuthentication !== null && appAuthentication.length > 0) { + const appname = app.name.toLowerCase().replace(" ", "_") + for (var key in appAuthentication) { + const authKey = appAuthentication[key] + if (authKey.app.id === app.id) { + authId = authKey.id + break + } + + const appauthname = authKey.app.name.toLowerCase().replace(" ", "_") + if (appauthname === appname) { + authId = authKey.id + } + } + } // List other nodes in the workflow and see if they have an environment set. If they do, use that as the default if (cy !== undefined && cy !== null) { @@ -8840,7 +8866,7 @@ const AngularWorkflow = (defaultprops) => { app.categories.length > 0 ? app.categories[0] : "", - authentication_id: "", + authentication_id: authId, finished: false, template: app.template === true ? true : false, }; @@ -9722,30 +9748,49 @@ const AngularWorkflow = (defaultprops) => { } } - setSelectedAction(newSelectedAction); - setUpdate(Math.random()); + // Last fix for params + if (newSelectedAction.parameters !== undefined && newSelectedAction.parameters !== null && newSelectedAction.parameters.length > 0) { + for (let paramkey in newSelectedAction.parameters) { + const param = newSelectedAction.parameters[paramkey] + if (param.name !== "body") { + continue + } - const allNodes = cy.nodes().jsons(); - if (allNodes !== undefined && allNodes !== null) { - for (let nodekey in allNodes) { - const currentNode = allNodes[nodekey]; - if ( - currentNode.data.attachedTo === oldaction.id && - currentNode.data.isDescriptor - ) { - const foundnode = cy.getElementById(currentNode.data.id); - if (foundnode !== null && foundnode !== undefined) { - const iconInfo = GetIconInfo(newaction); - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - foundnode.data("image", svgpin_Url); - foundnode.data("imageColor", iconInfo.iconBackgroundColor); - } - - break; + if (param.example !== undefined && param.example !== null && param.example.length > 0) { + if (param.value === undefined || param.value === null || param.value.length === 0) { + param.value = param.example } } + + newSelectedAction.parameters[paramkey] = param } + } + + console.log("New selected action: ", newSelectedAction) + setSelectedAction(newSelectedAction) + setUpdate(Math.random()) + + const allNodes = cy.nodes().jsons() + if (allNodes !== undefined && allNodes !== null) { + for (let nodekey in allNodes) { + const currentNode = allNodes[nodekey]; + if ( + currentNode.data.attachedTo === oldaction.id && + currentNode.data.isDescriptor + ) { + const foundnode = cy.getElementById(currentNode.data.id); + if (foundnode !== null && foundnode !== undefined) { + const iconInfo = GetIconInfo(newaction); + const svg_pin = ``; + const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); + foundnode.data("image", svgpin_Url); + foundnode.data("imageColor", iconInfo.iconBackgroundColor); + } + + break; + } + } + } // Send it in here, after all fields are filled // Disabled for now :( @@ -17673,6 +17718,71 @@ const AngularWorkflow = (defaultprops) => { ) } + const changeExecution = (data) => { + if ((data.result === undefined || data.result === null || data.result.length === 0) && data.status !== "FINISHED" && data.status !== "ABORTED") { + start() + setExecutionRunning(true) + setExecutionRequestStarted(false) + } + + var checkStarted = false + if (data.results !== undefined && data.results !== null && data.results.length > 0) { + if (data.execution_argument !== undefined && data.execution_argument !== null && data.execution_argument.includes("too large")) { + setExecutionData({}); + checkStarted = true + start(); + setExecutionRunning(true); + setExecutionRequestStarted(false); + } else { + if (data.results !== undefined && data.results !== null) { + for (let resultkey in data.results) { + if (data.results[resultkey].status !== "SUCCESS") { + continue + } + + if (data.results[resultkey].result.includes("too large")) { + setExecutionData({}); + checkStarted = true + start(); + setExecutionRunning(true); + setExecutionRequestStarted(false); + break + } + } + } + } + } + + const cur_execution = { + execution_id: data.execution_id, + authorization: data.authorization, + } + + setExecutionRequest(cur_execution) + setExecutionModalView(1) + + if (!checkStarted) { + handleUpdateResults(data, cur_execution) + + if (cy !== undefined && cy !== null) { + cy.elements().removeClass("success-highlight failure-highlight executing-highlight"); + for (let actionKey in data.workflow.actions) { + var actionitem = data.workflow.actions[actionKey] + + handleColoring(actionitem.id, "", actionitem.label) + } + + for (let resultKey in data.results) { + var item = data.results[resultKey] + + handleColoring(item.action.id, item.status, item.action.label) + } + } + + setExecutionData(data) + } + } + const ShowCopyingTooltip = () => { const [showCopying, setShowCopying] = React.useState(true) @@ -18101,7 +18211,7 @@ const AngularWorkflow = (defaultprops) => {

Details

@@ -18147,7 +18257,80 @@ const AngularWorkflow = (defaultprops) => { ) : null} - {isCloud ? ( + + + + + + + + + + + + + + {isCloud ? { - ) : null} + : null} +
{executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ?
@@ -18801,21 +18985,49 @@ const AngularWorkflow = (defaultprops) => { validate.result = JSON.parse(validate.result) } - const AppResultVariable = ({ data }) => { + const AppResultVariable = ({ data, action }) => { const [open, setOpen] = React.useState(false) const showVariable = data.value.length < 60 // Check if it's valid JSON const checked = validateJson(data.value.trim()) + if (data.name === "shuffle_action_logs" && data.value !== undefined && data.value !== null && data.value.length > 0 && data.value.includes("add env SHUFFLE_LOGS_DISABLED")) { + return ( +
+ + Action Logs + + + Logs for an action are not available without an onprem environment with the SHUFFLE_LOGS_DISABLED environment variable set to false: SHUFFLE_LOGS_DISABLED=false. Logs are enabled by default, except in scale mode. + +
+ ) + } + + var showlink = false + if (data.name.endsWith("-Url")) { + //data.name = data.name.toLowerCase().replaceAll("-", "_") + if (data.value.startsWith(", ")) { + data.value = data.value.substring(2) + } + + if (!data.value.startsWith("http") || (data.value.startsWith("/") && data.value.includes("?"))) { + showlink = true + } + } + return (
{data.value.length > 60 || checked.valid ? { variant="body2" style={{ whiteSpace: 'pre-line', + color: showlink ? "#f85a3e" : "white", + cursor: showlink ? "pointer" : "default", }} - color="textSecondary" + onClick={(e) => { + if (showlink) { + e.preventDefault() + e.stopPropagation() + window.open(data.value, "_blank") + } + }} + color={showlink ? "inherit" : "textSecondary"} > {data.value} @@ -18981,6 +19202,10 @@ const AngularWorkflow = (defaultprops) => { return "Consider whether your Orborus environment can connect to a local IP or not." } + if (stringjson.includes("kms/")) { + return "KMS authentication most likely failed. Check your notifications for more details on this page: /admin?admin_tab=priorities. If you need help with KMS, please contact support@shuffler.io" + } + if (stringjson.includes("invalidurl")) { // IF count of "http" is more than one, 1, it's prolly invalid var additionalinfo = "" @@ -19006,7 +19231,7 @@ const AngularWorkflow = (defaultprops) => { if (stringjson.includes("connectionerror")) { if (stringjson.includes("kms")) { - return "KMS authentication failed. Check your notifications for more details." + return "KMS authentication most likely failed (2). Check your notifications for more details on this page: /admin?admin_tab=priorities&kms=true. If you need help with KMS, please contact support@shuffler.io" } return "The URL is incorrect, or Shuffle can't reach it. Set up a Shuffle Environment in the same VLAN, or whitelist Shuffle's IPs." @@ -19352,7 +19577,7 @@ const AngularWorkflow = (defaultprops) => { } return ( - + ); })}
@@ -20184,6 +20409,8 @@ const AngularWorkflow = (defaultprops) => { //if (configureWorkflowModalOpen) { // setSelectedAction({}); //} + // + setSelectedMeta(undefined) }} PaperProps={{ style: { @@ -20315,11 +20542,27 @@ const AngularWorkflow = (defaultprops) => { overflowY: "auto", overflowX: "hidden", }} + onLoad={() => { + /* + if (isCloud && ReactGA !== undefined) { + toast("Sending GA info") + // Google analytics info about what app people are looking at + ReactGA.event({ + category: "workflow", + action: `documentation_load`, + label: selectedApp.name, + }) + + } + */ + }} > {selectedApp.documentation === undefined || selectedApp.documentation === null || selectedApp.documentation.length === 0 ? ( - + { style={{ marginTop: 25, marginBottom: 25, - backgroundColor: theme.palette.inputColor, + backgroundColor: "rgba(255,255,255,0.6)", }} /> - - There is currently no extended documentation available for this - app. - + +
+ + There is no Shuffle-specific documentation for this app yet. Documentation is written custom for each app, and is a community effort. Hope to see your contribution + + +
+ - Want to help the making of, or imrpvoe this app?{" "} + Want to help the making of, or improve this app?{" "} +
{ )}
) : ( +
+ {selectedMeta !== undefined && selectedMeta !== null && Object.getOwnPropertyNames(selectedMeta).length > 0 && selectedMeta.name !== undefined && selectedMeta.name !== null ? +
+
+ {isMobile ? null : ( + + + + + + )} + {isMobile ? null : ( +
+ )} + + {selectedMeta.read_time} minute + {selectedMeta.read_time === 1 ? "" : "s"} to read + +
+
+ {isMobile || + selectedMeta.contributors === undefined || + selectedMeta.contributors === null ? ( + "" + ) : ( +
+ {selectedMeta.contributors.slice(0, 7).map((data, index) => { + return ( + + + {data.url} + + + ); + })} +
+ )} +
+
+ : null} { > {selectedApp.documentation} +
)}
diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index 1000f7a0..4c816623 100755 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -955,7 +955,6 @@ const Apps = (props) => { : null - console.log("Sharing config: ", sharingConfiguration); const activateButton = selectedApp.generated && !selectedApp.activated ? (
@@ -1281,10 +1280,15 @@ const Apps = (props) => { {isCloud && !internalIds.includes(selectedApp.name.toLowerCase()) ? -

{passwordFormMessage}

- +

{passwordFormMessage}

{isCloud && ( <> diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 99d4fca1..b742157a 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -2153,7 +2153,7 @@ const Workflows = (props) => { } return ( -
+
{selectedCategory !== "" ? @@ -2206,8 +2206,17 @@ const Workflows = (props) => { Edit '{data.name}' + +
+ + {isDistributed || hasSuborgs ? + + This is a parentorg-controlled workflow. + + : null}
} placement="right"> + Date: Mon, 17 Jun 2024 01:41:39 +0200 Subject: [PATCH 04/46] Fixed backend refs --- backend/go-app/go.mod | 10 +++++----- backend/go-app/go.sum | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 5191a1d0..ebe85b01 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -20,15 +20,15 @@ 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.6.46 + github.com/shuffle/shuffle-shared v0.6.47 golang.org/x/crypto v0.22.0 google.golang.org/api v0.176.1 google.golang.org/grpc v1.63.2 gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.30.0 - k8s.io/apimachinery v0.30.0 - k8s.io/client-go v0.30.0 + k8s.io/api v0.30.2 + k8s.io/apimachinery v0.30.2 + k8s.io/client-go v0.30.2 ) require ( @@ -58,7 +58,7 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/frikky/schemaless v0.0.11 // indirect + github.com/frikky/schemaless v0.0.13 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum index 13000a5c..2023c819 100644 --- a/backend/go-app/go.sum +++ b/backend/go-app/go.sum @@ -160,6 +160,8 @@ github.com/frikky/schemaless v0.0.9 h1:RzNLPkJq5c4nlm5iLiTndFcbeQxdMGJIj266wSGt2 github.com/frikky/schemaless v0.0.9/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ= github.com/frikky/schemaless v0.0.11 h1:c4r6CJX30XI+SoJdT9RlUd9qYSQlx6hvwGRtsypu+uM= github.com/frikky/schemaless v0.0.11/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ= +github.com/frikky/schemaless v0.0.13 h1:ARiN9V7wr2VZXAr9JK5wvTbyPgpGrgeiL1VhR5MlgaQ= +github.com/frikky/schemaless v0.0.13/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsouza/go-dockerclient v1.11.0 h1:4ZAk6W7rPAtPXm7198EFqA5S68rwnNQORxlOA5OurCA= @@ -447,6 +449,8 @@ github.com/shuffle/shuffle-shared v0.6.31 h1:MK1SW1pwjIP7hznq+mMlTPM1R3LIOfi1/bU github.com/shuffle/shuffle-shared v0.6.31/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= github.com/shuffle/shuffle-shared v0.6.46 h1:v/IXc+4V8DCWflGKGgaI37uuGnsylMjqKRylwNoXVrA= github.com/shuffle/shuffle-shared v0.6.46/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= +github.com/shuffle/shuffle-shared v0.6.47 h1:EOalfIIBX97uGkgRRPsdUApts6yCCcXf1iSphm6UoE0= +github.com/shuffle/shuffle-shared v0.6.47/go.mod h1:XVIcR2/GyIk+6qmlpOG3NvIao6kCOma36EdMym4AeSY= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -981,10 +985,16 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= +k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= +k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= +k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= +k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= +k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= From 1296af8786d139e980f66bd2612f585685cb4066 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 17 Jun 2024 12:24:32 +0530 Subject: [PATCH 05/46] Fixed page reload issue on EnterKey press --- frontend/src/components/AppGrid.jsx | 10 ++++++++++ frontend/src/components/CreatorGrid.jsx | 5 +++++ frontend/src/components/DiscordChat.jsx | 5 +++++ frontend/src/components/DocsGrid.jsx | 5 +++++ frontend/src/components/SearchData.jsx | 4 ++-- frontend/src/components/WorkflowGrid.jsx | 5 +++++ 6 files changed, 32 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx index 0f3fedef..11e25f0b 100644 --- a/frontend/src/components/AppGrid.jsx +++ b/frontend/src/components/AppGrid.jsx @@ -232,6 +232,11 @@ const AppGrid = (props) => { removeQuery("q"); refine(event.currentTarget.value); }} + onKeyDown={(event) => { + if(event.key === "Enter") { + event.preventDefault(); + } + }} limit={5} /> {/*isSearchStalled ? 'My search is stalled' : ''*/} @@ -994,6 +999,11 @@ const AppGrid = (props) => { onChange={(event) => { setSearchQuery(event.currentTarget.value); }} + onKeyDown={(event) => { + if(event.key === "Enter") { + event.preventDefault(); + } + }} limit={5} /> {/*isSearchStalled ? 'My search is stalled' : ''*/} diff --git a/frontend/src/components/CreatorGrid.jsx b/frontend/src/components/CreatorGrid.jsx index ce6a197f..7010f483 100644 --- a/frontend/src/components/CreatorGrid.jsx +++ b/frontend/src/components/CreatorGrid.jsx @@ -136,6 +136,11 @@ const CreatorGrid = props => { removeQuery("q") refine(event.currentTarget.value) }} + onKeyDown={(event) => { + if(event.key === "Enter") { + event.preventDefault(); + } + }} /> {/*isSearchStalled ? 'My search is stalled' : ''*/} diff --git a/frontend/src/components/DiscordChat.jsx b/frontend/src/components/DiscordChat.jsx index e1d226b8..d06711c7 100644 --- a/frontend/src/components/DiscordChat.jsx +++ b/frontend/src/components/DiscordChat.jsx @@ -75,6 +75,11 @@ const DiscordChat = props => { fullWidth value={currentRefinement} onChange={(event) => refine(event.currentTarget.value)} + onKeyDown={(event) => { + if(event.key === "Enter") { + event.preventDefault(); + } + }} placeholder="Search Discord Chats" style={{ backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%", }} InputProps={{ diff --git a/frontend/src/components/DocsGrid.jsx b/frontend/src/components/DocsGrid.jsx index 06469251..bc17543f 100644 --- a/frontend/src/components/DocsGrid.jsx +++ b/frontend/src/components/DocsGrid.jsx @@ -124,6 +124,11 @@ const DocsGrid = props => { removeQuery("q") refine(event.currentTarget.value) }} + onKeyDown={(event) => { + if(event.key === "Enter") { + event.preventDefault(); + } + }} limit={5} /> {/*isSearchStalled ? 'My search is stalled' : ''*/} diff --git a/frontend/src/components/SearchData.jsx b/frontend/src/components/SearchData.jsx index 35e7b9e3..c390666a 100644 --- a/frontend/src/components/SearchData.jsx +++ b/frontend/src/components/SearchData.jsx @@ -88,12 +88,12 @@ const SearchData = props => { const textFieldRef = useRef(null); const keyPressHandler = (e) => { - if (e.which === 13) { + if (e.key === "Enter") { + e.preventDefault(); // navigate(`/search?q=${currentRefinement}`, { state: value, replace: true }); // setModalOpen(false); const trimmedValue = inputValue.trim(); if (trimmedValue !== '') { - e.preventDefault(); navigate(`/search?q=${trimmedValue}`, { state: trimmedValue, replace: true }); setModalOpen(false); } diff --git a/frontend/src/components/WorkflowGrid.jsx b/frontend/src/components/WorkflowGrid.jsx index 589e4f7d..e10d2131 100644 --- a/frontend/src/components/WorkflowGrid.jsx +++ b/frontend/src/components/WorkflowGrid.jsx @@ -221,6 +221,11 @@ const AppGrid = props => { removeQuery("q") refine(event.currentTarget.value) }} + onKeyDown={(event) => { + if(event.key === "Enter") { + event.preventDefault(); + } + }} limit={5} /> : null} From de2a8ba8c8f8cf29b0eabe8ded259a811cfc829c Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 17 Jun 2024 14:32:45 +0200 Subject: [PATCH 06/46] Added random_element filter to properly work --- backend/app_sdk/app_base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index a7e0d4f8..415f1e94 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -97,6 +97,7 @@ def md5_base64(a): a = str(a) foundhash = hashlib.md5(a.encode('utf-8')).hexdigest() return base64.b64encode(foundhash.encode('utf-8')) + @shuffle_filters.register def base64_encode(a): @@ -107,6 +108,17 @@ def base64_encode(a): except: return base64.b64encode(a).decode() +@shuffle_filters.register +def random_element(a): + # Choose a random item from an array + a = list(a) + + if len(a) == 0: + return "" + + return random.choice(a) + + @shuffle_filters.register def base64_decode(a): a = str(a) From 5964f914b962ae41a629dea5ce016106ad2c1cbe Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Wed, 29 May 2024 17:05:23 +0530 Subject: [PATCH 07/46] Fixed the Re-rendering issue of Apps/Actions & Triggers --- frontend/src/components/ParsedAction.jsx | 2455 +++++++++++----------- frontend/src/views/AngularWorkflow.jsx | 811 ++++--- 2 files changed, 1586 insertions(+), 1680 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 0190a6bd..c90e7747 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -182,7 +182,14 @@ const ParsedAction = (props) => { const [hiddenDescription, setHiddenDescription] = React.useState(true); const [autoCompleting, setAutocompleting] = React.useState(false); - + const [selectedActionParameters, setSelectedActionParameters] = React.useState([]); + const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); + const [actionlist, setActionlist] = React.useState([]); + const [jsonList, setJsonList] = React.useState([]); + const [showDropdown, setShowDropdown] = React.useState(false); + const [showDropdownNumber, setShowDropdownNumber] = React.useState(0); + const [showAutocomplete, setShowAutocomplete] = React.useState(false); + const [menuPosition, setMenuPosition] = useState(null); const isIntegration = selectedAction.app_id === "integration" useEffect(() => { @@ -372,15 +379,6 @@ const ParsedAction = (props) => { //setStartNode(selectedAction.id) }; - const AppActionArguments = (props) => { - const [selectedActionParameters, setSelectedActionParameters] = React.useState([]); - const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); - const [actionlist, setActionlist] = React.useState([]); - const [jsonList, setJsonList] = React.useState([]); - const [showDropdown, setShowDropdown] = React.useState(false); - const [showDropdownNumber, setShowDropdownNumber] = React.useState(0); - const [showAutocomplete, setShowAutocomplete] = React.useState(false); - const [menuPosition, setMenuPosition] = useState(null); useEffect(() => { if (selectedActionParameters !== undefined && selectedActionParameters !== null && selectedActionParameters.length === 0 @@ -1185,7 +1183,6 @@ const ParsedAction = (props) => { } // FIXME: Issue #40 - selectedActionParameters not reset - if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { var wrapperapp = { "id": "", @@ -1217,8 +1214,1198 @@ const ParsedAction = (props) => { // Check the actual value and if it's the same noAppSelected = true } - return ( -
+ + + const ActionSelectOption = (actionprops) => { + const { data, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops; + const [hover, setHover] = React.useState(false); + + return ( + +
setHover(true)} onMouseLeave={() => setHover(false)} + onClick={() => { + //setSelectedAction(actionprops) + //setShowActionList(false) + //setUpdate(Math.random()) + // + if (data !== undefined && data !== null) { + setNewSelectedAction({ + target: { + value: data.name + } + }); + } + }} + > +
+ + {useIcon} + + {newActionname} +
+ {extraDescription.length > 0 ? + + {extraDescription} + + : null} +
+
+ ) + } + + const sortByCategoryLabel = (a, b) => { + const aHasCategoryLabel = a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0 + const bHasCategoryLabel = b.category_label !== undefined && b.category_label !== null && b.category_label.length > 0 + + // Sort by existence and length of "category_label" + if (aHasCategoryLabel && !bHasCategoryLabel) { + return -1 + } else if (!aHasCategoryLabel && bHasCategoryLabel) { + return 1 + } else { + return 0 + } + } + + // Function to deduplicate based on the "name" field + const deduplicateByName = (array) => { + const uniqueNames = {}; + return array.filter(item => { + if (!item.hasOwnProperty('name') || !item.name.length) { + return true + } + if (!uniqueNames[item.name]) { + uniqueNames[item.name] = true + return true + } + return false + }) + } + + // Gets the most important actions first + const renderedActionOptions = deduplicateByName(( + selectedApp.actions === undefined || selectedApp.actions === null ? [] : + selectedApp.actions.filter((a) => + a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0).concat(sortByKey(selectedApp.actions, "label")) + ).sort(sortByCategoryLabel)) + + + const selectedAppIcon = selectedAction.large_image + var baselabel = selectedAction.label + return ( +
+ + {hideExtraTypes === true ? null : ( + +
+
+
{ + //window.open("/apps/${selectedAction.app_id}", "_blank") + }} + > + + + +

+ {( + selectedAction.app_name.charAt(0).toUpperCase() + + selectedAction.app_name.substring(1) + ).replaceAll("_", " ")} +

+
+
+ { + if (workflowExecutions.length > 0) { + // Look for the ID + var found = false; + for (let [key,keyval] in Object.entries(workflowExecutions)) { + if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { + continue; + } + + var foundResult = workflowExecutions[key].results.find( + (result) => result.action.id === selectedAction.id + ) + + if (foundResult === undefined || foundResult === null) { + continue; + } + + const oldstartnode = cy.getElementById(selectedAction.id); + if (oldstartnode !== undefined && oldstartnode !== null) { + const foundname = oldstartnode.data("label") + if (foundname !== undefined && foundname !== null) { + foundResult.action.label = foundname + } + } + + setSelectedResult(foundResult); + if (setCodeModalOpen !== undefined) { + setCodeModalOpen(true); + + found = true + } + + break; + } + + if (!found) { + toast("No result for this action yet. Please run the workflow first.") + } + } + }} + > + + + + + { + setAuthenticationModalOpen(true) + }} + > + + + + + {/* + {}} + > + + + + + + + */} + {/* + { + //setAuthenticationModalOpen(true); + console.log("Should enable/disable magic!") + console.log("Action: ", selectedAction) + if (selectedAction.run_magic_output === undefined) { + selectedAction.run_magic_output = true + } else { + if (selectedAction.run_magic_output === true) { + selectedAction.run_magic_output = false + } else { + selectedAction.run_magic_output = true + } + } + + setSelectedAction(selectedAction) + setUpdate(Math.random()); + }} + > + + + + + */} + {/* + { + }} + > + + + + + + + */} + { + //if (setAiQueryModalOpen !== undefined) { + // setAiQueryModalOpen(true) + //} else { + aiSubmit("Fill based on previous values", undefined, undefined, selectedAction) + //} + setAutocompleting(true) + }} + > + + {autoCompleting ? + + : + + } + + +
+
+
+ {/*selectedAction.id === workflow.start ? null : + + + + + */} + {selectedApp.versions !== null && + selectedApp.versions !== undefined && + selectedApp.versions.length > 1 ? ( + + ) : null} +
+
+
+
+ Name + { + // Copy the name value + const name = e.target.value + const parsedBaseLabel = "$"+baselabel.toLowerCase().replaceAll(" ", "_") + const newname = "$"+name.toLowerCase().replaceAll(" ", "_") + + // Check if it's the same as the current name in use + //if (name === selectedAction.label) { + // console.log("Returning from name thing") + // return + //} + + // Change in actions, triggers & conditions + // Highlight the changes somehow with a glow? + if (workflow.branches !== undefined && workflow.branches !== null) { + for (let [key,keyval] in Object.entries(workflow.branches)) { + if (workflow.branches[key].conditions !== undefined && workflow.branches[key].conditions !== null) { + for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) { + const condition = workflow.branches[key].conditions[subkey] + const sourceparam = condition.source + const destinationparam = condition.destination + + // Should have a smarter way of discovering node names + // Finding index(es) and replacing at the location + if (sourceparam.value.includes("$")) { + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (sourceparam.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value) + const extralength = newname.length-parsedBaseLabel.length + sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length) + + console.log("New: ", workflow.branches[key].conditions[subkey].source.value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + + if (destinationparam.value.includes("$")) { + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (destinationparam.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value) + const extralength = newname.length-parsedBaseLabel.length + destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length) + + console.log("New: ", workflow.branches[key].conditions[subkey].destination.value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + } + } + } + } + + for (let [key,keyval] in Object.entries(workflow.actions)) { + if (workflow.actions[key].id === selectedAction.id) { + continue + } + + const params = workflow.actions[key].parameters + console.log(params) + if (params === null || params === undefined) { + continue + } + + for (let [subkey, subkeyval] in Object.entries(params)) { + const param = workflow.actions[key].parameters[subkey]; + if (!param.value.includes("$")) { + continue + } + + // Should have a smarter way of discovering node names + // Do regex? + // Finding index(es) and replacing at the location + // + + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = param.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (param.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = param.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.actions[key].parameters[subkey].value) + const extralength = newname.length-parsedBaseLabel.length + param.value = param.value.substring(0, foundindex) + newname + param.value.substring(foundindex-extralength+newname.length, param.value.length) + + console.log("New: ", workflow.actions[key].parameters[subkey].value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + } + + setWorkflow(workflow); + setUpdate(Math.random()); + baselabel = name + }} + /> +
+ {/*!isCloud ? null :*/} +
+ + + Delay + { + if (actionDelayChange !== undefined) { + actionDelayChange(event) + } + }} + /> + + +
+ {/**/} +
+
+ )} + {selectedApp.name !== undefined && + selectedAction.authentication !== null && + selectedAction.authentication !== undefined && + selectedAction.authentication.length === 0 && + requiresAuthentication ? ( +
+ + + + + +
+ ) : null} + + {selectedAction.authentication !== undefined && + selectedAction.authentication !== null && + selectedAction.authentication.length > 0 ? ( +
+ Authentication +
+ + + {/* + + + curaction.authentication = authenticationOptions + if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") + */} + + { + setAuthenticationModalOpen(true); + }} + > + + + +
+
+ ) : null} + + {showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? ( +
+ Environment + +
+ ) : null} + + {workflow.execution_variables !== undefined && + workflow.execution_variables !== null && + workflow.execution_variables.length > 0 ? ( +
+ Execution variable (optional) + +
+ ) : null} + + +
+ {/*hideExtraTypes ? null : +
+ Actions +
+ */} + + {setNewSelectedAction !== undefined ? ( + { + // Most popular + // Is categorized + // Uncategorized + return option.category_label !== undefined && option.category_label !== null && option.category_label.length > 0 ? "Most used" : "All Actions"; + }} + renderGroup={(params) => { + + return ( +
  • + {params.group} + {params.children} +
  • + ) + }} + options={renderedActionOptions} + ListboxProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + }, + }} + filterOptions={(options, { inputValue }) => { + //console.log("Option contains?: ", inputValue, options) + const lowercaseValue = inputValue.toLowerCase() + options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue)) + + return options + }} + getOptionLabel={(option) => { + if (option === undefined || option === null || option.name === undefined || option.name === null ) { + return null; + } + + const newname = ( + option.name.charAt(0).toUpperCase() + option.name.substring(1) + ).replaceAll("_", " "); + + return newname; + }} + fullWidth + style={{ + backgroundColor: theme.palette.inputColor, + height: 50, + borderRadius: theme.palette.borderRadius, + }} + onChange={(event, newValue) => { + // Workaround with event lol + if (newValue !== undefined && newValue !== null) { + setNewSelectedAction({ + target: { + value: newValue.name + } + }); + } + }} + renderOption={(props, data, state) => { + var newActionname = data.name; + if (data.label !== undefined && data.label !== null && data.label.length > 0) { + newActionname = data.label; + } + + var newActiondescription = data.description; + //console.log("DESC: ", newActiondescription) + if (data.description === undefined || data.description === null) { + newActiondescription = "Description: No description defined for this action" + } else { + newActiondescription = "Description: "+newActiondescription + } + + const iconInfo = GetIconInfo({ name: data.name }); + const useIcon = iconInfo.originalIcon; + + if (newActionname === undefined || newActionname === null) { + newActionname = "No name" + data.name = "No name" + data.label = "No name" + } + + newActionname = (newActionname.charAt(0).toUpperCase() + newActionname.substring(1)).replaceAll("_", " "); + + var method = "" + var extraDescription = "" + if (data.name.includes("get_")) { + method = "GET" + } else if (data.name.includes("post_")) { + method = "POST" + } else if (data.name.includes("put_")) { + method = "PUT" + } else if (data.name.includes("patch_")) { + method = "PATCH" + } else if (data.name.includes("delete_")) { + method = "DELETE" + } else if (data.name.includes("options_")) { + method = "OPTIONS" + } else if (data.name.includes("connect_")) { + method = "CONNECT" + } + + // FIXME: Should it require a base URL? + if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) { + var extraUrl = "" + const descSplit = data.description.split("\n") + // Last line of descSplit + if (descSplit.length > 0) { + extraUrl = descSplit[descSplit.length-1] + } + + //for (let [line,lineval] in Object.entries(descSplit)) { + // if (descSplit[line].includes("http") && descSplit[line].includes("://")) { + // const urlsplit = descSplit[line].split("/") + // try { + // extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/") + // } catch (e) { + // //console.log("Failed - running with -1") + // extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/") + // } + + + // //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line]) + // //break + // } + //} + + if (extraUrl.length > 0) { + if (extraUrl.includes(" ")) { + extraUrl = extraUrl.split(" ")[0] + } + + if (extraUrl.includes("#")) { + extraUrl = extraUrl.split("#")[0] + } + + extraDescription = `${method} ${extraUrl}` + } else { + //console.log("No url found. Check again :)") + } + } + + return ( + + ); + }} + renderInput={(params) => { + if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) { + const prefixes = ["Post", "Put", "Patch"] + for (let [key,keyval] in Object.entries(prefixes)) { + if (params.inputProps.value.startsWith(prefixes[key])) { + params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1) + if (params.inputProps.value.length > 1) { + params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1) + } + break + } + } + + // Check if it starts with "Get List" and method is "Get" + if (params.inputProps.value.startsWith("Get List")) { + console.log("Get List") + } + } + + return ( + + ); + }} + /> + ) : null} + + {/*setNewSelectedAction !== undefined ? + + : null*/} + +
    { + Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0 ? +
    {isIntegration ? apps !== undefined && apps !== null && apps.length > 0 ?
    @@ -2984,1247 +4171,9 @@ const ParsedAction = (props) => { ); })}
    - ); - } - return null; - }; - - - const ActionSelectOption = (actionprops) => { - const { data, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops; - const [hover, setHover] = React.useState(false); - - return ( - -
    setHover(true)} onMouseLeave={() => setHover(false)} - onClick={() => { - //setSelectedAction(actionprops) - //setShowActionList(false) - //setUpdate(Math.random()) - // - if (data !== undefined && data !== null) { - setNewSelectedAction({ - target: { - value: data.name - } - }); - } - }} - > -
    - - {useIcon} - - {newActionname} -
    - {extraDescription.length > 0 ? - - {extraDescription} - - : null} -
    -
    - ) - } - - const sortByCategoryLabel = (a, b) => { - const aHasCategoryLabel = a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0 - const bHasCategoryLabel = b.category_label !== undefined && b.category_label !== null && b.category_label.length > 0 - - // Sort by existence and length of "category_label" - if (aHasCategoryLabel && !bHasCategoryLabel) { - return -1 - } else if (!aHasCategoryLabel && bHasCategoryLabel) { - return 1 - } else { - return 0 - } - } - - // Function to deduplicate based on the "name" field - const deduplicateByName = (array) => { - const uniqueNames = {}; - return array.filter(item => { - if (!item.hasOwnProperty('name') || !item.name.length) { - return true + : null } - if (!uniqueNames[item.name]) { - uniqueNames[item.name] = true - return true - } - return false - }) - } - - // Gets the most important actions first - const renderedActionOptions = deduplicateByName(( - selectedApp.actions === undefined || selectedApp.actions === null ? [] : - selectedApp.actions.filter((a) => - a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0).concat(sortByKey(selectedApp.actions, "label")) - ).sort(sortByCategoryLabel)) - - const selectedAppIcon = selectedAction.large_image - var baselabel = selectedAction.label - return ( -
    - - {hideExtraTypes === true ? null : ( - -
    -
    -
    { - //window.open("/apps/${selectedAction.app_id}", "_blank") - }} - > - - - -

    - {( - selectedAction.app_name.charAt(0).toUpperCase() + - selectedAction.app_name.substring(1) - ).replaceAll("_", " ")} -

    -
    -
    - { - if (workflowExecutions.length > 0) { - // Look for the ID - var found = false; - for (let [key,keyval] in Object.entries(workflowExecutions)) { - if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { - continue; - } - - var foundResult = workflowExecutions[key].results.find( - (result) => result.action.id === selectedAction.id - ) - - if (foundResult === undefined || foundResult === null) { - continue; - } - - const oldstartnode = cy.getElementById(selectedAction.id); - if (oldstartnode !== undefined && oldstartnode !== null) { - const foundname = oldstartnode.data("label") - if (foundname !== undefined && foundname !== null) { - foundResult.action.label = foundname - } - } - - setSelectedResult(foundResult); - if (setCodeModalOpen !== undefined) { - setCodeModalOpen(true); - - found = true - } - - break; - } - - if (!found) { - toast.info("No result for this action yet. Please run the workflow first.") - } - } else { - toast.info("No workflow runs to search through. Run the workflow first.") - } - }} - > - - - - - { - setAuthenticationModalOpen(true) - }} - > - - - - - {/* - {}} - > - - - - - - - */} - {/* - { - //setAuthenticationModalOpen(true); - console.log("Should enable/disable magic!") - console.log("Action: ", selectedAction) - if (selectedAction.run_magic_output === undefined) { - selectedAction.run_magic_output = true - } else { - if (selectedAction.run_magic_output === true) { - selectedAction.run_magic_output = false - } else { - selectedAction.run_magic_output = true - } - } - - setSelectedAction(selectedAction) - setUpdate(Math.random()); - }} - > - - - - - */} - {/* - { - }} - > - - - - - - - */} - { - //if (setAiQueryModalOpen !== undefined) { - // setAiQueryModalOpen(true) - //} else { - aiSubmit("Fill based on previous values", undefined, undefined, selectedAction) - //} - setAutocompleting(true) - }} - > - - {autoCompleting ? - - : - - } - - -
    -
    -
    - {/*selectedAction.id === workflow.start ? null : - - - - - */} - {selectedApp.versions !== null && - selectedApp.versions !== undefined && - selectedApp.versions.length > 1 ? ( - - ) : null} -
    -
    -
    -
    - Name - { - // Copy the name value - const name = e.target.value - const parsedBaseLabel = "$"+baselabel.toLowerCase().replaceAll(" ", "_") - const newname = "$"+name.toLowerCase().replaceAll(" ", "_") - - // Check if it's the same as the current name in use - //if (name === selectedAction.label) { - // console.log("Returning from name thing") - // return - //} - - // Change in actions, triggers & conditions - // Highlight the changes somehow with a glow? - if (workflow.branches !== undefined && workflow.branches !== null) { - for (let [key,keyval] in Object.entries(workflow.branches)) { - if (workflow.branches[key].conditions !== undefined && workflow.branches[key].conditions !== null) { - for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) { - const condition = workflow.branches[key].conditions[subkey] - const sourceparam = condition.source - const destinationparam = condition.destination - - // Should have a smarter way of discovering node names - // Finding index(es) and replacing at the location - if (sourceparam.value.includes("$")) { - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (sourceparam.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } - - console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value) - const extralength = newname.length-parsedBaseLabel.length - sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length) - - console.log("New: ", workflow.branches[key].conditions[subkey].source.value) - } else { - break - } - - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } - - } - } catch (e) { - console.log("Failed value replacement based on index: ", e) - } - } - - if (destinationparam.value.includes("$")) { - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (destinationparam.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } - - console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value) - const extralength = newname.length-parsedBaseLabel.length - destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length) - - console.log("New: ", workflow.branches[key].conditions[subkey].destination.value) - } else { - break - } - - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } - - } - } catch (e) { - console.log("Failed value replacement based on index: ", e) - } - } - } - } - } - } - - for (let [key,keyval] in Object.entries(workflow.actions)) { - if (workflow.actions[key].id === selectedAction.id) { - continue - } - - const params = workflow.actions[key].parameters - console.log(params) - if (params === null || params === undefined) { - continue - } - - for (let [subkey, subkeyval] in Object.entries(params)) { - const param = workflow.actions[key].parameters[subkey]; - if (!param.value.includes("$")) { - continue - } - - // Should have a smarter way of discovering node names - // Do regex? - // Finding index(es) and replacing at the location - // - - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = param.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (param.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = param.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } - - console.log("Old found: ", workflow.actions[key].parameters[subkey].value) - const extralength = newname.length-parsedBaseLabel.length - param.value = param.value.substring(0, foundindex) + newname + param.value.substring(foundindex-extralength+newname.length, param.value.length) - - console.log("New: ", workflow.actions[key].parameters[subkey].value) - } else { - break - } - - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } - - } - } catch (e) { - console.log("Failed value replacement based on index: ", e) - } - } - } - - setWorkflow(workflow); - setUpdate(Math.random()); - baselabel = name - }} - /> -
    - {/*!isCloud ? null :*/} -
    - - - Delay - { - if (actionDelayChange !== undefined) { - actionDelayChange(event) - } - }} - /> - - -
    - {/**/} -
    -
    - )} - {selectedApp.name !== undefined && - selectedAction.authentication !== null && - selectedAction.authentication !== undefined && - selectedAction.authentication.length === 0 && - requiresAuthentication ? ( -
    - - - - - -
    - ) : null} - - {selectedAction.authentication !== undefined && - selectedAction.authentication !== null && - selectedAction.authentication.length > 0 ? ( -
    - Authentication -
    - - - {/* - - - curaction.authentication = authenticationOptions - if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") - */} - - { - setAuthenticationModalOpen(true); - }} - > - - - -
    -
    - ) : null} - - - {showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? ( -
    - Environment - -
    - ) : null} - - {workflow.execution_variables !== undefined && - workflow.execution_variables !== null && - workflow.execution_variables.length > 0 ? ( -
    - Execution variable (optional) - -
    - ) : null} - - -
    - {/*hideExtraTypes ? null : -
    - Actions -
    - */} - - {setNewSelectedAction !== undefined ? ( - { - // Most popular - // Is categorized - // Uncategorized - return option.category_label !== undefined && option.category_label !== null && option.category_label.length > 0 ? "Most used" : "All Actions"; - }} - renderGroup={(params) => { - - return ( -
  • - {params.group} - {params.children} -
  • - ) - }} - options={renderedActionOptions} - ListboxProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - }, - }} - filterOptions={(options, { inputValue }) => { - //console.log("Option contains?: ", inputValue, options) - const lowercaseValue = inputValue.toLowerCase() - options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue)) - - return options - }} - getOptionLabel={(option) => { - if (option === undefined || option === null || option.name === undefined || option.name === null ) { - return null; - } - - const newname = ( - option.name.charAt(0).toUpperCase() + option.name.substring(1) - ).replaceAll("_", " "); - - return newname; - }} - fullWidth - style={{ - backgroundColor: theme.palette.inputColor, - height: 50, - borderRadius: theme.palette.borderRadius, - }} - onChange={(event, newValue) => { - // Workaround with event lol - if (newValue !== undefined && newValue !== null) { - setNewSelectedAction({ - target: { - value: newValue.name - } - }); - } - }} - renderOption={(props, data, state) => { - var newActionname = data.name; - if (data.label !== undefined && data.label !== null && data.label.length > 0) { - newActionname = data.label; - } - - var newActiondescription = data.description; - //console.log("DESC: ", newActiondescription) - if (data.description === undefined || data.description === null) { - newActiondescription = "Description: No description defined for this action" - } else { - newActiondescription = "Description: "+newActiondescription - } - - const iconInfo = GetIconInfo({ name: data.name }); - const useIcon = iconInfo.originalIcon; - - if (newActionname === undefined || newActionname === null) { - newActionname = "No name" - data.name = "No name" - data.label = "No name" - } - - newActionname = (newActionname.charAt(0).toUpperCase() + newActionname.substring(1)).replaceAll("_", " "); - - var method = "" - var extraDescription = "" - if (data.name.includes("get_")) { - method = "GET" - } else if (data.name.includes("post_")) { - method = "POST" - } else if (data.name.includes("put_")) { - method = "PUT" - } else if (data.name.includes("patch_")) { - method = "PATCH" - } else if (data.name.includes("delete_")) { - method = "DELETE" - } else if (data.name.includes("options_")) { - method = "OPTIONS" - } else if (data.name.includes("connect_")) { - method = "CONNECT" - } - - // FIXME: Should it require a base URL? - if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) { - var extraUrl = "" - const descSplit = data.description.split("\n") - // Last line of descSplit - if (descSplit.length > 0) { - extraUrl = descSplit[descSplit.length-1] - } - - //for (let [line,lineval] in Object.entries(descSplit)) { - // if (descSplit[line].includes("http") && descSplit[line].includes("://")) { - // const urlsplit = descSplit[line].split("/") - // try { - // extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/") - // } catch (e) { - // //console.log("Failed - running with -1") - // extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/") - // } - - - // //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line]) - // //break - // } - //} - - if (extraUrl.length > 0) { - if (extraUrl.includes(" ")) { - extraUrl = extraUrl.split(" ")[0] - } - - if (extraUrl.includes("#")) { - extraUrl = extraUrl.split("#")[0] - } - - extraDescription = `${method} ${extraUrl}` - } else { - //console.log("No url found. Check again :)") - } - } - - return ( - - ); - }} - renderInput={(params) => { - if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) { - const prefixes = ["Post", "Put", "Patch"] - for (let [key,keyval] in Object.entries(prefixes)) { - if (params.inputProps.value.startsWith(prefixes[key])) { - params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1) - if (params.inputProps.value.length > 1) { - params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1) - } - break - } - } - - // Check if it starts with "Get List" and method is "Get" - if (params.inputProps.value.startsWith("Get List")) { - console.log("Get List") - } - } - - return ( - - ); - }} - /> - ) : null} - - {/*setNewSelectedAction !== undefined ? - - : null*/} - -
    - +
    diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 021cc0d6..cfd65edd 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -4225,6 +4225,283 @@ const AngularWorkflow = (defaultprops) => { } }) + // Should get AI autocompletes + const aiSubmit = (value, setResponseMsg, setSuggestionLoading, inputAction) => { + if (setResponseMsg !== undefined) { + setResponseMsg("") + } + + if (value === undefined || value === "") { + console.log("No value input!") + return + } + + if (setSuggestionLoading !== undefined) { + setSuggestionLoading(true) + } + + console.log("Submit conversation with value: ", value); + + // This is to find sample response and parse it as string + + var AppContext = [] + if (inputAction !== undefined && inputAction !== null) { + const parents = getParents(inputAction) + + console.log("Parents: ", parents) + var actionlist = [] + if (parents.length > 1) { + for (let [key,keyval] in Object.entries(parents)) { + const item = parents[key]; + if (item.label === "Execution Argument") { + continue; + } + + var exampledata = item.example === undefined || item.example === null ? "" : item.example; + // Find previous execution and their variables + //exampledata === "" && + if (workflowExecutions.length > 0) { + // Look for the ID + const found = false; + for (let [key,keyval] in Object.entries(workflowExecutions)) { + if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { + continue; + } + + var foundResult = workflowExecutions[key].results.find((result) => result.action.id === item.id); + if (foundResult === undefined || foundResult === null) { + continue; + } + + if (foundResult.result !== undefined && foundResult.result !== null) { + foundResult = foundResult.result + } + + const valid = validateJson(foundResult, true) + if (valid.valid) { + if (valid.result.success === false) { + //console.log("Skipping success false autocomplete") + } else { + exampledata = valid.result; + break; + } + } else { + exampledata = foundResult; + } + } + } + + // 1. Take + const itemlabelComplete = item.label === null || item.label === undefined ? "" : item.label.split(" ").join("_"); + + const actionvalue = { + app_name: item.app_name, + action_name: item.name, + label: item.label, + + type: "action", + id: item.id, + name: item.label, + autocomplete: itemlabelComplete, + example: exampledata, + }; + + actionlist.push(actionvalue); + } + } + + var fixedResults = [] + for (var i = 0; i < actionlist.length; i++) { + const item = actionlist[i]; + const responseFix = SetJsonDotnotation(item.example, "") + + // Check if json + const validated = validateJson(responseFix) + var exampledata = responseFix; + if (validated.valid) { + exampledata = JSON.stringify(validated.result) + } + + AppContext.push({ + "app_name": item.app_name, + "action_name": item.action_name, + "label": item.label, + "example": exampledata, + "example_response": exampledata, + }) + } + } + + var conversationData = { + "query": value, + "output_format": "action", + "app_context": AppContext, + + "workflow_id": workflow.id, + } + + if (inputAction !== undefined) { + console.log("Add app context! This should them get parameters directly") + conversationData.output_format = "action_parameters" + + conversationData.app_id = inputAction.app_id + conversationData.app_name = inputAction.app_name + conversationData.action_name = inputAction.name + conversationData.parameters = inputAction.parameters + + if (!value.includes(inputAction.label)) { + conversationData.query = inputAction.label.replaceAll("_", " ") + } + } + + // Onprem not available yet (April 2023) + // Should: Make OpenAI work for them with their own key + //fetch("https://shuffler.io/api/v1/conversation", { + fetch(`${globalUrl}/api/v1/conversation`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(conversationData), + credentials: "include", + }) + .then((response) => { + if (setSuggestionLoading !== undefined) { + setSuggestionLoading(false) + } + + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + console.log("Conversation response: ", responseJson) + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + if (setResponseMsg !== undefined) { + setResponseMsg(responseJson.reason) + } + } + + return + } + + if (inputAction !== undefined) { + console.log("In input action! Should check params if they match, and add suggestions") + + if (responseJson.parameters === undefined || responseJson.parameters.length === 0) { + return + } + + var changed = false + + for (let paramkey in inputAction.parameters) { + const actionParam = inputAction.parameters[paramkey] + + if (actionParam.autocompleted === true) { + continue + } + + if (actionParam.configuration === true && actionParam.name !== "url") { + continue + } + + if (actionParam.value !== "" && actionParam.value !== actionParam.example) { + console.log("Skipping: ", actionParam) + continue + } + + for (let respParam of responseJson.parameters) { + if (respParam.name === actionParam.name) { + console.log("Found match for param: ", respParam) + + if (respParam.value === "") { + break + } + + changed = true + + inputAction.parameters[paramkey].autocompleted = true + inputAction.parameters[paramkey].value = respParam.value + break + } + } + } + + if (changed === true) { + console.log("Setting action! Force update pls :)") + setUpdate(Math.random()) + setSelectedAction(inputAction) + } + + return + } + + console.log("Suggestionbox location: ", suggestionBox) + + // Add action + if (responseJson.app_name !== undefined && responseJson.app_name !== null) { + // Always added to 0, 0 + // Should use suggestionBox.position.x, suggestionBox.position.y + var newitem = { + "data": responseJson, + "position": { + "x": suggestionBox.node_position.x !== undefined ? suggestionBox.node_position.x : 0, + "y": suggestionBox.node_position.y !== undefined ? suggestionBox.node_position.y + 100 : 0, + }, + "group": "nodes", + } + + newitem.type = "ACTION" + newitem.isStartNode = false + newitem.data.id = uuidv4() + newitem.data.type = "ACTION" + newitem.data.isStartNode = false + + newitem.data.is_valid = true + newitem.data.isValid = true + + cy.add({ + group: newitem.group, + data: newitem.data, + position: newitem.position, + }); + + // Add edge + const newId = uuidv4() + cy.add({ + group: "edges", + data: { + id: newId, + _id: newId, + source: suggestionBox.attachedTo, + target: newitem.data.id, + } + }) + //label: "Generated", + + setSuggestionBox({ + "position": { + "top": 500, + "left": 500, + }, + "open": false, + "attachedTo": "", + }); + } + }) + .catch((error) => { + if (setSuggestionLoading !== undefined) { + setSuggestionLoading(false) + } + + console.log("Conv response error: ", error); + }); + } + // Nodeselectbatching: // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once // onNodeClick @@ -13647,7 +13924,48 @@ const AngularWorkflow = (defaultprops) => { workflow.triggers[selectedTriggerIndex].parameters.length > 2 ? workflow.triggers[selectedTriggerIndex].parameters[2].value : ""; - } + }else if( + selectedTrigger.trigger_type === "USERINPUT" + ){ + if ( + workflow.triggers[selectedTriggerIndex].parameters === undefined || + workflow.triggers[selectedTriggerIndex].parameters === null || + workflow.triggers[selectedTriggerIndex].parameters.length === 0 + ) { + workflow.triggers[selectedTriggerIndex].parameters = []; + workflow.triggers[selectedTriggerIndex].parameters[0] = { + name: "alertinfo", + value: "Do you want to continue the workflow? Start parameters: $exec", + }; + + // boolean, + workflow.triggers[selectedTriggerIndex].parameters[1] = { + name: "options", + value: "boolean", + }; + + // email,sms,app ... + workflow.triggers[selectedTriggerIndex].parameters[2] = { + name: "type", + value: "subflow", + }; + + workflow.triggers[selectedTriggerIndex].parameters[3] = { + name: "email", + value: "test@test.com", + }; + workflow.triggers[selectedTriggerIndex].parameters[4] = { + name: "sms", + value: "0000000", + }; + workflow.triggers[selectedTriggerIndex].parameters[5] = { + name: "subflow", + value: "", + }; + + setWorkflow(workflow); + } + } } const WebhookSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "WEBHOOK" ? null : @@ -14442,48 +14760,7 @@ const AngularWorkflow = (defaultprops) => { }) } - const UserinputSidebar = () => { - if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers[selectedTriggerIndex] !== undefined) { - if ( - workflow.triggers[selectedTriggerIndex].parameters === undefined || - workflow.triggers[selectedTriggerIndex].parameters === null || - workflow.triggers[selectedTriggerIndex].parameters.length === 0 - ) { - workflow.triggers[selectedTriggerIndex].parameters = []; - workflow.triggers[selectedTriggerIndex].parameters[0] = { - name: "alertinfo", - value: "Do you want to continue the workflow? Start parameters: $exec", - }; - - // boolean, - workflow.triggers[selectedTriggerIndex].parameters[1] = { - name: "options", - value: "boolean", - }; - - // email,sms,app ... - workflow.triggers[selectedTriggerIndex].parameters[2] = { - name: "type", - value: "subflow", - }; - - workflow.triggers[selectedTriggerIndex].parameters[3] = { - name: "email", - value: "test@test.com", - }; - workflow.triggers[selectedTriggerIndex].parameters[4] = { - name: "sms", - value: "0000000", - }; - workflow.triggers[selectedTriggerIndex].parameters[5] = { - name: "subflow", - value: "", - }; - - setWorkflow(workflow); - } - - return ( + const UserinputSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "USERINPUT" ? null :

    {selectedTrigger.app_name} @@ -14820,11 +15097,6 @@ const AngularWorkflow = (defaultprops) => {

    - ) - } - - return null - } const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "SCHEDULE" ? null :
    @@ -16677,115 +16949,17 @@ const AngularWorkflow = (defaultprops) => { }; const RightSideBar = (props) => { - const { - //workflow, - //setWorkflow, - //setSelectedAction, - //setUpdate, - //selectedApp, - //workflowExecutions, - //setSelectedResult, - //selectedAction, - //setSelectedApp, - //setSelectedTrigger, - //setSelectedEdge, - //setCurrentView, - //cy, - //setAuthenticationModalOpen, - //setVariablesModalOpen, - //setCodeModalOpen, - //selectedNameChange, - //rightsidebarStyle, - //showEnvironment, - //selectedActionEnvironment, - //environments, - //setNewSelectedAction, - //appApiViewStyle, - //globalUrl, - //setSelectedActionEnvironment, - //requiresAuthentication, - //scrollConfig, - //setScrollConfig, - } = props; - if (!rightSideBarOpen) { - return null; - } var defaultReturn = null - if (Object.getOwnPropertyNames(selectedAction).length > 0) { - if (Object.getOwnPropertyNames(selectedAction).length === 0) { - return null; - } - - defaultReturn = - - } else if (Object.getOwnPropertyNames(selectedComment).length > 0) { + + if (Object.getOwnPropertyNames(selectedComment).length > 0) { defaultReturn = } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { - if (selectedTrigger.trigger_type === "SCHEDULE") { - // Handled elsewhere as an experiment - defaultReturn = null - } else if (selectedTrigger.trigger_type === "WEBHOOK") { - defaultReturn = null - } else if (selectedTrigger.trigger_type === "SUBFLOW") { + if (selectedTrigger.trigger_type === "SUBFLOW") { defaultReturn = } else if (selectedTrigger.trigger_type === "EMAIL") { defaultReturn = - } else if (selectedTrigger.trigger_type === "USERINPUT") { - defaultReturn = } else if (selectedTrigger.trigger_type === undefined) { //defaultReturn = return null; @@ -19646,12 +19820,68 @@ const AngularWorkflow = (defaultprops) => {
    {executionModal} - - + + { + rightSideBarOpen && Object.getOwnPropertyNames(selectedAction).length > 0 ? +
    + +
    : null + } {/* Looks for triggers" */} {/* Only fixed the ones that require scrolling on a small screen */} {/* Most important: Actions. But these are a lot more complex */} - {rightSideBarOpen && (selectedTrigger.trigger_type === "SCHEDULE" || selectedTrigger.trigger_type === "WEBHOOK" || selectedTrigger.trigger_type === "PIPELINE") ? + {rightSideBarOpen && (selectedTrigger.trigger_type === "SCHEDULE" || selectedTrigger.trigger_type === "WEBHOOK" || selectedTrigger.trigger_type === "PIPELINE" || selectedTrigger.trigger_type === "USERINPUT") ?
    {Object.getOwnPropertyNames(selectedTrigger).length > 0 ? selectedTrigger.trigger_type === "SCHEDULE" ? @@ -19660,6 +19890,8 @@ const AngularWorkflow = (defaultprops) => { PipelineSidebar : selectedTrigger.trigger_type === "WEBHOOK" ? WebhookSidebar + : selectedTrigger.trigger_type === "USERINPUT" ? + UserinputSidebar : null : null}
    @@ -20914,282 +21146,7 @@ const AngularWorkflow = (defaultprops) => { ) : null; - // Should get AI autocompletes - const aiSubmit = (value, setResponseMsg, setSuggestionLoading, inputAction) => { - if (setResponseMsg !== undefined) { - setResponseMsg("") - } - if (value === undefined || value === "") { - console.log("No value input!") - return - } - - if (setSuggestionLoading !== undefined) { - setSuggestionLoading(true) - } - - console.log("Submit conversation with value: ", value); - - // This is to find sample response and parse it as string - - var AppContext = [] - if (inputAction !== undefined && inputAction !== null) { - const parents = getParents(inputAction) - - console.log("Parents: ", parents) - var actionlist = [] - if (parents.length > 1) { - for (let [key,keyval] in Object.entries(parents)) { - const item = parents[key]; - if (item.label === "Execution Argument") { - continue; - } - - var exampledata = item.example === undefined || item.example === null ? "" : item.example; - // Find previous execution and their variables - //exampledata === "" && - if (workflowExecutions.length > 0) { - // Look for the ID - const found = false; - for (let [key,keyval] in Object.entries(workflowExecutions)) { - if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { - continue; - } - - var foundResult = workflowExecutions[key].results.find((result) => result.action.id === item.id); - if (foundResult === undefined || foundResult === null) { - continue; - } - - if (foundResult.result !== undefined && foundResult.result !== null) { - foundResult = foundResult.result - } - - const valid = validateJson(foundResult, true) - if (valid.valid) { - if (valid.result.success === false) { - //console.log("Skipping success false autocomplete") - } else { - exampledata = valid.result; - break; - } - } else { - exampledata = foundResult; - } - } - } - - // 1. Take - const itemlabelComplete = item.label === null || item.label === undefined ? "" : item.label.split(" ").join("_"); - - const actionvalue = { - app_name: item.app_name, - action_name: item.name, - label: item.label, - - type: "action", - id: item.id, - name: item.label, - autocomplete: itemlabelComplete, - example: exampledata, - }; - - actionlist.push(actionvalue); - } - } - - var fixedResults = [] - for (var i = 0; i < actionlist.length; i++) { - const item = actionlist[i]; - const responseFix = SetJsonDotnotation(item.example, "") - - // Check if json - const validated = validateJson(responseFix) - var exampledata = responseFix; - if (validated.valid) { - exampledata = JSON.stringify(validated.result) - } - - AppContext.push({ - "app_name": item.app_name, - "action_name": item.action_name, - "label": item.label, - "example": exampledata, - "example_response": exampledata, - }) - } - } - - var conversationData = { - "query": value, - "output_format": "action", - "app_context": AppContext, - - "workflow_id": workflow.id, - } - - if (inputAction !== undefined) { - console.log("Add app context! This should them get parameters directly") - conversationData.output_format = "action_parameters" - - conversationData.app_id = inputAction.app_id - conversationData.app_name = inputAction.app_name - conversationData.action_name = inputAction.name - conversationData.parameters = inputAction.parameters - - if (!value.includes(inputAction.label)) { - conversationData.query = inputAction.label.replaceAll("_", " ") - } - } - - // Onprem not available yet (April 2023) - // Should: Make OpenAI work for them with their own key - //fetch("https://shuffler.io/api/v1/conversation", { - fetch(`${globalUrl}/api/v1/conversation`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(conversationData), - credentials: "include", - }) - .then((response) => { - if (setSuggestionLoading !== undefined) { - setSuggestionLoading(false) - } - - if (response.status !== 200) { - console.log("Status not 200 for stream results :O!"); - } - - return response.json(); - }) - .then((responseJson) => { - console.log("Conversation response: ", responseJson) - if (responseJson.success === false) { - if (responseJson.reason !== undefined) { - if (setResponseMsg !== undefined) { - setResponseMsg(responseJson.reason) - } - } - - return - } - - if (inputAction !== undefined) { - console.log("In input action! Should check params if they match, and add suggestions") - - if (responseJson.parameters === undefined || responseJson.parameters.length === 0) { - return - } - - var changed = false - - for (let paramkey in inputAction.parameters) { - const actionParam = inputAction.parameters[paramkey] - - if (actionParam.autocompleted === true) { - continue - } - - if (actionParam.configuration === true && actionParam.name !== "url") { - continue - } - - if (actionParam.value !== "" && actionParam.value !== actionParam.example) { - console.log("Skipping: ", actionParam) - continue - } - - for (let respParam of responseJson.parameters) { - if (respParam.name === actionParam.name) { - console.log("Found match for param: ", respParam) - - if (respParam.value === "") { - break - } - - changed = true - - inputAction.parameters[paramkey].autocompleted = true - inputAction.parameters[paramkey].value = respParam.value - break - } - } - } - - if (changed === true) { - console.log("Setting action! Force update pls :)") - setUpdate(Math.random()) - setSelectedAction(inputAction) - } - - return - } - - console.log("Suggestionbox location: ", suggestionBox) - - // Add action - if (responseJson.app_name !== undefined && responseJson.app_name !== null) { - // Always added to 0, 0 - // Should use suggestionBox.position.x, suggestionBox.position.y - var newitem = { - "data": responseJson, - "position": { - "x": suggestionBox.node_position.x !== undefined ? suggestionBox.node_position.x : 0, - "y": suggestionBox.node_position.y !== undefined ? suggestionBox.node_position.y + 100 : 0, - }, - "group": "nodes", - } - - newitem.type = "ACTION" - newitem.isStartNode = false - newitem.data.id = uuidv4() - newitem.data.type = "ACTION" - newitem.data.isStartNode = false - - newitem.data.is_valid = true - newitem.data.isValid = true - - cy.add({ - group: newitem.group, - data: newitem.data, - position: newitem.position, - }); - - // Add edge - const newId = uuidv4() - cy.add({ - group: "edges", - data: { - id: newId, - _id: newId, - source: suggestionBox.attachedTo, - target: newitem.data.id, - } - }) - //label: "Generated", - - setSuggestionBox({ - "position": { - "top": 500, - "left": 500, - }, - "open": false, - "attachedTo": "", - }); - } - }) - .catch((error) => { - if (setSuggestionLoading !== undefined) { - setSuggestionLoading(false) - } - - console.log("Conv response error: ", error); - }); - } const SuggestionBoxUi = () => { const [suggestionValue, setSuggestionValue] = useState(""); From 3600502f94197afe3b3a828f9d81715a44f55039 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 30 May 2024 00:46:58 +0530 Subject: [PATCH 08/46] Fixed the action parameter bug --- frontend/src/components/ParsedAction.jsx | 12 ++++++------ frontend/src/views/AngularWorkflow.jsx | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index c90e7747..937b0557 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -381,13 +381,12 @@ const ParsedAction = (props) => { useEffect(() => { - if (selectedActionParameters !== undefined && selectedActionParameters !== null && selectedActionParameters.length === 0 + if (selectedActionParameters !== undefined && selectedActionParameters !== null ) { if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { setSelectedActionParameters(selectedAction.parameters); } } - if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) { // FIXME - this is the bad thing @@ -613,7 +612,8 @@ const ParsedAction = (props) => { setActionlist(actionlist); } } - }); + }, + [selectedAction, selectedVariableParameter, workflowExecutions, listCache]); const calculateHelpertext = (input_data) => { @@ -1307,6 +1307,8 @@ const ParsedAction = (props) => { const selectedAppIcon = selectedAction.large_image + console.log("Selected action: ", selectedAction) + console.log("Selected action paramaters: ", selectedAction.parameters) var baselabel = selectedAction.label return (
    @@ -2075,7 +2077,6 @@ const ParsedAction = (props) => {
    ) : null} - {workflow.execution_variables !== undefined && workflow.execution_variables !== null && workflow.execution_variables.length > 0 ? ( @@ -2395,7 +2396,6 @@ const ParsedAction = (props) => { })} : null*/} -
    { placeholder = data.example; - if (data.name === "url" && data.value !== undefined && data.value !== null && data.value.length === 0) { + if (data.name === "url" && data.value !== undefined && data.value !== null && data.value.length > 0) { data.value = data.example; } // In case of data.example diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index cfd65edd..32054a69 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -4584,6 +4584,7 @@ const AngularWorkflow = (defaultprops) => { workflow.actions[foundindex].name = curaction.name setWorkflow(workflow) + console.log(workflow) } break } From 73b5d1a30d54daefc069579766144135d36f7116 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 31 May 2024 12:58:56 +0530 Subject: [PATCH 09/46] Bug fixes --- frontend/src/components/ParsedAction.jsx | 16 ++++++++-------- frontend/src/views/AngularWorkflow.jsx | 3 +-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 937b0557..3ce989fe 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -380,12 +380,13 @@ const ParsedAction = (props) => { }; - useEffect(() => { - if (selectedActionParameters !== undefined && selectedActionParameters !== null - ) { + useEffect( + () => { + // if (selectedActionParameters !== undefined && selectedActionParameters !== null + // ) { if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { setSelectedActionParameters(selectedAction.parameters); - } + // } } if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) { @@ -2054,7 +2055,6 @@ const ParsedAction = (props) => { return ( { placeholder = data.example; - if (data.name === "url" && data.value !== undefined && data.value !== null && data.value.length > 0) { + if (data.name === "url") { data.value = data.example; } // In case of data.example @@ -3149,8 +3149,8 @@ const ParsedAction = (props) => { id={clickedFieldId} rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" - defaultValue={data.value} - //value={data.value} + // defaultValue={data.value} + value={data.value} //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 32054a69..b3711b81 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -16953,7 +16953,6 @@ const AngularWorkflow = (defaultprops) => { var defaultReturn = null - if (Object.getOwnPropertyNames(selectedComment).length > 0) { defaultReturn = } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { @@ -17811,7 +17810,7 @@ const AngularWorkflow = (defaultprops) => { console.log("IN useeffectt (2)" + collapsed) return; } - }) + },[]) /* componentWillUpdate = (nextProps, nextState) => { console.log(nextProps, nextState) From af734f7fb407db628d085ff695834b78bf0a9f6e Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Sun, 2 Jun 2024 15:30:10 +0530 Subject: [PATCH 10/46] Bug fix --- frontend/src/components/ParsedAction.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 3ce989fe..1db53973 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -3149,8 +3149,8 @@ const ParsedAction = (props) => { id={clickedFieldId} rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" - // defaultValue={data.value} - value={data.value} + defaultValue={data.value} + // value={data.value} //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', From 4f1bfc2313b002887993b4560db1d38bb9bb8beb Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 6 Jun 2024 12:56:01 +0530 Subject: [PATCH 11/46] Done with find action dropdown --- frontend/src/components/ParsedAction.jsx | 75 +- frontend/src/views/AngularWorkflow.jsx | 1027 +++++++++++----------- 2 files changed, 550 insertions(+), 552 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 1db53973..95a9ad4e 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -180,9 +180,8 @@ const ParsedAction = (props) => { const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); - const [autoCompleting, setAutocompleting] = React.useState(false); - const [selectedActionParameters, setSelectedActionParameters] = React.useState([]); + const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction.parameters); const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); const [actionlist, setActionlist] = React.useState([]); const [jsonList, setJsonList] = React.useState([]); @@ -382,9 +381,12 @@ const ParsedAction = (props) => { useEffect( () => { + console.log("UseEffect Rendered!") + console.log("Workflow", workflow) // if (selectedActionParameters !== undefined && selectedActionParameters !== null // ) { if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { + console.log("Setting action parameters!!") setSelectedActionParameters(selectedAction.parameters); // } } @@ -614,9 +616,13 @@ const ParsedAction = (props) => { } } }, - [selectedAction, selectedVariableParameter, workflowExecutions, listCache]); - - + [selectedAction,selectedApp,setNewSelectedAction] + ); + console.log("selectedActionParameters: ", selectedActionParameters) + console.log("selectedApp:", selectedApp) + console.log("selectedAction: ", selectedAction) + console.log("ACTIONLIST: ", actionlist) + console.log("selectedVariableParameter", selectedVariableParameter) const calculateHelpertext = (input_data) => { var helperText = "" var looperText = "" @@ -1184,7 +1190,7 @@ const ParsedAction = (props) => { } // FIXME: Issue #40 - selectedActionParameters not reset - + if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { var wrapperapp = { "id": "", "name": "noapp", @@ -1210,15 +1216,16 @@ const ParsedAction = (props) => { var authWritten = false; var noAppSelected = false - const paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name") + var paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name") if (paramIndex === -1 || selectedAction.parameters[paramIndex].value === "" || selectedAction.parameters[paramIndex].value === "noapp") { // Check the actual value and if it's the same noAppSelected = true } + } const ActionSelectOption = (actionprops) => { - const { data, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops; + const { option, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops; const [hover, setHover] = React.useState(false); return ( @@ -1234,18 +1241,20 @@ const ParsedAction = (props) => { paddingBottom: 4, backgroundColor: hover ? theme.palette.surfaceColor : theme.palette.inputColor, }} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} - onClick={() => { + onClick={(event) => { + // event.preventDefault() //setSelectedAction(actionprops) //setShowActionList(false) //setUpdate(Math.random()) // - if (data !== undefined && data !== null) { + if (option !== undefined && option !== null) { setNewSelectedAction({ target: { - value: data.name + value: option.name } }); } + document.activeElement.blur(); }} >
    @@ -1308,7 +1317,7 @@ const ParsedAction = (props) => { const selectedAppIcon = selectedAction.large_image - console.log("Selected action: ", selectedAction) +// console.log("Selected action: ", selectedAction) console.log("Selected action paramaters: ", selectedAction.parameters) var baselabel = selectedAction.label return ( @@ -2220,54 +2229,55 @@ const ParsedAction = (props) => { } }); } - }} - renderOption={(props, data, state) => { - var newActionname = data.name; - if (data.label !== undefined && data.label !== null && data.label.length > 0) { - newActionname = data.label; + event.target.blur(); + }} + renderOption={(props, option, state) => { + var newActionname = option.name; + if (option.label !== undefined && option.label !== null && option.label.length > 0) { + newActionname = option.label; } - var newActiondescription = data.description; + var newActiondescription = option.description; //console.log("DESC: ", newActiondescription) - if (data.description === undefined || data.description === null) { + if (option.description === undefined || option.description === null) { newActiondescription = "Description: No description defined for this action" } else { newActiondescription = "Description: "+newActiondescription } - const iconInfo = GetIconInfo({ name: data.name }); + const iconInfo = GetIconInfo({ name: option.name }); const useIcon = iconInfo.originalIcon; if (newActionname === undefined || newActionname === null) { newActionname = "No name" - data.name = "No name" - data.label = "No name" + option.name = "No name" + option.label = "No name" } newActionname = (newActionname.charAt(0).toUpperCase() + newActionname.substring(1)).replaceAll("_", " "); var method = "" var extraDescription = "" - if (data.name.includes("get_")) { + if (option.name.includes("get_")) { method = "GET" - } else if (data.name.includes("post_")) { + } else if (option.name.includes("post_")) { method = "POST" - } else if (data.name.includes("put_")) { + } else if (option.name.includes("put_")) { method = "PUT" - } else if (data.name.includes("patch_")) { + } else if (option.name.includes("patch_")) { method = "PATCH" - } else if (data.name.includes("delete_")) { + } else if (option.name.includes("delete_")) { method = "DELETE" - } else if (data.name.includes("options_")) { + } else if (option.name.includes("options_")) { method = "OPTIONS" - } else if (data.name.includes("connect_")) { + } else if (option.name.includes("connect_")) { method = "CONNECT" } // FIXME: Should it require a base URL? - if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) { + if (method.length > 0 && option.description !== undefined && option.description !== null && option.description.includes("http")) { var extraUrl = "" - const descSplit = data.description.split("\n") + const descSplit = option.description.split("\n") // Last line of descSplit if (descSplit.length > 0) { extraUrl = descSplit[descSplit.length-1] @@ -2306,7 +2316,8 @@ const ParsedAction = (props) => { return ( { //const data = JSON.parse(JSON.stringify(event.target.data())) const data = event.target.data() - + console.log("===============================Node selected=============================") console.log("NODE SELECT: ", data) if (data.app_name === "Shuffle Workflow") { @@ -8567,11 +8567,15 @@ const AngularWorkflow = (defaultprops) => { ); }; - const handleSetTab = (event, newValue) => { - setCurrentView(newValue); - }; + const HandleLeftView = () => { + // console.log("HandleLeftView Rendered!") + + const handleSetTab = (event, newValue) => { + setCurrentView(newValue); + }; + // Defaults to apps. var thisview = ( { ) } - - - const TriggersView = () => { - const triggersViewStyle = { - marginLeft: 10, - marginRight: 10, - display: "flex", - flexDirection: "column", - } - - // Predefined hurr - return ( -
    -
    - {triggers.map((trigger, index) => { - - /* - if (trigger.trigger_type === "PIPELINE") { - if (userdata.support !== true) { - return null - } - } - */ - - // Hiding since March 2024 - if (trigger.trigger_type === "EMAIL") { - return null - } - - const imagesize = isMobile ? 40 : trigger.large_image.includes("svg") ? 50 : 50 - var imageline = trigger.large_image.length === 0 ? - : - - - const title = trigger.trigger_type === "WEBHOOK" ? "Workflow starters" : trigger.trigger_type === "SUBFLOW" ? "Mid-Workflow" : "" - - const color = trigger.is_valid ? green : yellow; - return ( - - {title.length > 0 ? - - {title} - - : null} - - { - handleTriggerDrag(e, trigger); - }} - onStop={(e) => { - handleDragStop(e); - }} - dragging={false} - position={{ - x: 0, - y: 0, - }} - > - { }}> -
    - - - {imageline} - - {isMobile ? null : - - - - {trigger.name} - - - - - {trigger.description} - - - - } - -
    -
    -
    - ); - })} -
    -
    - ); - }; - - var newNodeId = ""; - var parsedApp = {}; - const handleTriggerDrag = (e, data) => { - const cycontainer = cy.container(); - // Chrome lol - if ( - e.pageX > cycontainer.offsetLeft && - e.pageX < cycontainer.offsetLeft + cycontainer.offsetWidth && - e.pageY > cycontainer.offsetTop && - e.pageY < cycontainer.offsetTop + cycontainer.offsetHeight - ) { - if (newNodeId.length > 0) { - var currentnode = cy.getElementById(newNodeId); - if (currentnode.length === 0) { - return; - } - - currentnode[0].renderedPosition("x", e.pageX - cycontainer.offsetLeft); - currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop); - } else { - if (workflow.start === "" || workflow.start === undefined) { - toast("Define a starting action first."); - return; - } - - const triggerLabel = getNextActionName(data.name); - - newNodeId = uuidv4(); - const newposition = { - x: e.pageX - cycontainer.offsetLeft, - y: e.pageY - cycontainer.offsetTop, - }; - - const newAppData = { - app_name: data.name, - app_version: "1.0.0", - environment: isCloud ? "cloud" : data.environment, - description: data.description, - long_description: data.long_description, - errors: [], - id_: newNodeId, - _id_: newNodeId, - id: newNodeId, - finished: false, - label: triggerLabel, - type: data.type, - is_valid: true, - trigger_type: data.trigger_type, - large_image: data.large_image, - status: "uninitialized", - name: data.name, - isStartNode: false, - position: newposition, - }; - - // Can all the data be in here? hmm - const nodeToBeAdded = { - group: "nodes", - data: newAppData, - renderedPosition: newposition, - }; - - cy.add(nodeToBeAdded); - parsedApp = nodeToBeAdded; - return; - } - } - }; - - const handleDragStop = (e, app) => { - var currentnode = cy.getElementById(newNodeId); - if ( - currentnode === undefined || - currentnode === null || - currentnode.length === 0 - ) { - return; - } - - // Using remove & replace, as this triggers the function - // onNodeAdded() with this node after it's added - - currentnode.remove(); - parsedApp.data.finished = true; - parsedApp.data.position = currentnode.renderedPosition(); - parsedApp.position = currentnode.renderedPosition(); - parsedApp.renderedPosition = currentnode.renderedPosition(); - - var newAppData = parsedApp.data; - if (newAppData.type === "ACTION") { - - //const activateApp = (appid) => { - if (newAppData.activated === false) { - activateApp(newAppData.app_id, false) - } - - // AUTHENTICATION - if (app.authentication !== undefined && app.authentication !== null && app.authentication.required === true) { - - // Setup auth here :) - const authenticationOptions = []; - var findAuthId = ""; - if ( - newAppData.authentication_id !== null && - newAppData.authentication_id !== undefined && - newAppData.authentication_id.length > 0 - ) { - findAuthId = newAppData.authentication_id; - } - - const tmpAuth = JSON.parse(JSON.stringify(appAuthentication)); - for (let authkey in tmpAuth) { - if (authkey === undefined) { - continue - } - - var item = tmpAuth[authkey]; - const newfields = {}; - for (let fieldkey in item.fields) { - if (item.fields[fieldkey] === undefined) { - console.log("Problem with filterkey in Node select", fieldkey) - continue - } - - const filterkey = item.fields[fieldkey]["key"] - if (filterkey === null || filterkey === undefined) { - console.log("Problem with filterkey 2. Null or undefined 3") - continue - } - - newfields[filterkey] = item.fields[fieldkey]["value"]; - } - - item.fields = newfields; - if (item.app.id === app.id || item.app.name === app.name) { - authenticationOptions.push(item); - - if (item.id === findAuthId) { - newAppData.selectedAuthentication = item - newAppData.authentication_id = item.id - - } else if (findAuthId === "") { - // Will always be set to the last one if one isn't found. - // Last = timestamp too - newAppData.selectedAuthentication = item - newAppData.authentication_id = item.id - } - } - } - - if ( - authenticationOptions !== undefined && - authenticationOptions !== null && - authenticationOptions.length > 0 - ) { - for (let authkey in authenticationOptions) { - const option = authenticationOptions[authkey]; - - if (option.active && newAppData.authentication_id === "") { - newAppData.selectedAuthentication = option; - newAppData.authentication_id = option.id; - break; - } - } - } - } else { - newAppData.authentication = []; - newAppData.authentication_id = ""; - newAppData.selectedAuthentication = {}; - } - - parsedApp.data = newAppData; - cy.add(parsedApp); - } else if (newAppData.type === "TRIGGER") { - cy.add(parsedApp); - } - - newNodeId = ""; - parsedApp = {}; - }; - - const barHeight = bodyHeight - appBarSize - 50; - const appScrollStyle = { - overflow: "scroll", - maxHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight, - minHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight, - marginTop: 1, - overflowY: "auto", - overflowX: "hidden", - } - - const handleAppDrag = (e, app) => { - const cycontainer = cy.container(); - - - if (app.type === "TRIGGER") { - handleTriggerDrag(e, app) - return - } - - //console.log("e: ", e) - //console.log("Offset: ", cycontainer) - - // Chrome lol - if ( - e.pageX > cycontainer.offsetLeft && - e.pageX < cycontainer.offsetLeft + cycontainer.offsetWidth && - e.pageY > cycontainer.offsetTop && - e.pageY < cycontainer.offsetTop + cycontainer.offsetHeight - ) { - if (newNodeId.length > 0) { - var currentnode = cy.getElementById(newNodeId); - if ( - currentnode === undefined || - currentnode === null || - currentnode.length === 0 - ) { - return; - } - - currentnode[0].renderedPosition("x", e.pageX - cycontainer.offsetLeft) - currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop) - } else { - if (workflow.public) { - console.log("workflow is public - not adding") - return; - } - - if (app.actions === undefined || app.actions === null) { - app.actions = [] - } - - /* - if (app.actions === undefined || app.actions === null || app.actions.length === 0) { - toast("App " + app.name + " currently has no actions to perform. Please go to https://shuffler.io/apps to edit it.") - - return - } - */ - - newNodeId = uuidv4(); - const actionType = "ACTION"; - const actionLabel = getNextActionName(app.name); - //console.log("Next action name: ", actionLabel) - var parameters = null; - var example = ""; - var description = "" - - const startIndex = app.actions.findIndex((action) => action.category_label !== undefined && action.category_label !== null && action.category_label.length > 0) - const actionIndex = startIndex < 0 ? 0 : startIndex - - // Make the first action the most relevant one for them based on previous use - if ( - app.actions[actionIndex].parameters !== undefined && - app.actions[actionIndex].parameters !== null && - app.actions[actionIndex].parameters.length > 0 - ) { - parameters = app.actions[actionIndex].parameters; - for (let paramkey in parameters) { - // Check if parameter.name == "headers" and if it includes "=undefined". If it does, set the value to example if it exists, otherwise empty - if (parameters[paramkey].name === "headers" && parameters[paramkey].value.includes("=undefined")) { - if (parameters[paramkey].example !== undefined && parameters[paramkey].example !== null && parameters[paramkey].example.length > 0) { - parameters[paramkey].value = parameters[paramkey].example - } else { - parameters[paramkey].value = "" - } - } - } - - //parameters = app.actions[0].parameters; - } - - if ( - app.actions[actionIndex].returns !== undefined && - app.actions[actionIndex].returns !== null && - app.actions[actionIndex].returns.example !== undefined && - app.actions[actionIndex].returns.example !== null && - app.actions[actionIndex].returns.example.length > 0 - ) { - example = app.actions[actionIndex].returns.example; - } - - if ( - app.actions[actionIndex].description !== undefined && - app.actions[actionIndex].description !== null && - app.actions[actionIndex].description.length > 0 - ) { - description = app.actions[actionIndex].description - } - - var parsedEnvironments = - environments === null || environments === [] - ? "cloud" - : environments[defaultEnvironmentIndex] === undefined - ? "cloud" - : environments[defaultEnvironmentIndex].Name - - // Basic automatic auth mapping - var authId = "" - if (appAuthentication !== undefined && appAuthentication !== null && appAuthentication.length > 0) { - const appname = app.name.toLowerCase().replace(" ", "_") - for (var key in appAuthentication) { - const authKey = appAuthentication[key] - if (authKey.app.id === app.id) { - authId = authKey.id - break - } - - const appauthname = authKey.app.name.toLowerCase().replace(" ", "_") - if (appauthname === appname) { - authId = authKey.id - } - } - } - - // List other nodes in the workflow and see if they have an environment set. If they do, use that as the default - if (cy !== undefined && cy !== null) { - const foundnodes = cy.nodes().jsons() - if (foundnodes !== undefined && foundnodes !== null && foundnodes.length > 0) { - // As they should all be the same, this is just an override - for (let nodekey in foundnodes) { - const curnode = foundnodes[nodekey] - if (curnode.data.environment !== undefined && curnode.data.environment !== null && curnode.data.environment.length > 0) { - parsedEnvironments = curnode.data.environment - break - } - } - } - } - - const newAppData = { - name: app.actions[actionIndex].name, - label: actionLabel, - app_name: app.name, - app_version: app.app_version, - app_id: app.id, - sharing: app.sharing, - private_id: app.private_id, - description: description, - environment: parsedEnvironments, - errors: [], - finished: false, - id_: newNodeId, - _id_: newNodeId, - id: newNodeId, - is_valid: true, - type: actionType, - parameters: parameters, - isStartNode: false, - large_image: app.large_image, - run_magic_output: false, - authentication: [], - execution_variable: undefined, - example: example, - required_body_fields: app.actions[actionIndex].required_body_fields, - category: - app.categories !== null && - app.categories !== undefined && - app.categories.length > 0 - ? app.categories[0] - : "", - authentication_id: authId, - finished: false, - template: app.template === true ? true : false, - }; - - // FIXME: overwrite category if the ACTION chosen has a different category - // - - if (!isCloud && (!app.is_valid || (!app.activated && app.generated))) { - console.log("NOT VALID: Activate!") - - activateApp(app.id, false) - } - - // const image = "url("+app.large_image+")" - // FIXME - find the cytoscape offset position - // Can this be done with zoom calculations? - const nodeToBeAdded = { - group: "nodes", - data: newAppData, - renderedPosition: { - x: e.pageX - cycontainer.offsetLeft, - y: e.pageY - cycontainer.offsetTop, - }, - }; - - parsedApp = nodeToBeAdded; - cy.add(nodeToBeAdded); - return; - } - } - }; - const AppView = (props) => { const { allApps, prioritizedApps, filteredApps, extraApps } = props; - + // console.log("AppView Rendered!") //extraApps, const [visibleApps, setVisibleApps] = React.useState( Array.prototype.concat.apply( @@ -9833,8 +9323,505 @@ const AngularWorkflow = (defaultprops) => {
    ); +} + + const TriggersView = () => { + console.log("TriggerView Rendered!") + const triggersViewStyle = { + marginLeft: 10, + marginRight: 10, + display: "flex", + flexDirection: "column", + } + + // Predefined hurr + return ( +
    +
    + {triggers.map((trigger, index) => { + + /* + if (trigger.trigger_type === "PIPELINE") { + if (userdata.support !== true) { + return null + } + } + */ + + // Hiding since March 2024 + if (trigger.trigger_type === "EMAIL") { + return null + } + + const imagesize = isMobile ? 40 : trigger.large_image.includes("svg") ? 50 : 50 + var imageline = trigger.large_image.length === 0 ? + : + + + const title = trigger.trigger_type === "WEBHOOK" ? "Workflow starters" : trigger.trigger_type === "SUBFLOW" ? "Mid-Workflow" : "" + + const color = trigger.is_valid ? green : yellow; + return ( + + {title.length > 0 ? + + {title} + + : null} + + { + handleTriggerDrag(e, trigger); + }} + onStop={(e) => { + handleDragStop(e); + }} + dragging={false} + position={{ + x: 0, + y: 0, + }} + > + { }}> +
    + + + {imageline} + + {isMobile ? null : + + + + {trigger.name} + + + + + {trigger.description} + + + + } + +
    +
    +
    + ); + })} +
    +
    + ); + } + + var newNodeId = ""; + var parsedApp = {}; + const handleTriggerDrag = (e, data) => { + const cycontainer = cy.container(); + // Chrome lol + if ( + e.pageX > cycontainer.offsetLeft && + e.pageX < cycontainer.offsetLeft + cycontainer.offsetWidth && + e.pageY > cycontainer.offsetTop && + e.pageY < cycontainer.offsetTop + cycontainer.offsetHeight + ) { + if (newNodeId.length > 0) { + var currentnode = cy.getElementById(newNodeId); + if (currentnode.length === 0) { + return; + } + + currentnode[0].renderedPosition("x", e.pageX - cycontainer.offsetLeft); + currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop); + } else { + if (workflow.start === "" || workflow.start === undefined) { + toast("Define a starting action first."); + return; + } + + const triggerLabel = getNextActionName(data.name); + + newNodeId = uuidv4(); + const newposition = { + x: e.pageX - cycontainer.offsetLeft, + y: e.pageY - cycontainer.offsetTop, + }; + + const newAppData = { + app_name: data.name, + app_version: "1.0.0", + environment: isCloud ? "cloud" : data.environment, + description: data.description, + long_description: data.long_description, + errors: [], + id_: newNodeId, + _id_: newNodeId, + id: newNodeId, + finished: false, + label: triggerLabel, + type: data.type, + is_valid: true, + trigger_type: data.trigger_type, + large_image: data.large_image, + status: "uninitialized", + name: data.name, + isStartNode: false, + position: newposition, + }; + + // Can all the data be in here? hmm + const nodeToBeAdded = { + group: "nodes", + data: newAppData, + renderedPosition: newposition, + }; + + cy.add(nodeToBeAdded); + parsedApp = nodeToBeAdded; + return; + } + } }; + const handleDragStop = (e, app) => { + var currentnode = cy.getElementById(newNodeId); + if ( + currentnode === undefined || + currentnode === null || + currentnode.length === 0 + ) { + return; + } + + // Using remove & replace, as this triggers the function + // onNodeAdded() with this node after it's added + + currentnode.remove(); + parsedApp.data.finished = true; + parsedApp.data.position = currentnode.renderedPosition(); + parsedApp.position = currentnode.renderedPosition(); + parsedApp.renderedPosition = currentnode.renderedPosition(); + + var newAppData = parsedApp.data; + if (newAppData.type === "ACTION") { + + //const activateApp = (appid) => { + if (newAppData.activated === false) { + activateApp(newAppData.app_id, false) + } + + // AUTHENTICATION + if (app.authentication !== undefined && app.authentication !== null && app.authentication.required === true) { + + // Setup auth here :) + const authenticationOptions = []; + var findAuthId = ""; + if ( + newAppData.authentication_id !== null && + newAppData.authentication_id !== undefined && + newAppData.authentication_id.length > 0 + ) { + findAuthId = newAppData.authentication_id; + } + + const tmpAuth = JSON.parse(JSON.stringify(appAuthentication)); + for (let authkey in tmpAuth) { + if (authkey === undefined) { + continue + } + + var item = tmpAuth[authkey]; + const newfields = {}; + for (let fieldkey in item.fields) { + if (item.fields[fieldkey] === undefined) { + console.log("Problem with filterkey in Node select", fieldkey) + continue + } + + const filterkey = item.fields[fieldkey]["key"] + if (filterkey === null || filterkey === undefined) { + console.log("Problem with filterkey 2. Null or undefined 3") + continue + } + + newfields[filterkey] = item.fields[fieldkey]["value"]; + } + + item.fields = newfields; + if (item.app.id === app.id || item.app.name === app.name) { + authenticationOptions.push(item); + + if (item.id === findAuthId) { + newAppData.selectedAuthentication = item + newAppData.authentication_id = item.id + + } else if (findAuthId === "") { + // Will always be set to the last one if one isn't found. + // Last = timestamp too + newAppData.selectedAuthentication = item + newAppData.authentication_id = item.id + } + } + } + + if ( + authenticationOptions !== undefined && + authenticationOptions !== null && + authenticationOptions.length > 0 + ) { + for (let authkey in authenticationOptions) { + const option = authenticationOptions[authkey]; + + if (option.active && newAppData.authentication_id === "") { + newAppData.selectedAuthentication = option; + newAppData.authentication_id = option.id; + break; + } + } + } + } else { + newAppData.authentication = []; + newAppData.authentication_id = ""; + newAppData.selectedAuthentication = {}; + } + + parsedApp.data = newAppData; + cy.add(parsedApp); + } else if (newAppData.type === "TRIGGER") { + cy.add(parsedApp); + } + + newNodeId = ""; + parsedApp = {}; + }; + + const barHeight = bodyHeight - appBarSize - 50; + const appScrollStyle = { + overflow: "scroll", + maxHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight, + minHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight, + marginTop: 1, + overflowY: "auto", + overflowX: "hidden", + } + + const handleAppDrag = (e, app) => { + const cycontainer = cy.container(); + + + if (app.type === "TRIGGER") { + handleTriggerDrag(e, app) + return + } + + //console.log("e: ", e) + //console.log("Offset: ", cycontainer) + + // Chrome lol + if ( + e.pageX > cycontainer.offsetLeft && + e.pageX < cycontainer.offsetLeft + cycontainer.offsetWidth && + e.pageY > cycontainer.offsetTop && + e.pageY < cycontainer.offsetTop + cycontainer.offsetHeight + ) { + if (newNodeId.length > 0) { + var currentnode = cy.getElementById(newNodeId); + if ( + currentnode === undefined || + currentnode === null || + currentnode.length === 0 + ) { + return; + } + + currentnode[0].renderedPosition("x", e.pageX - cycontainer.offsetLeft) + currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop) + } else { + if (workflow.public) { + console.log("workflow is public - not adding") + return; + } + + if (app.actions === undefined || app.actions === null) { + app.actions = [] + } + + /* + if (app.actions === undefined || app.actions === null || app.actions.length === 0) { + toast("App " + app.name + " currently has no actions to perform. Please go to https://shuffler.io/apps to edit it.") + + return + } + */ + + newNodeId = uuidv4(); + const actionType = "ACTION"; + const actionLabel = getNextActionName(app.name); + //console.log("Next action name: ", actionLabel) + var parameters = null; + var example = ""; + var description = "" + + const startIndex = app.actions.findIndex((action) => action.category_label !== undefined && action.category_label !== null && action.category_label.length > 0) + const actionIndex = startIndex < 0 ? 0 : startIndex + + // Make the first action the most relevant one for them based on previous use + if ( + app.actions[actionIndex].parameters !== undefined && + app.actions[actionIndex].parameters !== null && + app.actions[actionIndex].parameters.length > 0 + ) { + parameters = app.actions[actionIndex].parameters; + for (let paramkey in parameters) { + // Check if parameter.name == "headers" and if it includes "=undefined". If it does, set the value to example if it exists, otherwise empty + if (parameters[paramkey].name === "headers" && parameters[paramkey].value.includes("=undefined")) { + if (parameters[paramkey].example !== undefined && parameters[paramkey].example !== null && parameters[paramkey].example.length > 0) { + parameters[paramkey].value = parameters[paramkey].example + } else { + parameters[paramkey].value = "" + } + } + } + + //parameters = app.actions[0].parameters; + } + + if ( + app.actions[actionIndex].returns !== undefined && + app.actions[actionIndex].returns !== null && + app.actions[actionIndex].returns.example !== undefined && + app.actions[actionIndex].returns.example !== null && + app.actions[actionIndex].returns.example.length > 0 + ) { + example = app.actions[actionIndex].returns.example; + } + + if ( + app.actions[actionIndex].description !== undefined && + app.actions[actionIndex].description !== null && + app.actions[actionIndex].description.length > 0 + ) { + description = app.actions[actionIndex].description + } + + var parsedEnvironments = + environments === null || environments === [] + ? "cloud" + : environments[defaultEnvironmentIndex] === undefined + ? "cloud" + : environments[defaultEnvironmentIndex].Name; + + // List other nodes in the workflow and see if they have an environment set. If they do, use that as the default + if (cy !== undefined && cy !== null) { + const foundnodes = cy.nodes().jsons() + if (foundnodes !== undefined && foundnodes !== null && foundnodes.length > 0) { + // As they should all be the same, this is just an override + for (let nodekey in foundnodes) { + const curnode = foundnodes[nodekey] + if (curnode.data.environment !== undefined && curnode.data.environment !== null && curnode.data.environment.length > 0) { + parsedEnvironments = curnode.data.environment + break + } + } + } + } + + const newAppData = { + name: app.actions[actionIndex].name, + label: actionLabel, + app_name: app.name, + app_version: app.app_version, + app_id: app.id, + sharing: app.sharing, + private_id: app.private_id, + description: description, + environment: parsedEnvironments, + errors: [], + finished: false, + id_: newNodeId, + _id_: newNodeId, + id: newNodeId, + is_valid: true, + type: actionType, + parameters: parameters, + isStartNode: false, + large_image: app.large_image, + run_magic_output: false, + authentication: [], + execution_variable: undefined, + example: example, + required_body_fields: app.actions[actionIndex].required_body_fields, + category: + app.categories !== null && + app.categories !== undefined && + app.categories.length > 0 + ? app.categories[0] + : "", + authentication_id: "", + finished: false, + template: app.template === true ? true : false, + }; + + // FIXME: overwrite category if the ACTION chosen has a different category + // + + if (!isCloud && (!app.is_valid || (!app.activated && app.generated))) { + console.log("NOT VALID: Activate!") + + activateApp(app.id, false) + } + + // const image = "url("+app.large_image+")" + // FIXME - find the cytoscape offset position + // Can this be done with zoom calculations? + const nodeToBeAdded = { + group: "nodes", + data: newAppData, + renderedPosition: { + x: e.pageX - cycontainer.offsetLeft, + y: e.pageY - cycontainer.offsetTop, + }, + }; + + parsedApp = nodeToBeAdded; + cy.add(nodeToBeAdded); + return; + } + } + }; + + const getNextActionName = (appName) => { var highest = ""; From 355466a9a28ae03f8255dec188bf102b93804ea6 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 6 Jun 2024 17:44:15 +0530 Subject: [PATCH 12/46] Done with delay --- frontend/src/components/ParsedAction.jsx | 31 +++++++++--- frontend/src/views/AngularWorkflow.jsx | 64 ++++++++++++------------ 2 files changed, 54 insertions(+), 41 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 95a9ad4e..7b308d71 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -177,7 +177,8 @@ const ParsedAction = (props) => { const classes = useStyles(); const [hideBody, setHideBody] = React.useState(true); const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false); - + const [appActionName, setAppActionName] = React.useState(selectedAction.label); + const [delay, setDelay] = React.useState(selectedAction?.execution_delay); const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); const [autoCompleting, setAutocompleting] = React.useState(false); @@ -197,6 +198,7 @@ const ParsedAction = (props) => { } }, [expansionModalOpen]) + useEffect(() => { if (selectedAction.parameters === null || selectedAction.parameters === undefined) { return @@ -383,6 +385,9 @@ const ParsedAction = (props) => { () => { console.log("UseEffect Rendered!") console.log("Workflow", workflow) + setAppActionName(selectedAction.label) + setDelay(selectedAction.execution_delay) + // if (selectedActionParameters !== undefined && selectedActionParameters !== null // ) { if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { @@ -618,6 +623,12 @@ const ParsedAction = (props) => { }, [selectedAction,selectedApp,setNewSelectedAction] ); + + useEffect(() => { + selectedNameChange(appActionName) + actionDelayChange(delay) + },[appActionName,delay]) + console.log("selectedActionParameters: ", selectedActionParameters) console.log("selectedApp:", selectedApp) console.log("selectedAction: ", selectedAction) @@ -1628,11 +1639,17 @@ const ParsedAction = (props) => { fullWidth color="primary" placeholder={selectedAction.label} - defaultValue={selectedAction.label} - onChange={selectedNameChange} + value={appActionName} + onChange={ + (event) => { + let newValue = event.target.value + newValue = newValue.replaceAll(" ", "_") + setAppActionName(newValue) + } + } onBlur={(e) => { // Copy the name value - const name = e.target.value + const name = appActionName const parsedBaseLabel = "$"+baselabel.toLowerCase().replaceAll(" ", "_") const newname = "$"+name.toLowerCase().replaceAll(" ", "_") @@ -1848,11 +1865,9 @@ const ParsedAction = (props) => { disableUnderline: true, }} placeholder={selectedAction.execution_delay} - defaultValue={selectedAction.execution_delay} + value={delay} onChange={(event) => { - if (actionDelayChange !== undefined) { - actionDelayChange(event) - } + setDelay(event.target.value) }} /> diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 7e173716..bece66c2 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -22,7 +22,6 @@ import { CodeHandler, Img, OuterLink, } from "../views/Docs.jsx"; import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; import algoliasearch from 'algoliasearch/lite'; - import { Zoom, Fade, @@ -10067,44 +10066,43 @@ const AngularWorkflow = (defaultprops) => { // appname & version // description // ACTION select - const selectedNameChange = (event) => { - event.target.value = event.target.value.replaceAll("(", ""); - event.target.value = event.target.value.replaceAll(")", ""); - event.target.value = event.target.value.replaceAll("]", ""); - event.target.value = event.target.value.replaceAll("[", ""); - event.target.value = event.target.value.replaceAll("{", ""); - event.target.value = event.target.value.replaceAll("}", ""); - event.target.value = event.target.value.replaceAll("*", ""); - event.target.value = event.target.value.replaceAll("!", ""); - event.target.value = event.target.value.replaceAll("@", ""); - event.target.value = event.target.value.replaceAll("#", ""); - event.target.value = event.target.value.replaceAll("$", ""); - event.target.value = event.target.value.replaceAll("%", ""); - event.target.value = event.target.value.replaceAll("&", ""); - event.target.value = event.target.value.replaceAll("#", ""); - event.target.value = event.target.value.replaceAll(".", ""); - event.target.value = event.target.value.replaceAll(",", ""); - event.target.value = event.target.value.replaceAll(" ", "_"); - event.target.value = event.target.value.replaceAll("^", "_"); - event.target.value = event.target.value.replaceAll("'", "_"); - event.target.value = event.target.value.replaceAll("\"", "_"); - event.target.value = event.target.value.replaceAll("\"", "_"); - event.target.value = event.target.value.replaceAll(":", "_"); - event.target.value = event.target.value.replaceAll(";", "_"); - event.target.value = event.target.value.replaceAll("=", "_"); - event.target.value = event.target.value.replaceAll("+", "_"); - - selectedAction.label = event.target.value; + const selectedNameChange = (appActionName) => { + appActionName = appActionName.replaceAll("(", ""); + appActionName = appActionName.replaceAll(")", ""); + appActionName = appActionName.replaceAll("]", ""); + appActionName = appActionName.replaceAll("[", ""); + appActionName = appActionName.replaceAll("{", ""); + appActionName = appActionName.replaceAll("}", ""); + appActionName = appActionName.replaceAll("*", ""); + appActionName = appActionName.replaceAll("!", ""); + appActionName = appActionName.replaceAll("@", ""); + appActionName = appActionName.replaceAll("#", ""); + appActionName = appActionName.replaceAll("$", ""); + appActionName = appActionName.replaceAll("%", ""); + appActionName = appActionName.replaceAll("&", ""); + appActionName = appActionName.replaceAll("#", ""); + appActionName = appActionName.replaceAll(".", ""); + appActionName = appActionName.replaceAll(",", ""); + appActionName = appActionName.replaceAll(" ", "_"); + appActionName = appActionName.replaceAll("^", "_"); + appActionName = appActionName.replaceAll("'", "_"); + appActionName = appActionName.replaceAll("\"", "_"); + appActionName = appActionName.replaceAll("\"", "_"); + appActionName = appActionName.replaceAll(":", "_"); + appActionName = appActionName.replaceAll(";", "_"); + appActionName = appActionName.replaceAll("=", "_"); + appActionName = appActionName.replaceAll("+", "_"); + selectedAction.label = appActionName; setSelectedAction(selectedAction); }; - const actionDelayChange = (event) => { - if (isNaN(event.target.value)) { - console.log("NAN: ", event.target.value) + const actionDelayChange = (delay) => { + if (isNaN(delay)) { + console.log("NAN: ", delay) return } - const parsedNumber = parseInt(event.target.value) + const parsedNumber = parseInt(delay) if (parsedNumber > 86400) { console.log("Max number is 1 day (86400)") return From 03a1e62e4ede84a7becf60b7cc2a349635d24cf5 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 7 Jun 2024 17:10:54 +0530 Subject: [PATCH 13/46] Fixed the parameter value change dynamically --- frontend/src/components/ParsedAction.jsx | 58 +++++++++++++++--------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 7b308d71..2d428ca8 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -178,12 +178,16 @@ const ParsedAction = (props) => { const [hideBody, setHideBody] = React.useState(true); const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false); const [appActionName, setAppActionName] = React.useState(selectedAction.label); - const [delay, setDelay] = React.useState(selectedAction?.execution_delay); + const [delay, setDelay] = React.useState(selectedAction?.execution_delay || 0); + const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); const [autoCompleting, setAutocompleting] = React.useState(false); const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction.parameters); const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); + const [paramValues, setParamValues] = React.useState( + selectedAction.parameters.map((param) => param.value) || [] + ); const [actionlist, setActionlist] = React.useState([]); const [jsonList, setJsonList] = React.useState([]); const [showDropdown, setShowDropdown] = React.useState(false); @@ -386,8 +390,9 @@ const ParsedAction = (props) => { console.log("UseEffect Rendered!") console.log("Workflow", workflow) setAppActionName(selectedAction.label) - setDelay(selectedAction.execution_delay) + setDelay(selectedAction?.execution_delay) + setParamValues(selectedAction.parameters.map((param) => param.value) || []) // if (selectedActionParameters !== undefined && selectedActionParameters !== null // ) { if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { @@ -634,6 +639,16 @@ const ParsedAction = (props) => { console.log("selectedAction: ", selectedAction) console.log("ACTIONLIST: ", actionlist) console.log("selectedVariableParameter", selectedVariableParameter) + + const handleParamChange = (event, count,data) => { + setParamValues((prev) => { + const newValues = [...prev]; + newValues[count] = event.target.value; + return newValues; + } + ); + changeActionParameter(event, count, data) + } const calculateHelpertext = (input_data) => { var helperText = "" var looperText = "" @@ -1574,7 +1589,7 @@ const ParsedAction = (props) => { MenuProps={{ disableScrollLock: true, }} - defaultValue={selectedAction.app_version} + value={selectedAction.app_version} onChange={(event) => { const newversion = selectedApp.versions.find( (tmpApp) => tmpApp.version == event.target.value @@ -2340,22 +2355,23 @@ const ParsedAction = (props) => { /> ); }} - renderInput={(params) => { - if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) { - const prefixes = ["Post", "Put", "Patch"] - for (let [key,keyval] in Object.entries(prefixes)) { - if (params.inputProps.value.startsWith(prefixes[key])) { - params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1) - if (params.inputProps.value.length > 1) { - params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1) + renderInput={(params) => { + if (params.inputProps?.value) { + const prefixes = ["Post", "Put", "Patch"]; + for (let prefix of prefixes) { + if (params.inputProps.value.startsWith(prefix)) { + let newValue = params.inputProps.value.replace(prefix + " ", ""); + if (newValue.length > 1) { + newValue = newValue.charAt(0).toUpperCase() + newValue.substring(1); } - break + // Set the new value without mutating inputProps + params = { ...params, inputProps: { ...params.inputProps, value: newValue } }; + break; } } - // Check if it starts with "Get List" and method is "Get" if (params.inputProps.value.startsWith("Get List")) { - console.log("Get List") + console.log("Get List"); } } @@ -2802,9 +2818,9 @@ const ParsedAction = (props) => { placeholder = data.example; - if (data.name === "url") { - data.value = data.example; - } + // if (data.name === "url") { + // data.value = data.example; + // } // In case of data.example if (data.value === undefined || data.value === null) { data.value = "" @@ -3046,7 +3062,6 @@ const ParsedAction = (props) => { } } //selectedActionParameters - if (changed) { // Sort selectedActionParameters based on selectedActionParameters.required //selectedActionParameters.sort((a, b) => (a.required < b.required) ? 1 : -1) @@ -3175,8 +3190,8 @@ const ParsedAction = (props) => { id={clickedFieldId} rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" - defaultValue={data.value} - // value={data.value} + // defaultValue={data.value} + value={paramValues[count] !== undefined ? paramValues[count] : data.value} //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', @@ -3196,7 +3211,8 @@ const ParsedAction = (props) => { placeholder={placeholder} onChange={(event) => { //changeActionParameterCodemirror(event, count, data) - changeActionParameter(event, count, data); + // changeActionParameter(event, count, data); + handleParamChange(event, count, data) }} helperText={returnHelperText(data.name, data.value)} onBlur={(event) => { From a56b5ed8bf35fca0469368eb2eebbd4fca26b8ae Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 10 Jun 2024 15:18:48 +0530 Subject: [PATCH 14/46] Fixed the delay issue --- frontend/src/components/ParsedAction.jsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 2d428ca8..70a5970a 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -390,16 +390,15 @@ const ParsedAction = (props) => { console.log("UseEffect Rendered!") console.log("Workflow", workflow) setAppActionName(selectedAction.label) - setDelay(selectedAction?.execution_delay) - + setDelay(selectedAction?.execution_delay || 0) + if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { + console.log("Setting action parameters!!") + setSelectedActionParameters(selectedAction.parameters); + // } + } setParamValues(selectedAction.parameters.map((param) => param.value) || []) // if (selectedActionParameters !== undefined && selectedActionParameters !== null // ) { - if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { - console.log("Setting action parameters!!") - setSelectedActionParameters(selectedAction.parameters); - // } - } if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) { // FIXME - this is the bad thing From 5b48e290d723eaf9221602b4c02954a272b1e9c3 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Tue, 11 Jun 2024 16:09:01 +0530 Subject: [PATCH 15/46] Fixed the bug with param value change --- frontend/src/components/ParsedAction.jsx | 39 ++++++++++++++++-------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 70a5970a..af509c51 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -183,11 +183,16 @@ const ParsedAction = (props) => { const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); const [autoCompleting, setAutocompleting] = React.useState(false); - const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction.parameters); + const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction?.parameters || []); const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); const [paramValues, setParamValues] = React.useState( - selectedAction.parameters.map((param) => param.value) || [] - ); + selectedAction?.parameters.map((param) => { + return { + name: param.name, + value: param.value, + } + }) + ); const [actionlist, setActionlist] = React.useState([]); const [jsonList, setJsonList] = React.useState([]); const [showDropdown, setShowDropdown] = React.useState(false); @@ -201,8 +206,17 @@ const ParsedAction = (props) => { setLastSaved(false) } }, [expansionModalOpen]) + useEffect(() => { + setParamValues(selectedAction.parameters.map((param) => { + return { + name: param.name, + value: param.value, + } + })) + },[ + selectedAction, selectedApp,setNewSelectedAction + ]) - useEffect(() => { if (selectedAction.parameters === null || selectedAction.parameters === undefined) { return @@ -396,7 +410,6 @@ const ParsedAction = (props) => { setSelectedActionParameters(selectedAction.parameters); // } } - setParamValues(selectedAction.parameters.map((param) => param.value) || []) // if (selectedActionParameters !== undefined && selectedActionParameters !== null // ) { if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) { @@ -638,14 +651,14 @@ const ParsedAction = (props) => { console.log("selectedAction: ", selectedAction) console.log("ACTIONLIST: ", actionlist) console.log("selectedVariableParameter", selectedVariableParameter) - const handleParamChange = (event, count,data) => { - setParamValues((prev) => { - const newValues = [...prev]; - newValues[count] = event.target.value; - return newValues; - } - ); + const newParams = [...paramValues]; + newParams.map((param) => { + if (param.name === data.name) { + param.value = event.target.value; + } + }) + setParamValues(newParams); changeActionParameter(event, count, data) } const calculateHelpertext = (input_data) => { @@ -3190,7 +3203,7 @@ const ParsedAction = (props) => { rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" // defaultValue={data.value} - value={paramValues[count] !== undefined ? paramValues[count] : data.value} + value={data.value} //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', From 70a313d40a11cc567f2ef8f0c865ef31fe7fde3b Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Tue, 11 Jun 2024 16:23:31 +0530 Subject: [PATCH 16/46] Fixed the autoComplete issue:Closecase in Hive --- frontend/src/components/ParsedAction.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index af509c51..12f81ad8 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -885,10 +885,12 @@ const ParsedAction = (props) => { } //console.log("CHANGING ACTION COUNT !") - selectedActionParameters[count].autocompleted = false - selectedAction.parameters[count].autocompleted = false - selectedActionParameters[count].value = event.target.value; - selectedAction.parameters[count].value = event.target.value; + setTimeout(() => { + // selectedActionParameters[count].autocompleted = false + selectedAction.parameters[count].autocompleted = false + // selectedActionParameters[count].value = event.target.value; + selectedAction.parameters[count].value = event.target.value; + },100) var forceUpdate = false if (isCloud && (selectedAction.app_name === "Shuffle Tools" || selectedAction.app_name === "email") && (selectedAction.name === "send_email_shuffle" || selectedAction.name === "send_sms_shuffle") && data.name === "apikey") { @@ -3994,7 +3996,7 @@ const ParsedAction = (props) => { } const buttonTitle = `Authenticate ${selectedApp.name.replaceAll("_", " ")}` - const hasAutocomplete = data.autocompleted === true + const hasAutocomplete = data?.autocompleted === true return (
    {hideBodyButton} From df8493ae69eb2cc9722cd6c5b4f884381d0d6e45 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Tue, 11 Jun 2024 16:32:01 +0530 Subject: [PATCH 17/46] Solved the param change issue --- frontend/src/components/ParsedAction.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 12f81ad8..5f7edc20 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -3205,7 +3205,11 @@ const ParsedAction = (props) => { rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" // defaultValue={data.value} - value={data.value} + value={ + paramValues.find((param) => param.name === data.name) !== undefined + ? paramValues.find((param) => param.name === data.name).value + : data.value + } //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', From bcda8c6143d26ead65960486a7dee7c760cf1631 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Tue, 11 Jun 2024 17:11:55 +0530 Subject: [PATCH 18/46] Fixed the dropdown bug in UserInputSidebar --- frontend/src/views/AngularWorkflow.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index bece66c2..be234c12 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -14944,6 +14944,7 @@ const AngularWorkflow = (defaultprops) => { onChange={(event, newValue) => { console.log("Changed autocomplete!") handleWorkflowSelectionUpdate({ target: { value: newValue } }, true) + event.target.blur(); }} renderOption={(props, data, state) => { if (data.id === workflow.id) { @@ -14972,6 +14973,7 @@ const AngularWorkflow = (defaultprops) => { value: data, }}, true) + document.activeElement.blur(); }} > From e87b112ff6fc521e78477d6a1bee71d41ce5b936 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Wed, 12 Jun 2024 12:59:09 +0530 Subject: [PATCH 19/46] Fixed the bug --- frontend/src/components/ParsedAction.jsx | 390 +++++++++-------------- 1 file changed, 154 insertions(+), 236 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 5f7edc20..c02afb09 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -401,244 +401,160 @@ const ParsedAction = (props) => { useEffect( () => { - console.log("UseEffect Rendered!") - console.log("Workflow", workflow) - setAppActionName(selectedAction.label) - setDelay(selectedAction?.execution_delay || 0) - if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { - console.log("Setting action parameters!!") - setSelectedActionParameters(selectedAction.parameters); - // } - } - // if (selectedActionParameters !== undefined && selectedActionParameters !== null - // ) { - if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) { - - // FIXME - this is the bad thing - setSelectedVariableParameter(workflow.workflow_variables[0].name); - } - - if (actionlist.length === 0) { - // FIXME: Have previous execution values in here - if (workflowExecutions.length > 0) { - for (let [key,keyval] in Object.entries(workflowExecutions)) { - if ( - workflowExecutions[key].execution_argument === undefined || - workflowExecutions[key].execution_argument === null || - workflowExecutions[key].execution_argument.length === 0 - ) { - continue; - } - - const valid = validateJson(workflowExecutions[key].execution_argument) - if (valid.valid) { - actionlist.push({ - type: "Execution Argument", - name: "Execution Argument", - value: "$exec", - highlight: "exec", - autocomplete: "exec", - example: valid.result, - }) - break - } - } - + console.log("UseEffect Rendered!"); + console.log("Workflow", workflow); + + // Only set app action name if it has changed + if (selectedAction.label !== appActionName) { + setAppActionName(selectedAction.label); } - + + // Only set delay if it has changed + const newDelay = selectedAction?.execution_delay || 0; + if (newDelay !== delay) { + setDelay(newDelay); + } + + // Only set selected action parameters if they have changed + if (selectedAction.parameters && selectedAction.parameters.length > 0) { + console.log("Setting action parameters!!"); + setSelectedActionParameters(selectedAction.parameters); + } + + // Only set selected variable parameter if it is null or undefined + if (!selectedVariableParameter && workflow.workflow_variables?.length > 0) { + setSelectedVariableParameter(workflow.workflow_variables[0].name); + } + + // Initialize action list if it is empty if (actionlist.length === 0) { - actionlist.push({ - type: "Execution Argument", - name: "Execution Argument", - value: "$exec", - highlight: "exec", - autocomplete: "exec", - example: "", - }) - } - - /* - actionlist.push({ - type: "Shuffle DB", - name: "Shuffle DB", - value: "$shuffle_cache", - highlight: "shuffle_cache", - autocomplete: "shuffle_cache", - example: { - "what": "", - "unique gmail ids new": "", - }, - }) - */ - - var cachekey = { - type: "Shuffle DB", - name: "Shuffle DB", - value: "$shuffle_cache", - highlight: "shuffle_cache", - autocomplete: "shuffle_cache", - example: "", - } - - if (listCache !== undefined && listCache !== null && listCache.keys !== undefined && listCache.keys !== null && listCache.keys.length > 0) { - cachekey.example = {} - - for (var i in listCache.keys) { - const item = listCache.keys[i] - if (item.key === undefined || item.key === null || item.key.length === 0) { - continue - } - - var itemvalue = item.value === undefined || item.value === null ? "" : item.value - try{ - if (itemvalue.length > 10000) { - itemvalue = "" - } - - } catch (e) { - itemvalue = "" - } - - var itemkey = item.key.split(" ").join("_") - cachekey.example[itemkey] = { - "value": itemvalue, - } - } - } else { - } - - actionlist.push(cachekey) - - if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) { - for (let [key,keyval] in Object.entries(workflow.workflow_variables)) { - const item = workflow.workflow_variables[key]; - actionlist.push({ - type: "workflow_variable", - name: item.name, - value: item.value, - id: item.id, - autocomplete: `${item.name.split(" ").join("_")}`, - example: item.value, - }); - } - } - - if (workflow.execution_variables !== null && workflow.execution_variables !== undefined && workflow.execution_variables.length > 0) { - for (let [key,keyval] in Object.entries(workflow.execution_variables)) { - const item = workflow.execution_variables[key] - - var exampleoutput = "" - for (let execkey in workflowExecutions) { - const exec = workflowExecutions[execkey] - if (exec["execution_variables"] === undefined || exec["execution_variables"] === null) { - continue - } - - const foundExec = exec.execution_variables.find((exvar) => exvar.name === item.name) - if (!foundExec) { - continue - } - - if (foundExec.value !== undefined && foundExec.value !== null && foundExec.value.length > 0) { - exampleoutput = foundExec.value - break - } - } - - actionlist.push({ - type: "execution_variable", - name: item.name, - value: item.value, - id: item.id, - autocomplete: `${item.name.split(" ").join("_")}`, - example: exampleoutput, - }); - } - } - - // Loops parent nodes' old results to fix autocomplete - if (getParents !== undefined) { - var parents = getParents(selectedAction) - - if (parents.length > 1) { - var labels = [] - //for (let [parentkey, parentkeyval] in Object.entries(parents)) { - for (let parentkey in parents) { - const parentNode = parents[parentkey] - if (parentNode.label === "Execution Argument") { - continue - } - - //if (labels.includes(item.label)) { - // continue - //} - - labels.push(parentNode.label) - - var exampledata = parentNode.example === undefined || parentNode.example === null ? "" : parentNode.example - // Find previous execution and their variables - //exampledata === "" && - if (workflowExecutions.length > 0) { - // Look for the ID - const found = false; - for (let wfkey in workflowExecutions) { - if (workflowExecutions[wfkey].results === undefined || workflowExecutions[wfkey].results === null) { - - continue; - } - - var foundResult = workflowExecutions[wfkey].results.find((result) => result.action.id === parentNode.id) - - if (foundResult === undefined || foundResult === null) { - continue - } - - if (foundResult.result !== undefined && foundResult.result !== null) { - foundResult = foundResult.result - } - - const valid = validateJson(foundResult) - if (valid.valid) { - if (valid.result.success === false) { - //console.log("Skipping success false autocomplete") - } else { - - // FIXME: Have a merge system to allow to use kind of any key from that node in the last 10-20 execs - //if (exampledata.length > 0) { - // exampledata = valid.result - //} else { - // exampledata = valid.result - //} - - exampledata = valid.result - break + if (workflowExecutions.length > 0) { + for (let [key, keyval] of Object.entries(workflowExecutions)) { + const execArg = workflowExecutions[key].execution_argument; + if (execArg && execArg.length > 0) { + const valid = validateJson(execArg); + if (valid.valid) { + actionlist.push({ + type: "Execution Argument", + name: "Execution Argument", + value: "$exec", + highlight: "exec", + autocomplete: "exec", + example: valid.result, + }); + break; + } } - } else { - exampledata = foundResult } - } - } - - // 1. Take - const itemlabelComplete = parentNode.label === null || parentNode.label === undefined ? "" : parentNode.label.split(" ").join("_"); - - const actionvalue = { - type: "action", - id: parentNode.id, - name: parentNode.label, - autocomplete: itemlabelComplete, - example: exampledata, - } - - actionlist.push(actionvalue) - } - } - - setActionlist(actionlist); - } - } - }, - [selectedAction,selectedApp,setNewSelectedAction] + } + + if (actionlist.length === 0) { + actionlist.push({ + type: "Execution Argument", + name: "Execution Argument", + value: "$exec", + highlight: "exec", + autocomplete: "exec", + example: "", + }); + } + + let cacheKey = { + type: "Shuffle DB", + name: "Shuffle DB", + value: "$shuffle_cache", + highlight: "shuffle_cache", + autocomplete: "shuffle_cache", + example: "", + }; + + if (listCache?.keys?.length > 0) { + cacheKey.example = {}; + for (let item of listCache.keys) { + if (item.key) { + let itemValue = item.value ?? ""; + if (itemValue.length > 10000) { + itemValue = ""; + } + cacheKey.example[item.key.split(" ").join("_")] = { value: itemValue }; + } + } + } + + actionlist.push(cacheKey); + + if (workflow.workflow_variables?.length > 0) { + for (let [key, keyval] of Object.entries(workflow.workflow_variables)) { + const item = workflow.workflow_variables[key]; + actionlist.push({ + type: "workflow_variable", + name: item.name, + value: item.value, + id: item.id, + autocomplete: item.name.split(" ").join("_"), + example: item.value, + }); + } + } + + if (workflow.execution_variables?.length > 0) { + for (let [key, keyval] of Object.entries(workflow.execution_variables)) { + const item = workflow.execution_variables[key]; + let exampleOutput = ""; + for (let exec of workflowExecutions) { + const foundExec = exec.execution_variables?.find(exvar => exvar.name === item.name); + if (foundExec?.value) { + exampleOutput = foundExec.value; + break; + } + } + actionlist.push({ + type: "execution_variable", + name: item.name, + value: item.value, + id: item.id, + autocomplete: item.name.split(" ").join("_"), + example: exampleOutput, + }); + } + } + + if (getParents) { + const parents = getParents(selectedAction); + if (parents.length > 1) { + const labels = []; + for (let parentNode of parents) { + if (parentNode.label !== "Execution 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; + } + } + } + } + actionlist.push({ + type: "action", + id: parentNode.id, + name: parentNode.label, + autocomplete: parentNode.label.split(" ").join("_"), + example: exampleData, + }); + } + } + } + } + + setActionlist(actionlist); + } + }, + [selectedAction,selectedApp,setNewSelectedAction,workflow, workflowExecutions, listCache, getParents, actionlist] ); useEffect(() => { @@ -886,9 +802,9 @@ const ParsedAction = (props) => { //console.log("CHANGING ACTION COUNT !") setTimeout(() => { - // selectedActionParameters[count].autocompleted = false + selectedActionParameters[count].autocompleted = false selectedAction.parameters[count].autocompleted = false - // selectedActionParameters[count].value = event.target.value; + selectedActionParameters[count].value = event.target.value; selectedAction.parameters[count].value = event.target.value; },100) @@ -1060,10 +976,12 @@ const ParsedAction = (props) => { } } + setTimeout(() => { selectedActionParameters[count].autocompleted = false selectedAction.parameters[count].autocompleted = false selectedActionParameters[count].value = data selectedAction.parameters[count].value = data + }, 100); setSelectedAction(selectedAction) //setUpdate(Math.random()) //setUpdate(event.target.value) From da4e25f6c9c97c1058a6dad089ab5e10c6999b74 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Wed, 12 Jun 2024 23:38:52 +0530 Subject: [PATCH 20/46] Fixes in ParsedAction --- frontend/src/components/ParsedAction.jsx | 33 ++++++++++++++---------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index c02afb09..60e87f22 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -206,16 +206,16 @@ const ParsedAction = (props) => { setLastSaved(false) } }, [expansionModalOpen]) - useEffect(() => { - setParamValues(selectedAction.parameters.map((param) => { - return { - name: param.name, - value: param.value, - } - })) - },[ - selectedAction, selectedApp,setNewSelectedAction - ]) +// useEffect(() => { +// setParamValues(selectedAction.parameters.map((param) => { +// return { +// name: param.name, +// value: param.value, +// } +// })) +// },[ +// selectedAction, selectedApp,setNewSelectedAction +// ]) useEffect(() => { if (selectedAction.parameters === null || selectedAction.parameters === undefined) { @@ -404,6 +404,12 @@ const ParsedAction = (props) => { console.log("UseEffect Rendered!"); console.log("Workflow", workflow); + setParamValues(selectedAction.parameters.map((param) => { + return { + name: param.name, + value: param.value, + } + })) // Only set app action name if it has changed if (selectedAction.label !== appActionName) { setAppActionName(selectedAction.label); @@ -554,14 +560,13 @@ const ParsedAction = (props) => { setActionlist(actionlist); } }, - [selectedAction,selectedApp,setNewSelectedAction,workflow, workflowExecutions, listCache, getParents, actionlist] + [selectedAction,selectedApp,setNewSelectedAction,workflow, workflowExecutions, getParents] ); - useEffect(() => { selectedNameChange(appActionName) actionDelayChange(delay) },[appActionName,delay]) - + console.log("selectedActionParameters: ", selectedActionParameters) console.log("selectedApp:", selectedApp) console.log("selectedAction: ", selectedAction) @@ -3126,7 +3131,7 @@ const ParsedAction = (props) => { value={ paramValues.find((param) => param.name === data.name) !== undefined ? paramValues.find((param) => param.name === data.name).value - : data.value + : "" } //options={{ // theme: 'gruvbox-dark', From 50dcf618a0f1fcc668a1e914b2c9ca02066d5f11 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 13 Jun 2024 00:04:33 +0530 Subject: [PATCH 21/46] Changes --- frontend/src/views/AngularWorkflow.jsx | 546 +++++++------------------ 1 file changed, 144 insertions(+), 402 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index be234c12..2f9a123d 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -553,7 +553,7 @@ const AngularWorkflow = (defaultprops) => { }, [editWorkflowModalOpen]) // New for generated stuff - const releaseToConnectLabel = "Release to Connect" +const releaseToConnectLabel = "Release to Connect" const integrationApps = [{ "id": "integration", "name": "Integration Framework", @@ -1446,18 +1446,18 @@ const AngularWorkflow = (defaultprops) => { trigger.parameters = [] - const topic = document.getElementById('topic')?.value - const bootstrapServers = document.getElementById('bootstrap_servers')?.value - const groupId = document.getElementById('group_id')?.value + const topic = document.getElementById('topic')?.value; + const bootstrapServers = document.getElementById('bootstrap_servers')?.value; + const groupId = document.getElementById('group_id')?.value; //const autoOffsetReset = document.getElementById('auto_offset_reset')?.value; if(topic) { trigger.parameters.push({ name: "topic", value: topic - }) + }); } else { - toast("Please enter the topic name"); + toast("please enter the topic name"); return; } @@ -3283,19 +3283,12 @@ const AngularWorkflow = (defaultprops) => { // don't redirect if it exists const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; 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..`) - setTimeout(() => { - window.location.pathname = "/workflows"; - }, 2000); - } else if (sessionToken !== null && workflow_id === "3abdfb21-b40f-4e50-b855-ac0d62f83cbe") { - toast(`Injecting session token and reloading workflow..`) - setTimeout(() => { - setCookie("session_token", sessionToken, { path: "/" }); - window.location.href = "https://shuffler.io/workflows/3abdfb21-b40f-4e50-b855-ac0d62f83cbe"; - }, 2000); - } + if (execFound === null) { + toast(`You don't access to this workflow or loading failed. Redirecting to workflows in a few seconds..`) + setTimeout(() => { + window.location.pathname = "/workflows"; + }, 2000); + } } } @@ -3776,28 +3769,20 @@ const AngularWorkflow = (defaultprops) => { const onNodeDragStop = (event, selectedAction) => { const nodedata = event.target.data(); if (nodedata.id === selectedAction.id) { - //console.log("Same node, return") - return + return; } if (nodedata.finished === false) { - //console.log("Node is not finished, return") - return + return; } const connected = event.target.connectedEdges().jsons() if (connected.length > 0 && connected !== undefined) { for (let connectkey in connected) { const edge = connected[connectkey] - if (edge.data.decorator && edge.data.label === releaseToConnectLabel) { - // Transform to normal edge - const currentedge = cy.getElementById(edge.data.id) - if (currentedge !== undefined && currentedge !== null) { - currentedge.data("decorator", false) - currentedge.data("label", "") - } - continue - } + //console.log("EDGE:", edge) + + //const edge = edgeBase.json() const sourcenode = cy.getElementById(edge.data.source) const destinationnode = cy.getElementById(edge.data.target) @@ -4012,141 +3997,26 @@ const AngularWorkflow = (defaultprops) => { } if (nodedata.id === selectedAction.id) { - return + return; } - - if ((nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT" || nodedata.type === "ACTION") && !nodedata.isStartNode) { - // Check if it already has any non-decorator branches attached to it - const branches = cy.elements('edge').jsons() - var branchFound = false - var decoratorIds = [] - for (var branchkey in branches) { - if (branches[branchkey].data.source === nodedata.id || branches[branchkey].data.target === nodedata.id) { - - if (branches[branchkey].data.decorator === true) { - - // Add the source/destination - if (branches[branchkey].data.source === nodedata.id) { - decoratorIds.push(branches[branchkey].data.target) - } else { - decoratorIds.push(branches[branchkey].data.source) - } - - continue - } - - branchFound = true - break - } - } - - if (!branchFound) { - //console.log("Found action during drag. Checking closest nodes as it doesn't have a valid branch") - var closestNode = null - var minDistance = 300 - - const draggedNode = event.target - const allnodes = cy.nodes().jsons() - for (var nodekey in allnodes) { - const node = allnodes[nodekey] - if (node.data.id === nodedata.id) { - continue - } - - // Decorators - if (node.data.attachedTo !== undefined) { - continue - } - - if (node.position === undefined || node.position === null || node.position.x === undefined || node.position.y === undefined) { - continue - } - - if (node.data.type !== "ACTION" && node.data.type !== "TRIGGER") { - continue - } - - const distance = Math.sqrt( - Math.pow(draggedNode.position('x') - node.position.x, 2) + - Math.pow(draggedNode.position('y') - node.position.y, 2) - ) - - if (decoratorIds.includes(node.data.id)) { - //console.log("Found existing decorator for: ", node.data.app_name, "Distance: ", distance) - - if (distance > 300) { - // Remove the branch - const edgeToRemove = cy.getElementById(branches[branchkey].data.id) - if (edgeToRemove !== null && edgeToRemove !== undefined) { - //console.log("Removing edge: ", edgeToRemove) - edgeToRemove.remove() - //decoratorIds.splice(decoratorIds.indexOf(node.data.id), 1) - break - } - } - } - if (distance < minDistance) { - minDistance = distance - closestNode = node - } - } + /* + // Tried looking for the closest node by position. aStar path not working entirely. + console.log("NODE: ", event.target) + const closestNode = cy.elements().aStar({ + root: nodedata.id, + goal: 'node', + directed: false, + }) - if (closestNode !== null && closestNode !== undefined) { - //console.log("Closest node app: ", closestNode.data.app_name, "Distance: ", minDistance) - - /* - if (decoratorIds.length > 0) { - console.log("Decorators already exists. If within distance of 15 add to existing, otherwise remove old and add new: ", decoratorIds) - for (var decoratorkey in decoratorIds) { - const decoratorEdge = cy.getElementById(decoratorIds[decoratorkey]) - if (decoratorEdge === null || decoratorEdge === undefined) { - continue - } - - const sourceNode = cy.getElementById(decoratorEdge.data.source) - const targetNode = cy.getElementById(decoratorEdge.data.target) - - const distance = Math.sqrt( - Math.pow(draggedNode.position('x') - sourceNode.position('x'), 2) + - Math.pow(draggedNode.position('y') - sourceNode.position('y'), 2) - ) - - // Check plus minus 15 in distance from mindistance - if (distance > minDistance - 15 && distance < minDistance + 15) { - console.log("Within distance of 15, add to existing edge") - } else { - console.log("Outside distance of 15, remove old edge and add new") - } - - } - } - */ - - if (decoratorIds.length === 0) { - //const edgeCurve = calculateEdgeCurve(draggedNode.position(), closestNode.position) - //currentedge.style('control-point-distance', edgeCurve.distance) - //currentedge.style('control-point-weight', edgeCurve.weight) - - const newId = uuidv4() - cy.add({ - group: "edges", - data: { - decorator: true, - id: newId, - _id: newId, - source: closestNode.data.id, - target: nodedata.id, - label: releaseToConnectLabel, - conditions: [], - } - }) - } - } - } - } + if (closestNode.found) { + console.log("No closest node found for: ", nodedata.id) + } else { + console.log("Closest: ", closestNode) + } + */ if ( originalLocation.x === 0 && @@ -4205,11 +4075,11 @@ const AngularWorkflow = (defaultprops) => { } // Ensure it only happens once - document.removeEventListener("mousemove", onMouseUpdate, false) - } + document.removeEventListener("mousemove", onMouseUpdate, false); + }; - document.addEventListener("mousemove", onMouseUpdate, false) - } + document.addEventListener("mousemove", onMouseUpdate, false); + }; useBeforeunload(() => { @@ -4222,7 +4092,7 @@ const AngularWorkflow = (defaultprops) => { document.removeEventListener("paste", handlePaste, true); } } - }) + }); // Should get AI autocompletes const aiSubmit = (value, setResponseMsg, setSuggestionLoading, inputAction) => { @@ -5624,10 +5494,11 @@ const AngularWorkflow = (defaultprops) => { // Checks for errors in edges when they're added const onEdgeAdded = (event) => { - const edge = event.target.data() - //console.log("EDGE ADDED!: ", edge) + setLastSaved(false); + const edge = event.target.data(); + + //console.log("edge added: ", edge) if (edge.source === undefined && edge.target === undefined) { - console.log("Edge source and target is undefined") return } @@ -5641,7 +5512,6 @@ const AngularWorkflow = (defaultprops) => { const sourcenode = cy.getElementById(edge.source) const destinationnode = cy.getElementById(edge.target) if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { - console.log("Source or destination node is undefined") } else { //console.log("Edge added: Is it a trigger? If so, check if it already has a branch and remove it: ", sourcenode.data()) if (sourcenode.data("type") === "TRIGGER") { @@ -5654,10 +5524,10 @@ const AngularWorkflow = (defaultprops) => { console.log("Node: ", targetedge) if (targetedge !== -1) { + event.target.remove() //console.log("Found branch already!") toast.error("Triggers can have exactly one target node") - event.target.remove() return @@ -5678,10 +5548,6 @@ const AngularWorkflow = (defaultprops) => { } } - if (edge.decorator === true) { - console.log("Doing nothing to branch because decorator") - return - } var targetnode = workflow.triggers.findIndex( (data) => data.id === edge.target @@ -5721,14 +5587,15 @@ const AngularWorkflow = (defaultprops) => { } } - if (eventTarget.data("isDescriptor") === true || eventTarget.data("type") === "COMMENT") { + if ( + eventTarget.data("isDescriptor") === true || + eventTarget.data("type") === "COMMENT" + ) { console.log("Removing because of descriptor or comment") - event.target.remove() - return + event.target.remove(); + return; } - - setLastSaved(false) targetnode = -1; // Check if: @@ -5736,51 +5603,38 @@ const AngularWorkflow = (defaultprops) => { // dest == dest && source == source // backend: check all children? to stop recursion var found = false; - const branches = cy.edges().jsons() - - const startNode = cy.nodes().jsons().find((node) => node.data.isStartNode === true) - var startnodeId = workflow.start - if (startNode !== undefined && startNode !== null) { - startnodeId = startNode.data.id - } - - //for (let branchkey in workflow.branches) { - for (let branchkey in branches) { - const branch = branches[branchkey].data - - //if (workflow.branches[branchkey].destination_id === edge.source && workflow.branches[branchkey].source_id === edge.target) { - if (branch.target === edge.source && branch.source === edge.target) { - toast("A branch in the opposite direction already exists") - event.target.remove() - found = true - break - - //} else if (workflow.branches[branchkey].destination_id === edge.target && workflow.branches[branchkey].source_id === edge.source) { - } else if (branch.target === edge.target && branch.source === edge.source) { - - if (branch.conditions === undefined) { - // Edgehandles - } else { - console.log("Removing because the same branch already exists") - event.target.remove() - - found = true - break - } - } else if (edge.target === startnodeId) { - targetnode = workflow.triggers.findIndex((data) => data.id === edge.source) + 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; + } else if ( + workflow.branches[branchkey].destination_id === edge.target && + workflow.branches[branchkey].source_id === edge.source + ) { + //toast("That branch already exists"); + event.target.remove(); + found = true; + break; + } else if (edge.target === workflow.start) { + targetnode = workflow.triggers.findIndex( + (data) => data.id === edge.source + ); if (targetnode === -1) { if (targetnode.type !== "TRIGGER") { - toast("Can't make arrow to starting node") - event.target.remove() - break + toast("Can't make arrow to starting node"); + event.target.remove(); + break; } found = true; } - //} else if (edge.source === workflow.branches[branchkey].source_id) { - } else if (edge.source === branch.source) { + } else if (edge.source === workflow.branches[branchkey].source_id) { // FIXME: Verify multi-target for triggers // 1. Check if destination exists // 2. Check if source is a trigger @@ -5824,6 +5678,7 @@ const AngularWorkflow = (defaultprops) => { newdst !== null ) { const dstdata = RunAutocompleter(newdst.data()); + //console.log("DST Autocompleter: ", dstdata); } var newbranch = { @@ -11538,101 +11393,11 @@ const AngularWorkflow = (defaultprops) => { {/* Check if dest is the same as start */} - {conditionsDisabled ? - - Conditions are unavailable between triggers and the startnode. - - : null} - -
    - {/* - - - - */} - -
    + {conditionsDisabled ? + + Conditions are unavailable between triggers and the startnode. + + : null}
    ); }; @@ -12396,7 +12161,7 @@ const AngularWorkflow = (defaultprops) => { return transformedData; - } + }; const AppAuthSelector = ({ appAuthData }) => { const [selectedAuth, setSelectedAuth] = useState(""); @@ -12409,7 +12174,6 @@ const AngularWorkflow = (defaultprops) => { const handleShowingValue = (appName) => { let mappingWithName = {} let listWithValues = workflow.triggers[selectedTriggerIndex].parameters[5]?.value.split(";").filter(e => e).map(e => e.split("=")) - console.log("LIST WITH VALUES: ", listWithValues) for (let i = 0; i < listWithValues.length; i++) { mappingWithName[listWithValues[i][0]] = listWithValues[i][1] @@ -13092,7 +12856,7 @@ const AngularWorkflow = (defaultprops) => { data: newbranch, }; - cy.add(cybranch) + cy.add(cybranch); } console.log("Value to be set: ", e.target.value); @@ -13636,27 +13400,25 @@ const AngularWorkflow = (defaultprops) => {
    - {/*
    -
    -
    -
    - Auth Override -
    -
    +
    +
    +
    + Auth Override +
    +
    -
    -
    - -
    -
    -
    +
    +
    + +
    +
    +
    - */}
    - ) + ); } return null; @@ -15610,8 +15372,12 @@ const AngularWorkflow = (defaultprops) => { right: 0, left: isMobile ? 20 : leftBarSize + 20, top: isMobile ? 30 : appBarSize + 20, + pointerEvents: "none", } + + + const TopCytoscapeBar = (props) => { if (workflow.public === true) { return null @@ -15627,7 +15393,7 @@ const AngularWorkflow = (defaultprops) => {
    {

    {workflow.name}

    @@ -16376,8 +16143,6 @@ const AngularWorkflow = (defaultprops) => { } } - /* - // Infinitely annoying. Need a new bind if (( event.ctrlKey || event.metaKey ) && event.shiftKey) { console.log("Shift key pressed") if (!workflow.public && executionModalOpen) { @@ -16389,8 +16154,7 @@ const AngularWorkflow = (defaultprops) => { setExecutionModalView(0); } } - */ - } + }; document.addEventListener('keydown', handleKeyDown); @@ -18260,7 +18024,7 @@ const AngularWorkflow = (defaultprops) => {
    {foundnotifications > 0 ? - + { @@ -19319,11 +19083,10 @@ const AngularWorkflow = (defaultprops) => { return "The queries or data sent to the API is most likely wrong (400). Check the body of the result for more information." } - /* if (result.status === 200 || result.status === 201 || result.status === 204) { return "It looks like the result was successful! If it didn't work, make sure to check if the body you are sending was correct." } - */ + // Validate and check for newlines if (result.success !== false) { @@ -21012,9 +20775,8 @@ const AngularWorkflow = (defaultprops) => {
    Configuration options for {selectedOption}
    - - {selectedOption === "Kafka Queue" ? -
    + {selectedOption === "Kafka Queue" && ( + <> Topic { placeholder={"earliest"} defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || ''} /> */} -
    - : null} - - param.name === "bootstrap_servers")?.value) || ''} - /> - + + )}
    - ); - }; - + if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { if (workflow.triggers[selectedTriggerIndex] === undefined) { return null; @@ -13360,10 +13062,298 @@ const releaseToConnectLabel = "Release to Connect" }} /> {!showDropdown ? null : - + { + handleMenuClose(); + }} + open={!!menuPosition} + style={{ + border: `2px solid #f85a3e`, + color: "white", + marginTop: 2, + }} + > + {actionlist.map((innerdata) => { + const icon = + innerdata.type === "action" ? ( + + ) : innerdata.type === "workflow_variable" || + innerdata.type === "execution_variable" ? ( + + ) : ( + + ); + + const handleExecArgumentHover = (inside) => { + var exec_text_field = document.getElementById( + "execution_argument_input_field" + ); + if (exec_text_field !== null) { + if (inside) { + exec_text_field.style.border = "2px solid #f85a3e"; + } else { + exec_text_field.style.border = ""; + } + } + + // Also doing arguments + if ( + workflow.triggers !== undefined && + workflow.triggers !== null && + workflow.triggers.length > 0 + ) { + for (let triggerkey in workflow.triggers) { + const item = workflow.triggers[triggerkey]; + + if (cy !== undefined) { + var node = cy.getElementById(item.id); + if (node.length > 0) { + if (inside) { + node.addClass("shuffle-hover-highlight"); + } else { + node.removeClass("shuffle-hover-highlight"); + } + } + } + } + } + } + + const handleActionHover = (inside, actionId) => { + if (cy !== undefined) { + var node = cy.getElementById(actionId); + if (node.length > 0) { + if (inside) { + node.addClass("shuffle-hover-highlight"); + } else { + node.removeClass("shuffle-hover-highlight"); + } + } + } + }; + + const handleMouseover = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(true); + } else if (innerdata.type === "action") { + handleActionHover(true, innerdata.id); + } + }; + + const handleMouseOut = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(false); + } else if (innerdata.type === "action") { + handleActionHover(false, innerdata.id); + } + }; + + var parsedPaths = []; + console.log("Found example data: ", innerdata.example) + if (typeof innerdata.example === "object") { + parsedPaths = GetParsedPaths(innerdata.example, ""); + } + + const coverColor = "#82ccc3" + + return parsedPaths.length > 0 ? ( + + {/* + + {icon} {innerdata.name} +
    + } + parentMenuOpen={!!menuPosition} + style={{ + backgroundColor: theme.palette.inputColor, + color: "white", + minWidth: 250, + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ) + + return ( + { }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
    + {icon} {pathdata.name} +
    +
    +
    + ); + })} + + */} + + + {icon} {innerdata.name} + + } + parentMenuOpen={!!menuPosition} + style={{ + color: "white", + minWidth: 250, + maxWidth: 250, + maxHeight: 50, + overflow: "hidden", + }} + onClick={() => { + console.log("CLICKED: ", innerdata); + console.log(innerdata.example) + handleItemClick([innerdata]); + }} + > + + + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + + {innerdata.name} + + + + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + // + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ); + // + + const indentation_count = (pathdata.name.match(/\./g) || []).length+1 + const baseIndent =
    + //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0 + const boxPadding = 0 + const namesplit = pathdata.name.split(".") + const newname = namesplit[namesplit.length-1] + return ( + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
    + {Array(indentation_count).fill().map((subdata, subindex) => { + return ( + baseIndent + ) + })} + {icon} {newname} + {pathdata.type === "list" ? { + + }} /> : null} +
    +
    +
    + ); + })} + + + + ) : ( + handleMouseover()} + onMouseOut={() => { + handleMouseOut(); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + +
    + {icon} {innerdata.name} +
    +
    +
    + ); + })} + } {/*
    0) { defaultReturn = } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { - if (selectedTrigger.trigger_type === "SUBFLOW") { - defaultReturn = - } else if (selectedTrigger.trigger_type === "EMAIL") { + if (selectedTrigger.trigger_type === "EMAIL") { defaultReturn = } else if (selectedTrigger.trigger_type === undefined) { //defaultReturn = @@ -19648,6 +19636,13 @@ const releaseToConnectLabel = "Release to Connect" : null}
    : null} + + { + rightSideBarOpen && selectedTrigger.trigger_type === "SUBFLOW"&& Object.getOwnPropertyNames(selectedTrigger).length > 0 ? +
    + +
    : null + } {/* Date: Mon, 17 Jun 2024 11:42:15 +0530 Subject: [PATCH 25/46] Made paramValues separate --- frontend/src/components/ParsedAction.jsx | 27 ++++++++++-------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 9bf06710..a9b0c955 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -206,16 +206,16 @@ const ParsedAction = (props) => { setLastSaved(false) } }, [expansionModalOpen]) -// useEffect(() => { -// setParamValues(selectedAction.parameters.map((param) => { -// return { -// name: param.name, -// value: param.value, -// } -// })) -// },[ -// selectedAction, selectedApp,setNewSelectedAction -// ]) + useEffect(() => { + setParamValues(selectedAction.parameters.map((param) => { + return { + name: param.name, + value: param.value, + } + })) + },[ + selectedAction, selectedApp,setNewSelectedAction, setUpdate, showDropdown, showAutocomplete, selectedActionParameters + ]) // useEffect(() => { // if (selectedAction.parameters === null || selectedAction.parameters === undefined) { @@ -404,12 +404,7 @@ const ParsedAction = (props) => { console.log("UseEffect Rendered!"); console.log("Workflow", workflow); - setParamValues(selectedAction.parameters.map((param) => { - return { - name: param.name, - value: param.value, - } - })) + // Only set app action name if it has changed if (selectedAction.label !== appActionName) { setAppActionName(selectedAction.label); From 1570f852b43fb1bc1ce98d03adb7cff70defa9c1 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 17 Jun 2024 16:52:43 +0530 Subject: [PATCH 26/46] Fixed the exec args issue --- frontend/src/components/ParsedAction.jsx | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index a9b0c955..5761da74 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -214,9 +214,24 @@ const ParsedAction = (props) => { } })) },[ - selectedAction, selectedApp,setNewSelectedAction, setUpdate, showDropdown, showAutocomplete, selectedActionParameters + selectedAction, selectedApp,setNewSelectedAction, workflow, ]) +// useEffect(() => { +// if(!showAutocomplete){ +// paramValueChange(); +// } +// },[menuPosition,showAutocomplete]) + +// const paramValueChange = () => { +// setParamValues(selectedAction.parameters.map((param) => { +// return { +// name: param.name, +// value: param.value, +// } +// })) +// } + // useEffect(() => { // if (selectedAction.parameters === null || selectedAction.parameters === undefined) { // return @@ -801,12 +816,10 @@ const ParsedAction = (props) => { } //console.log("CHANGING ACTION COUNT !") - setTimeout(() => { selectedActionParameters[count].autocompleted = false selectedAction.parameters[count].autocompleted = false selectedActionParameters[count].value = event.target.value; selectedAction.parameters[count].value = event.target.value; - },100) var forceUpdate = false if (isCloud && (selectedAction.app_name === "Shuffle Tools" || selectedAction.app_name === "email") && (selectedAction.name === "send_email_shuffle" || selectedAction.name === "send_sms_shuffle") && data.name === "apikey") { @@ -3608,7 +3621,7 @@ const ParsedAction = (props) => { //selectedAction.parameters[count].value = selectedActionParameters[count].value; //setSelectedAction(selectedAction); //setUpdate(Math.random()); - + setShowDropdown(false); setMenuPosition(null); }; From 1a4b673bc8208baa6ee7e3bf2d6496b0bb8c2726 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 17 Jun 2024 17:00:23 +0530 Subject: [PATCH 27/46] Fixed the webhook associated app dropdown issue --- frontend/src/components/ParsedAction.jsx | 1 - frontend/src/views/AngularWorkflow.jsx | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 5761da74..9d8a4988 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -2204,7 +2204,6 @@ const ParsedAction = (props) => { } }); } - event.target.blur(); }} renderOption={(props, option, state) => { var newActionname = option.name; diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 65e7abff..f7d6227d 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -13829,6 +13829,7 @@ const releaseToConnectLabel = "Release to Connect" setUpdate(Math.random()); } + document.activeElement.blur(); }} >
    From db1138019f72ab0665cb232ac84062e9658fa670 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 17 Jun 2024 16:39:50 +0200 Subject: [PATCH 28/46] Minor angularworkflow fix --- frontend/src/views/AngularWorkflow.jsx | 47 +++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 021cc0d6..a2ef33d2 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -2012,6 +2012,13 @@ const AngularWorkflow = (defaultprops) => { headers["Org-Id"] = useworkflow.org_id } + // Realtime makes the workflow if it doesn't exist + /* + if (duplicationOrg !== undefined && duplicationOrg !== null && duplicationOrg.length > 0) { + headers["Org-Id"] = duplicationOrg + } + */ + setLastSaved(true); fetch(`${globalUrl}/api/v1/workflows/${useworkflow.id}`, { method: "PUT", @@ -2033,7 +2040,8 @@ const AngularWorkflow = (defaultprops) => { return response.json(); }) .then((responseJson) => { - if (duplicationOrg !== undefined && duplicationOrg !== null && duplicationOrg.length > 0) { + if (useworkflow.id === originalWorkflow.id && duplicationOrg !== undefined && duplicationOrg !== null && duplicationOrg.length > 0) { + //duplicateParentWorkflow(useworkflow, duplicationOrg, true) duplicateParentWorkflow(useworkflow, duplicationOrg, true) } @@ -3234,15 +3242,19 @@ const AngularWorkflow = (defaultprops) => { }; const getChildWorkflows = (parentWorkflowId) => { - if (workflow.suborg_distribution === undefined || workflow.suborg_distribution === null || workflow.suborg_distribution.length === 0) { + if (originalWorkflow.suborg_distribution === undefined || originalWorkflow.suborg_distribution === null || originalWorkflow.suborg_distribution.length === 0) { + console.log("No suborg distribution") return } + const orgId = originalWorkflow.org_id === undefined || originalWorkflow.org_id === null || originalWorkflow.org_id === "" ? "" : originalWorkflow.org_id + fetch(`${globalUrl}/api/v1/workflows/${parentWorkflowId}/child_workflows`, { method: "GET", headers: { "Content-Type": "application/json", - Accept: "application/json", + "Accept": "application/json", + "Org-Id": orgId, }, credentials: "include", }) @@ -3325,6 +3337,10 @@ const AngularWorkflow = (defaultprops) => { getChildWorkflows(responseJson.id) } + if (responseJson.parentorg_workflow !== undefined && responseJson.parentorg_workflow !== null && responseJson.parentorg_workflow.length > 0) { + getChildWorkflows(responseJson.parentorg_workflow) + } + // Not sure why this is necessary. if (responseJson.isValid === undefined) { responseJson.isValid = true; @@ -8584,6 +8600,11 @@ const AngularWorkflow = (defaultprops) => { return; } + if (parsedApp === undefined || parsedApp === null || parsedApp.data === undefined || parsedApp.data === null) { + toast("Failed to add trigger. Please try again.") + return + } + // Using remove & replace, as this triggers the function // onNodeAdded() with this node after it's added @@ -13584,7 +13605,7 @@ const AngularWorkflow = (defaultprops) => { // Special SCHEDULE handler var trigger_header_auth = "" - if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers[selectedTriggerIndex] !== undefined ) { + if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers !== null && workflow.triggers !== undefined && workflow.triggers.length >= selectedTriggerIndex && workflow.triggers[selectedTriggerIndex] !== undefined ) { if (selectedTrigger.trigger_type === "SCHEDULE" && workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null) { console.log("Autofixing schedule") @@ -15475,34 +15496,49 @@ const AngularWorkflow = (defaultprops) => { View Suborg workflow { borderRadius: theme.palette.borderRadius, }} onChange={(e) => { + console.log("SELECT ONCHANGE DONE") + if (selectedActionParameters[count].value[selectedActionParameters[count].value.length - 1] === ".") { e.target.value.autocomplete = e.target.value.autocomplete.slice(1,e.target.value.autocomplete.length); } diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 75613a2b..bc863f68 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -4398,15 +4398,12 @@ const releaseToConnectLabel = "Release to Connect" //const data = JSON.parse(JSON.stringify(event.target.data())) const data = event.target.data() - console.log("===============================Node selected=============================") - console.log("NODE SELECT: ", data) - if (data.app_name === "Shuffle Workflow") { - console.log("Shuffle Workflow selected") - if ((data.parameters !== undefined) && (data.parameters.length > 0)) { - getWorkflowApps(data.parameters[0].value) + if (data.app_name === "Shuffle Workflow") { + if ((data.parameters !== undefined) && (data.parameters.length > 0)) { + getWorkflowApps(data.parameters[0].value) + } } - } if (data.buttonType == "ACTIONSUGGESTION") { const attachedToId = data.attachedTo @@ -6930,8 +6927,6 @@ const releaseToConnectLabel = "Release to Connect" const allNodes = cy.nodes().jsons(); if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") { - console.log("In this :)") - var found = false; for (let nodekey in allNodes) { const currentNode = allNodes[nodekey]; @@ -9928,7 +9923,6 @@ const releaseToConnectLabel = "Release to Connect" } } - console.log("New selected action: ", newSelectedAction) setSelectedAction(newSelectedAction) setUpdate(Math.random()) @@ -21328,15 +21322,12 @@ const releaseToConnectLabel = "Release to Connect" if (paramcheck !== undefined) { // Escapes all double quotes const toReplace = event.target.value.trim().replaceAll("\\\"", "\"").replaceAll("\"", "\\\""); - console.log("REPLACE WITH: ", toReplace) if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) { paramcheck["value_replace"] = [{ "key": data.name, "value": toReplace, }] - console.log("IN IF: ", paramcheck) - } else { const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name) if (subparamindex === -1) { @@ -21347,8 +21338,6 @@ const releaseToConnectLabel = "Release to Connect" } else { paramcheck["value_replace"][subparamindex]["value"] = toReplace } - - console.log("IN ELSE: ", paramcheck) } if (paramcheck["value_replace"] === undefined) { From ed6ecc0bf4cab6f22c43155a115378dfa5315db7 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 20 Jun 2024 14:57:40 +0200 Subject: [PATCH 32/46] Fixed a lot of billing-ui problems, and added rate limiting alerts --- frontend/src/components/Billing.jsx | 1919 +++++++++++++++++---------- frontend/src/views/Workflows.jsx | 2 +- 2 files changed, 1190 insertions(+), 731 deletions(-) diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index e52bdce7..e567a5c7 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -7,7 +7,7 @@ import countries from "../components/Countries.jsx"; import { Box, Paper, - Typography, + Typography, Divider, Button, Grid, @@ -19,94 +19,150 @@ import { DialogTitle, DialogContent, TextField, - InputAdornment, + InputAdornment, IconButton, - Chip, + Chip, Checkbox, - Tooltip, + Tooltip, + DialogContentText, + DialogActions, + LinearProgress } from "@mui/material"; -import { useNavigate, Link } from "react-router-dom"; +import { useNavigate, Link, json } from "react-router-dom"; import { Autocomplete } from "@mui/material"; -import { toast } from "react-toastify" +import { toast } from "react-toastify" import { - Cached as CachedIcon, - ContentCopy as ContentCopyIcon, - Draw as DrawIcon, - Close as CloseIcon, + Cached as CachedIcon, + ContentCopy as ContentCopyIcon, + Draw as DrawIcon, + Close as CloseIcon, + Delete, + RestaurantRounded, } from "@mui/icons-material"; //import { useAlert import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; import BillingStats from "../components/BillingStats.jsx"; import { handlePayasyougo } from "../views/HandlePaymentNew.jsx" +import DeleteIcon from '@mui/icons-material/Delete'; const Billing = (props) => { - const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, clickedFromOrgTab } = props; - //const alert = useAlert(); - let navigate = useNavigate(); + const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, clickedFromOrgTab } = props; + //const alert = useAlert(); + let navigate = useNavigate(); - const [selectedDealModalOpen, setSelectedDealModalOpen] = React.useState(false); - const [dealList, setDealList] = React.useState([]); - const [dealName, setDealName] = React.useState(""); - const [dealAddress, setDealAddress] = React.useState(""); - const [dealType, setDealType] = React.useState("MSSP"); - const [dealCountry, setDealCountry] = React.useState("United States"); - const [dealCurrency, setDealCurrency] = React.useState("USD"); - const [dealStatus, setDealStatus] = React.useState("initiated"); - const [dealValue, setDealValue] = React.useState(""); - const [dealDiscount, setDealDiscount] = React.useState(""); - const [dealerror, setDealerror] = React.useState(""); + const [selectedDealModalOpen, setSelectedDealModalOpen] = React.useState(false); + const [dealList, setDealList] = React.useState([]); + const [dealName, setDealName] = React.useState(""); + const [dealAddress, setDealAddress] = React.useState(""); + const [dealType, setDealType] = React.useState("MSSP"); + const [dealCountry, setDealCountry] = React.useState("United States"); + const [dealCurrency, setDealCurrency] = React.useState("USD"); + const [dealStatus, setDealStatus] = React.useState("initiated"); + const [dealValue, setDealValue] = React.useState(""); + const [dealDiscount, setDealDiscount] = React.useState(""); + const [dealerror, setDealerror] = React.useState(""); + const [openChangeEmailBox, setOpenChangeEmailBox] = useState(false); + const [isMouseOverOnChangeEmail, setIsMouseOverOnChangeEmail] = useState(false); + const [currentAppRunsInPercentage, setCurrentAppRunsInPercentage] = useState(0); + const [currentAppRunsInNumber, setCurrentAppRunsInNumber] = useState(0); + const [alertThresholds, setAlertThresholds] = useState(selectedOrganization.Billing !== undefined && selectedOrganization.Billing.AlertThreshold !== undefined && selectedOrganization.Billing.AlertThreshold !== null ? selectedOrganization.Billing.AlertThreshold : [{ percentage: '', count: '', Email_send: false }]); + const [currentIndex, setCurrentIndex] = useState(0); + + useEffect(() => { + if (userdata.app_execution_limit !== undefined && userdata.app_execution_usage !== undefined) { + const percentage = (userdata.app_execution_usage / userdata.app_execution_limit) * 100; + setCurrentAppRunsInPercentage(Math.round(percentage)); + setCurrentAppRunsInNumber(userdata.app_execution_limit - userdata.app_execution_usage); + } + }, [userdata]); + + + const [BillingEmail, setBillingEmail] = useState(selectedOrganization.Billing !== undefined && selectedOrganization.Billing.Email !== undefined && selectedOrganization.Billing.Email != null && selectedOrganization.Billing.Email.length > 0 ? selectedOrganization.Billing.Email : selectedOrganization.org); + + useState(() => { + // Set the billing email + setBillingEmail( + selectedOrganization.Billing !== undefined && + selectedOrganization.Billing.Email !== undefined && + selectedOrganization.Billing.Email.length > 0 + ? selectedOrganization.Billing.Email + : selectedOrganization.org + ); + + // Set and sort the alert thresholds + const alertThresholds = selectedOrganization.Billing !== undefined && + selectedOrganization.Billing.AlertThreshold !== undefined && + selectedOrganization.Billing.AlertThreshold !== null + ? selectedOrganization.Billing.AlertThreshold + : [{ percentage: '', count: '', Email_send: false }]; + + const sortedAlertThresholds = alertThresholds.sort((a, b) => { + const countA = parseFloat(a.count); + const countB = parseFloat(b.count); + if (isNaN(countA)) return 1; + if (isNaN(countB)) return -1; + + return countA - countB; + }); + + setAlertThresholds(sortedAlertThresholds); + + const findCurrentIndex = sortedAlertThresholds.some(threshold => threshold.Email_send === false); + setCurrentIndex(findCurrentIndex ? sortedAlertThresholds.findIndex(threshold => threshold.Email_send === false) : - 1); + + }, [selectedOrganization]); const stripe = typeof window === 'undefined' || window.location === undefined ? "" : props.stripeKey === undefined ? "" : window.Stripe ? window.Stripe(props.stripeKey) : "" const products = [ - { code: "", label: "MSSP", phone: "" }, - { code: "", label: "Enterprise", phone: "" }, - { code: "", label: "Consultancy", phone: "" }, - { code: "", label: "Support", phone: "" }, - ]; + { code: "", label: "MSSP", phone: "" }, + { code: "", label: "Enterprise", phone: "" }, + { code: "", label: "Consultancy", phone: "" }, + { code: "", label: "Support", phone: "" }, + ]; const handleGetDeals = (orgId) => { - console.log("Get deals!"); + console.log("Get deals!"); - if (orgId.length === 0) { - toast( - "Organization ID not defined (get deals). Please contact us on https://shuffler.io if this persists logout." - ); - return; - } + if (orgId.length === 0) { + toast( + "Organization ID not defined (get deals). Please contact us on https://shuffler.io if this persists logout." + ); + return; + } - const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`; - fetch(url, { - method: "GET", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - }) - .then((response) => { - if (response.status !== 200) { - console.log("Bad status code in get deals: ", response.status); - } + const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`; + fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + if (response.status !== 200) { + console.log("Bad status code in get deals: ", response.status); + } - return response.json(); - }) - .then((responseJson) => { - console.log("Got deals: ", responseJson); - if (responseJson.success === false) { - toast("Failed loading deals. Contact support if this persists"); - } else { - setDealList(responseJson); - } - }) - .catch((error) => { - console.log("Error getting org deals: ", error); - toast( - "Failed getting deals for your org. Contact support if this persists." - ); - }); - }; + return response.json(); + }) + .then((responseJson) => { + console.log("Got deals: ", responseJson); + if (responseJson.success === false) { + toast("Failed loading deals. Contact support if this persists"); + } else { + setDealList(responseJson); + } + }) + .catch((error) => { + console.log("Error getting org deals: ", error); + toast( + "Failed getting deals for your org. Contact support if this persists." + ); + }); + }; useEffect(() => { if (isCloud && selectedOrganization.partner_info !== undefined && selectedOrganization.partner_info.reseller === true) { @@ -121,15 +177,15 @@ const Billing = (props) => { maxWidth: 400, width: "100%", backgroundColor: theme.palette.platformColor, - borderRadius: theme.palette.borderRadius*2, + borderRadius: theme.palette.borderRadius * 2, border: "1px solid rgba(255,255,255,0.3)", - marginRight: 10, + marginRight: 10, marginTop: 15, } - const isCloud = - window.location.host === "localhost:3002" || - window.location.host === "shuffler.io"; + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; billingInfo.subscription = { "active": true, @@ -161,82 +217,83 @@ const Billing = (props) => { var checkoutObject = { lineItems: [ { - price: priceItem, + price: priceItem, quantity: 1 }, ], mode: "subscription", billingAddressCollection: "auto", - successUrl: successUrl, - cancelUrl: failUrl, + successUrl: successUrl, + cancelUrl: failUrl, clientReferenceId: props.userdata.active_org.id, } //submitType: "donate", stripe.redirectToCheckout(checkoutObject) - .then(function (result) { - console.log("SUCCESS STRIPE?: ", result) + .then(function (result) { + console.log("SUCCESS STRIPE?: ", result) - ReactGA.event({ - category: "pricing", - action: "add_card_success", - label: "", + ReactGA.event({ + category: "pricing", + action: "add_card_success", + label: "", + }) }) - }) - .catch(function(error) { - console.error("STRIPE ERROR: ", error) + .catch(function (error) { + console.error("STRIPE ERROR: ", error) - ReactGA.event({ - category: "pricing", - action: "add_card_error", - label: "", - }) - }); + ReactGA.event({ + category: "pricing", + action: "add_card_error", + label: "", + }) + }); } const cancelSubscriptions = (subscription_id) => { - const orgId = selectedOrganization.id; - const data = { - subscription_id: subscription_id, - action: "cancel", - org_id: selectedOrganization.id, - }; + const orgId = selectedOrganization.id; + const data = { + subscription_id: subscription_id, + action: "cancel", + org_id: selectedOrganization.id, + }; - const url = globalUrl + `/api/v1/orgs/${orgId}/cancel`; - fetch(url, { - mode: "cors", - method: "POST", - body: JSON.stringify(data), - credentials: "include", - crossDomain: true, - withCredentials: true, - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - }) - .then(function (response) { - if (response.status !== 200) { - console.log("Error in response"); - } - - if (handleGetOrg != undefined) { - handleGetOrg(selectedOrganization.id); + const url = globalUrl + `/api/v1/orgs/${orgId}/cancel`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); } - return response.json(); - }) - .then(function (responseJson) { - if (responseJson.success !== undefined && responseJson.success) { - toast("Successfully stopped subscription!"); - } else { - toast("Failed stopping subscription. Please contact us."); - } - }) - .catch(function (error) { - console.log("Error: ", error); - toast("Failed stopping subscription. Please contact us."); - }); - }; + if (handleGetOrg != undefined) { + handleGetOrg(selectedOrganization.id); + } + + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success !== undefined && responseJson.success) { + toast("Successfully stopped subscription!"); + } else { + toast("Failed stopping subscription. Please contact us."); + } + }) + .catch(function (error) { + console.log("Error: ", error); + toast("Failed stopping subscription. Please contact us."); + }); + }; + const sendSignatureRequest = (subscription) => { const url = `${globalUrl}/api/v1/orgs/${selectedOrganization.id}`; @@ -246,46 +303,47 @@ const Billing = (props) => { org_id: selectedOrganization.id, subscription: subscription, }), - mode: "cors", - method: "POST", - credentials: "include", - crossDomain: true, - withCredentials: true, - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - }) - .then((response) => { - if (response.status !== 200) { - console.log("Error in response"); - } - return response.json(); - }) - .then((responseJson) => { - console.log("Response from signature request: ", responseJson); - }) - .catch((error) => { - console.log("Error: ", error); + mode: "cors", + method: "POST", + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, }) + .then((response) => { + if (response.status !== 200) { + console.log("Error in response"); + } + return response.json(); + }) + .then((responseJson) => { + console.log("Response from signature request: ", responseJson); + }) + .catch((error) => { + console.log("Error: ", error); + }) } const SubscriptionObject = (props) => { - const { globalUrl, index, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, subscription, highlight, } = props; + const { globalUrl, index, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, subscription, highlight, } = props; - const [signatureOpen, setSignatureOpen] = React.useState(false); - const [tosChecked, setTosChecked] = React.useState(subscription.eula_signed) + const [signatureOpen, setSignatureOpen] = React.useState(false); + const [tosChecked, setTosChecked] = React.useState(subscription.eula_signed) const [hovered, setHovered] = React.useState(false) + const [newBillingEmail, setNewBillingEmail] = useState(''); var top_text = "Base Cloud Access" if (subscription.limit === undefined && subscription.level === undefined || subscription.level === null || subscription.level === 0) { subscription.name = "Enterprise" subscription.currency_text = "$" - subscription.price = subscription.level*180 - subscription.limit = subscription.level*100000 + subscription.price = subscription.level * 180 + subscription.limit = subscription.level * 100000 subscription.interval = subscription.recurrence subscription.features = [ "Includes " + subscription.limit + " app runs/month. ", - "Multi-Tenancy and Region-Selection", + "Multi-Tenancy and Region-Selection", "And all other features from /pricing", ] } @@ -301,17 +359,17 @@ const Billing = (props) => { if (subscription.name.includes("default")) { top_text = "Custom Contract" newPaperstyle.border = "1px solid #f85a3e" - showSupport = true + showSupport = true } if (subscription.name.includes("App Run Units")) { top_text = "Cloud Access" - showSupport = true + showSupport = true } if (subscription.name.includes("Open Source")) { top_text = "Open Source" - showSupport = true + showSupport = true } if (subscription.name.includes("Scale")) { @@ -327,8 +385,80 @@ const Billing = (props) => { newPaperstyle.backgroundColor = theme.palette.surfaceColor } + const handleClickOpen = () => { + setOpenChangeEmailBox(true); + }; + + const handleCloseChangeEmailBox = () => { + setOpenChangeEmailBox(false); + }; + + + const getCircularReplacer = () => { + const seen = new WeakSet(); + return (key, value) => { + if (typeof value === 'object' && value !== null) { + if (seen.has(value)) { + return; + } + seen.add(value); + } + return value; + }; + }; + + const HandleChangeBillingEmail = (orgId) => { + const email = newBillingEmail; + const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/; + console.log("Pattern matches: ", emailPattern.test(email)); + if (!emailPattern.test(email)) { + toast("Please enter a valid email address"); + return; + } else { + setNewBillingEmail(email); + } + + toast("Updating billing email. Please Wait") + + const data = { + org_id: orgId, + email: newBillingEmail, + billing: { + email: newBillingEmail, + }, + }; + + const url = `${globalUrl}/api/v1/orgs/${orgId}/billing`; + fetch(url, { + method: "POST", + body: JSON.stringify(data), + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + if (response.status !== 200) { + console.log("Bad status code in get org:", response.status); + } + return response.json(); + }).then((responseJson) => { + console.log("Got org:", responseJson); + if (responseJson.success === true) { + toast.success("Successfully updated billing email"); + setBillingEmail(newBillingEmail); + setOpenChangeEmailBox(false); + } else { + toast.error("Failed to update billing email. Please try again."); + } + }) + .catch((error) => { + console.log("Error getting org:", error); + }); + } + return ( - setHovered(true)} onMouseLeave={() => setHovered(false)} @@ -336,34 +466,34 @@ const Billing = (props) => { - { - e.preventDefault(); - setSignatureOpen(false); - setTosChecked(false) - }} - > - - + { + e.preventDefault(); + setSignatureOpen(false); + setTosChecked(false) + }} + > + + Read and Accept the EULA @@ -371,13 +501,13 @@ const Billing = (props) => { rows={17} multiline fullWidth - InputProps={{ - readOnly: true, - style: { - fontSize: 14, - color: "rgba(255, 255, 255, 0.6)", - } - }} + InputProps={{ + readOnly: true, + style: { + fontSize: 14, + color: "rgba(255, 255, 255, 0.6)", + } + }} value={subscription.eula} /> { }} inputProps={{ 'aria-label': 'primary checkbox' }} /> - { + { setTosChecked(!tosChecked) }}> Accept - + By clicking the “accept” button, you are signing the document, electronically agreeing that it has the same legal validity and effects as a handwritten signature, and that you have the competent authority to represent and sign on behalf an entity. Need support or have questions? Contact us at support@shuffler.io. -
    +
    -
    +
    {top_text === "Base Cloud Access" && userdata.has_card_available === true ? { @@ -436,80 +566,80 @@ const Billing = (props) => { }} variant="outlined" color="primary" - /> + /> : null} {top_text} {top_text === "Base Cloud Access" && userdata.has_card_available === false ? - - : null} + : null} {isCloud && highlight === true && top_text !== "Base Cloud Access" ? - { setSignatureOpen(true) }} > - + - : null} + : null}
    - -
    - - {subscription.name} - + +
    + + {subscription.name} + - {subscription.currency_text !== undefined ? -
    - - {subscription.currency_text}{subscription.price} - - - / {subscription.interval} - -
    + {subscription.currency_text !== undefined ? +
    + + {subscription.currency_text}{subscription.price} + + + / {subscription.interval} + +
    : null} - - Features - -
      + + Features + +
        {subscription.features !== undefined && subscription.features !== null ? subscription.features.map((feature, index) => { var parsedFeature = feature if (feature.includes("Documentation: ")) { - parsedFeature = - Documentation to get started } if (feature.includes("Worker License: ")) { - const fieldId = "webhook_uri_field_"+index + const fieldId = "webhook_uri_field_" + index parsedFeature = - + @@ -517,47 +647,47 @@ const Billing = (props) => { {}} - InputProps={{ - endAdornment: - - { - var copyText = document.getElementById(fieldId); - if (copyText !== undefined && copyText !== null) { - console.log("NAVIGATOR: ", navigator); - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - toast("Can only copy over HTTPS (port 3443)"); - return; - } + style={{ + backgroundColor: theme.palette.inputColor, + borderRadius: theme.palette.borderRadius, + }} + id={fieldId} + onClick={() => { }} + InputProps={{ + endAdornment: + + { + var copyText = document.getElementById(fieldId); + if (copyText !== undefined && copyText !== null) { + console.log("NAVIGATOR: ", navigator); + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + toast("Can only copy over HTTPS (port 3443)"); + return; + } - navigator.clipboard.writeText(copyText.value); - copyText.select(); - copyText.setSelectionRange( - 0, - 99999 - ); /* For mobile devices */ + navigator.clipboard.writeText(copyText.value); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999 + ); /* For mobile devices */ - /* Copy the text inside the text field */ - document.execCommand("copy"); - toast("Copied Webhook URL"); - } else { - console.log("Couldn't find webhook URI field: ", copyText); - } - }} - edge="end" - > - - - - }} + /* Copy the text inside the text field */ + document.execCommand("copy"); + toast("Copied Webhook URL"); + } else { + console.log("Couldn't find webhook URI field: ", copyText); + } + }} + edge="end" + > + + + + }} fullWidth /> @@ -565,30 +695,102 @@ const Billing = (props) => { return (
      • - + {parsedFeature}
      • ) }) : null} -
      -
    - {isCloud && (highlight === true && (subscription.name === "Pay as you go" && subscription.limit <= 10000) || subscription.name === "Open Source") ? - - - {subscription.name.includes("Scale") ? - "" - : + +
    + {isCloud && (highlight === true && (subscription.name === "Pay as you go" && subscription.limit <= 10000) || subscription.name === "Open Source") ? + + + {subscription.name.includes("Scale") ? + "" + : - userdata.has_card_available === true ? - "While you have a card attached to your account, Shuffle will no longer prevent workflows from running. Billing will occur at the start of each month." - : - `You are not subscribed to any plan and are using the free plan with max 10,000 app runs per month. Upgrade to deactivate this limit.` - } + userdata.has_card_available === true ? + "While you have a card attached to your account, Shuffle will no longer prevent workflows from running. Billing will occur at the start of each month." + : + `You are not subscribed to any plan and are using the free plan with max 10,000 app runs per month. Upgrade to deactivate this limit.` + } + +
    + + Billing email: {BillingEmail} - Billing email: {selectedOrganization.org} - {/*isCloud ? + {userdata.has_card_available === true && ( + + )} + + Change Billing Email + + + Enter the new billing email address. + + { if (event.key === 'Enter') HandleChangeBillingEmail(selectedOrganization.id) }} + onChange={(e) => setNewBillingEmail(e.target.value)} + /> + + + + + + +
    + {/*isCloud ? : null*/} - + {userdata.has_card_available === true ? + - {userdata.has_card_available === true ? - : null} - -
    + + : null} - {showSupport ? + {showSupport ? - : null } - + : null} + ) } const addDealModal = ( - { - setSelectedDealModalOpen(false); - }} - PaperProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - minWidth: "800px", - minHeight: "320px", - }, - }} - > - - Register new deal - - -
    - { - setDealName(e.target.value); - }} - /> - { - setDealAddress(e.target.value); - }} - /> -
    -
    - { - setDealValue(e.target.value); - }} - /> - option.label} - onChange={(event, newValue) => { - setDealCountry(newValue.label); - }} - renderOption={(props, option) => ( - img": { mr: 2, flexShrink: 0 } }} - {...props} - > - - {option.label} ({option.code}) +{option.phone} - - )} - renderInput={(params) => ( - - )} - /> - { - setDealType(newValue); - }} - getOptionLabel={(option) => option.label} - renderOption={(props, option) => ( - img": { mr: 2, flexShrink: 0 } }} - {...props} - > - {option.label} - - )} - renderInput={(params) => ( - - )} - /> -
    - {dealerror.length > 0 ? ( - - error registering: {dealerror} - - ) : null} -
    - - -
    -
    -
    - ); + //setDealName("") + //setDealAddress("") + //setDealCountry("") + //setDealValue("") + }} + > + Cancel + + +
    + + + ); - const submitDeal = (dealName, dealAddress, dealCountry, dealValue) => { - if (dealerror.length > 0) { - setDealerror(""); - } + const submitDeal = (dealName, dealAddress, dealCountry, dealValue) => { + if (dealerror.length > 0) { + setDealerror(""); + } - const orgId = selectedOrganization.id; - const data = { - reseller_org: orgId, - name: dealName, - address: dealAddress, - country: dealCountry, - value: dealValue, - }; + const orgId = selectedOrganization.id; + const data = { + reseller_org: orgId, + name: dealName, + address: dealAddress, + country: dealCountry, + value: dealValue, + }; - const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`; - fetch(url, { - mode: "cors", - method: "POST", - body: JSON.stringify(data), - credentials: "include", - crossDomain: true, - withCredentials: true, - headers: { - "Content-Type": "application/json; charset=utf-8", - }, - }) - .then(function (response) { - if (response.status !== 200) { - console.log("Error in response"); - } + const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); + } - return response.json(); - }) - .then(function (responseJson) { - if (responseJson.success === true) { - setSelectedDealModalOpen(false); - toast( - "Added new deal! We will be in touch shortly with an update." - ); + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success === true) { + setSelectedDealModalOpen(false); + toast( + "Added new deal! We will be in touch shortly with an update." + ); + + setDealName(""); + setDealAddress(""); + setDealValue(""); + setDealCountry("United States"); + setDealType("MSSP"); + } else { + setDealerror(responseJson.reason); + } + }) + .catch(function (error) { + //console.log("Error: ", error); + setDealerror(error.toString()); + toast("Failed adding deal reg: ", error); + }); + }; + const addAlertThreshold = () => { + setAlertThresholds([...alertThresholds, { percentage: '', count: '', Email_send: false }]); + }; + + const updateAlertThreshold = (index, field, value) => { + + if (field === 'percentage') { + if (value > 100 || value < 0) { + value = 0 + toast("The percentage value should be between 0 and 100") + } + } else if (field === 'count') { + if (value < 0 || value >= userdata.app_execution_limit) { + value = 0 + toast("The count value should be greater than 0 and less than the total app execution limit") + } + } + + + const totalValue = userdata.app_execution_limit; + const newAlertThresholds = alertThresholds.map((threshold, i) => { + if (i === index) { + const newValue = parseFloat(value); + if (field === 'percentage') { + const newCount = (newValue / 100) * totalValue; + return { + ...threshold, + percentage: isNaN(newValue) ? '' : Math.round(newValue), + count: isNaN(newCount) ? '' : Math.round(newCount), + Email_send: false + }; + } else if (field === 'count') { + const newPercentage = (newValue / totalValue) * 100; + return { + ...threshold, + count: newValue, + percentage: isNaN(newPercentage) ? '' : Math.round(newPercentage), + Email_send: false + }; + } + } + return threshold; + }); + setAlertThresholds(newAlertThresholds); + }; + + + const handleDeleteAlertThreshold = (index) => { + const newAlertThresholds = alertThresholds.filter((_, i) => i !== index); + setAlertThresholds(newAlertThresholds); + + // Update currentIndex based on remaining elements + const findCurrentIndex = newAlertThresholds.some(threshold => threshold.Email_send === false); + setCurrentIndex(findCurrentIndex ? newAlertThresholds.findIndex(threshold => threshold.Email_send === false) : - 1); + }; + + const HandleEditOrgForAlertThreshold = (orgId) => { + + // Use the `some` method to check for invalid counts + const invalidCount = alertThresholds.some((threshold) => { + if (threshold.count === '') { + toast("Please enter a valid Count or Percentage value"); + return true; // Stop checking further and return true if invalid + } + return false; + }); + + // If any invalid count is found, return early + if (invalidCount) { + return; + } + + toast("Updating Email Alert Threshold. Please wait..."); + + const data = { + org_id: orgId, + billing: { + email: BillingEmail, + AlertThreshold: alertThresholds.map(threshold => ({ + ...threshold, + percentage: parseInt(threshold.percentage, 10), + count: parseInt(threshold.count, 10), + })), + }, + }; + + const url = `${globalUrl}/api/v1/orgs/${orgId}`; + fetch(url, { + method: "POST", + body: JSON.stringify(data), + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + if (response.status !== 200) { + console.log("Bad status code in get org:", response.status); + } + return response.json(); + }).then((responseJson) => { + console.log("Got org:", responseJson); + if (responseJson.success === true) { + toast.success("Successfully updated Email Alert Thresholds"); + const findCurrentIndex = alertThresholds.some(threshold => threshold.Email_send === false); + setCurrentIndex(findCurrentIndex ? alertThresholds.findIndex(threshold => threshold.Email_send === false) : - 1); + } else { + toast.error("Failed to update Email Alert Thresholds. Please try again."); + } + }) + .catch((error) => { + console.log("Error getting org:", error); + }); + }; + + const getSafeValue = (value) => { + + if (value === undefined || value === null || isNaN(value)) { + return 0; + } else { + return value; + } + }; - setDealName(""); - setDealAddress(""); - setDealValue(""); - setDealCountry("United States"); - setDealType("MSSP"); - } else { - setDealerror(responseJson.reason); - } - }) - .catch(function (error) { - //console.log("Error: ", error); - setDealerror(error.toString()); - toast("Failed adding deal reg: ", error); - }); - }; const isChildOrg = userdata.active_org.creator_org !== "" && userdata.active_org.creator_org !== undefined && userdata.active_org.creator_org !== null return ( -
    - {addDealModal} - {clickedFromOrgTab? -

    Billing & Licensing

    : - - Billing & Licensing - } - {clickedFromOrgTab? - {isCloud ? - "Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below." - : - "Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update." - }: - - {isCloud ? +
    + {addDealModal} + {clickedFromOrgTab ? +

    Billing & Licensing

    : + + Billing & Licensing + } + {clickedFromOrgTab ? + {isCloud ? "Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below." : "Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update." - } - } + } : + + {isCloud ? + "Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below." + : + "Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update." + } + } - {userdata.support === true ? -
    + {userdata.support === true ? +
    For sales: Create  New Cloud Contract @@ -1001,7 +1327,7 @@ const Billing = (props) => { New Onprem Contract -   -   +   -   Google Drive Link @@ -1009,22 +1335,20 @@ const Billing = (props) => { Sales Process - -
    : null } - {isChildOrg ? - - Billing is handled by your parent organisation. Reach out to support@shuffler.io if you have questions about this. - + {isChildOrg ? + + Billing is handled by your parent organisation. Reach out to support@shuffler.io if you have questions about this. + : null} -
    - {isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null : +
    + {isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null : { subscription={billingInfo.subscription} highlight={selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0} /> - : !isCloud ? - - - - - : null} + : !isCloud ? + + + + + : null} {isCloud && selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== null && selectedOrganization.subscriptions.length > 0 && - !isChildOrg ? - selectedOrganization.subscriptions - .reverse() - .map((sub, index) => { - return ( - - ) - }) - : null} - {/* + !isChildOrg ? + selectedOrganization.subscriptions + .reverse() + .map((sub, index) => { + return ( + + ) + }) + : null} + {/* { */} -
    +
    - {/*isCloud && + {/*isCloud && selectedOrganization.partner_info !== undefined && selectedOrganization.partner_info.reseller === true ? (
    @@ -1333,21 +1657,156 @@ const Billing = (props) => {
    ) : null*/} -
    +
    - Utilization & Stats + Manage Billing -
    - + Manage your billing and licensing information below. When you reach the certain thresholds of your subscription limit, you will be notified by email. + + Current Usage: + + + You have used {currentAppRunsInPercentage}% of total app execution limit or {userdata.app_execution_usage} app runs out of {userdata.app_execution_limit} app runs. + + +
    + + Set email alert thresholds for app runs + + + You will be notified by email when you reach the + {currentIndex !== -1 + ? " " + getSafeValue(alertThresholds[currentIndex].percentage) + '%' + " " + : " " + '0%' + " " + } + of your total app execution limit or + {currentIndex !== -1 + ? " " + getSafeValue(alertThresholds[currentIndex].count) + " " + : " " + 0 + " "} + app runs. + + +
    + {alertThresholds.map((threshold, index) => ( +
    + updateAlertThreshold(index, 'percentage', e.target.value)} + margin="normal" + variant="outlined" + inputProps={{ + max: 100, + }} + /> + updateAlertThreshold(index, 'count', e.target.value)} + margin="normal" + variant="outlined" + /> + { + alertThresholds.length > 1 && + ( + + ) + } +
    + ))} +
    +
    + + + +
    +
    + + Utilization & Stats + +
    + + />
    ) } diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 299e0c2d..ffdf0ec4 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -2153,7 +2153,7 @@ const Workflows = (props) => { } return ( -
    +
    {selectedCategory !== "" ? From e395832fc479e05875a4edf32ece78dae3249818 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Thu, 20 Jun 2024 20:44:50 +0530 Subject: [PATCH 33/46] Fixed the issue of merging the renderingIssue branch --- README.md | 2 + frontend/src/components/AppGrid.jsx | 4 - frontend/src/components/ParsedAction.jsx | 3283 +++++++++++----------- frontend/src/components/WorkflowGrid.jsx | 2 +- frontend/src/views/AngularWorkflow.jsx | 1821 ++++++------ 5 files changed, 2468 insertions(+), 2644 deletions(-) diff --git a/README.md b/README.md index 3e22c1d4..c1a214e2 100755 --- a/README.md +++ b/README.md @@ -11,11 +11,13 @@ Shuffle Automation [Shuffle](https://shuffler.io) is an automation platform for and by the community, focusing on accessibility for anyone to automate. Security operations is complex, but it doesn't have to be. +[ Get training ](https://shuffler.io/training) [_Key Features_](https://shuffler.io/docs/features) — [_Community & Support_](https://discord.gg/B2CBzUm) — [_Documentation_](https://shuffler.io/docs) — [_Getting Started_](https://shuffler.io/docs/getting_started) — [_Development_](https://github.com/shuffle/Shuffle/blob/master/.github/CONTRIBUTING.md) +[ Set up a demo call ](https://shuffler.io/contact) Follow us on Twitter at [@shuffleio](https://twitter.com/shuffleio). diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx index 11e25f0b..b6133cbf 100644 --- a/frontend/src/components/AppGrid.jsx +++ b/frontend/src/components/AppGrid.jsx @@ -5,7 +5,6 @@ import ReactGA from "react-ga4"; import { Link } from "react-router-dom"; import { removeQuery } from "../components/ScrollToTop.jsx"; import { useMemo } from "react"; - import { Tabs, Tab, Collapse } from "@mui/material"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ExpandLessIcon from "@mui/icons-material/ExpandLess"; @@ -33,10 +32,8 @@ import { ClearRefinements, connectStateResults } from "react-instantsearch-dom"; - import aa from "search-insights"; import { useLocation } from 'react-router-dom'; - import "./FilterCSS.css"; import { @@ -158,7 +155,6 @@ const AppGrid = (props) => { const handleSearch = () => { refine(searchQuery.trim()); }; - return (
    { } = props; const classes = useStyles(); - const [hideBody, setHideBody] = React.useState(true); - const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false); - + const [hideBody, setHideBody] = React.useState(true) + const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false) + const [appActionName, setAppActionName] = React.useState(selectedAction.label); + const [delay, setDelay] = React.useState(selectedAction?.execution_delay || 0); + const [fieldCount, setFieldCount] = React.useState(0); const [hiddenDescription, setHiddenDescription] = React.useState(true); - const [autoCompleting, setAutocompleting] = React.useState(false); - + const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction?.parameters || []); + const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); + const [paramValues, setParamValues] = React.useState( + selectedAction?.parameters.map((param) => { + return { + name: param.name, + value: param.value, + } + }) + ); + const [actionlist, setActionlist] = React.useState([]); + const [jsonList, setJsonList] = React.useState([]); + const [showDropdown, setShowDropdown] = React.useState(false); + const [showDropdownNumber, setShowDropdownNumber] = React.useState(0); + const [showAutocomplete, setShowAutocomplete] = React.useState(false); + const [menuPosition, setMenuPosition] = useState(null); const isIntegration = selectedAction.app_id === "integration" useEffect(() => { @@ -190,12 +206,22 @@ const ParsedAction = (props) => { setLastSaved(false) } }, [expansionModalOpen]) + useEffect(() => { + setParamValues(selectedAction.parameters.map((param) => { + return { + name: param.name, + value: param.value, + } + })) + },[ + selectedAction, selectedApp,setNewSelectedAction, workflow, + ]) useEffect(() => { if (selectedAction.parameters === null || selectedAction.parameters === undefined) { return } - + const paramcheck = selectedAction.parameters.find(param => param.name === "body") if (paramcheck === undefined || paramcheck === null) { return @@ -206,12 +232,11 @@ const ParsedAction = (props) => { setHideBody(true) } else { setHideBody(false) - + if (paramcheck.id === "UNTOGGLED") { setActivateHidingBodyButton(false) } } - }, []) const keywords = [ @@ -372,252 +397,184 @@ const ParsedAction = (props) => { //setStartNode(selectedAction.id) }; - const AppActionArguments = (props) => { - const [selectedActionParameters, setSelectedActionParameters] = React.useState([]); - const [selectedVariableParameter, setSelectedVariableParameter] = React.useState(""); - const [actionlist, setActionlist] = React.useState([]); - const [jsonList, setJsonList] = React.useState([]); - const [showDropdown, setShowDropdown] = React.useState(false); - const [showDropdownNumber, setShowDropdownNumber] = React.useState(0); - const [showAutocomplete, setShowAutocomplete] = React.useState(false); - const [menuPosition, setMenuPosition] = useState(null); - - useEffect(() => { - if (selectedActionParameters !== undefined && selectedActionParameters !== null && selectedActionParameters.length === 0 - ) { - if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) { - setSelectedActionParameters(selectedAction.parameters); - } - } - - if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) { - - // FIXME - this is the bad thing - setSelectedVariableParameter(workflow.workflow_variables[0].name); - } - - if (actionlist.length === 0) { - // FIXME: Have previous execution values in here - if (workflowExecutions.length > 0) { - for (let [key,keyval] in Object.entries(workflowExecutions)) { - if ( - workflowExecutions[key].execution_argument === undefined || - workflowExecutions[key].execution_argument === null || - workflowExecutions[key].execution_argument.length === 0 - ) { - continue; - } - - const valid = validateJson(workflowExecutions[key].execution_argument) - if (valid.valid) { - actionlist.push({ - type: "Execution Argument", - name: "Execution Argument", - value: "$exec", - highlight: "exec", - autocomplete: "exec", - example: valid.result, - }) - break - } - } + useEffect( + () => { + + // Only set app action name if it has changed + if (selectedAction.label !== appActionName) { + setAppActionName(selectedAction.label); } - - if (actionlist.length === 0) { - actionlist.push({ - type: "Execution Argument", - name: "Execution Argument", - value: "$exec", - highlight: "exec", - autocomplete: "exec", - example: "", - }) + + // Only set delay if it has changed + const newDelay = selectedAction?.execution_delay || 0; + if (newDelay !== delay) { + setDelay(newDelay); } + + // Only set selected action parameters if they have changed + if (selectedAction.parameters && selectedAction.parameters.length > 0) { + setSelectedActionParameters(selectedAction.parameters); + } + + // Only set selected variable parameter if it is null or undefined + if (!selectedVariableParameter && workflow.workflow_variables?.length > 0) { + setSelectedVariableParameter(workflow.workflow_variables[0].name); + } + + + }, + [selectedAction,selectedApp,setNewSelectedAction,workflow, workflowExecutions, getParents] + ); - /* - actionlist.push({ - type: "Shuffle DB", - name: "Shuffle DB", - value: "$shuffle_cache", - highlight: "shuffle_cache", - autocomplete: "shuffle_cache", - example: { - "what": "", - "unique gmail ids new": "", - }, - }) - */ + useEffect(() => { + const newActionList = []; - var cachekey = { - type: "Shuffle DB", - name: "Shuffle DB", - value: "$shuffle_cache", - highlight: "shuffle_cache", - autocomplete: "shuffle_cache", - example: "", + // Process workflowExecutions + if (workflowExecutions.length > 0) { + for (let execution of workflowExecutions) { + const execArg = execution.execution_argument; + if (execArg && execArg.length > 0) { + const valid = validateJson(execArg); + if (valid.valid) { + newActionList.push({ + type: "Execution Argument", + name: "Execution Argument", + value: "$exec", + highlight: "exec", + autocomplete: "exec", + example: valid.result, + }); + break; + } + } + } } - if (listCache !== undefined && listCache !== null && listCache.keys !== undefined && listCache.keys !== null && listCache.keys.length > 0) { - cachekey.example = {} - - for (var i in listCache.keys) { - const item = listCache.keys[i] - if (item.key === undefined || item.key === null || item.key.length === 0) { - continue - } - - var itemvalue = item.value === undefined || item.value === null ? "" : item.value - try{ - if (itemvalue.length > 10000) { - itemvalue = "" - } - - } catch (e) { - itemvalue = "" - } - - var itemkey = item.key.split(" ").join("_") - cachekey.example[itemkey] = { - "value": itemvalue, - } - } - } else { - } - - actionlist.push(cachekey) - - if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) { - for (let [key,keyval] in Object.entries(workflow.workflow_variables)) { - const item = workflow.workflow_variables[key]; - actionlist.push({ - type: "workflow_variable", - name: item.name, - value: item.value, - id: item.id, - autocomplete: `${item.name.split(" ").join("_")}`, - example: item.value, + // Add default Execution Argument if none were added + if (newActionList.length === 0) { + newActionList.push({ + type: "Execution Argument", + name: "Execution Argument", + value: "$exec", + highlight: "exec", + autocomplete: "exec", + example: "", }); - } + + let cacheKey = { + type: "Shuffle DB", + name: "Shuffle DB", + value: "$shuffle_cache", + highlight: "shuffle_cache", + autocomplete: "shuffle_cache", + example: "", + }; + + if (listCache?.keys?.length > 0) { + cacheKey.example = {}; + for (let item of listCache.keys) { + if (item.key) { + let itemValue = item.value ?? ""; + if (itemValue.length > 10000) { + itemValue = ""; + } + cacheKey.example[item.key.split(" ").join("_")] = { value: itemValue }; + } + } + } + + newActionList.push(cacheKey); } - if (workflow.execution_variables !== null && workflow.execution_variables !== undefined && workflow.execution_variables.length > 0) { - for (let [key,keyval] in Object.entries(workflow.execution_variables)) { - const item = workflow.execution_variables[key] - - var exampleoutput = "" - for (let execkey in workflowExecutions) { - const exec = workflowExecutions[execkey] - if (exec["execution_variables"] === undefined || exec["execution_variables"] === null) { - continue - } - - const foundExec = exec.execution_variables.find((exvar) => exvar.name === item.name) - if (!foundExec) { - continue - } - - if (foundExec.value !== undefined && foundExec.value !== null && foundExec.value.length > 0) { - exampleoutput = foundExec.value - break - } - } - - actionlist.push({ - type: "execution_variable", - name: item.name, - value: item.value, - id: item.id, - autocomplete: `${item.name.split(" ").join("_")}`, - example: exampleoutput, - }); - } + // Process workflow variables + if (workflow.workflow_variables?.length > 0) { + for (let variable of workflow.workflow_variables) { + newActionList.push({ + type: "workflow_variable", + name: variable.name, + value: variable.value, + id: variable.id, + autocomplete: variable.name.split(" ").join("_"), + example: variable.value, + }); + } } - // Loops parent nodes' old results to fix autocomplete - if (getParents !== undefined) { - var parents = getParents(selectedAction) + // Process execution variables + if (workflow.execution_variables?.length > 0) { + for (let variable of workflow.execution_variables) { + let exampleOutput = ""; + for (let exec of workflowExecutions) { + const foundExec = exec.execution_variables?.find(exvar => exvar.name === variable.name); + if (foundExec?.value) { + exampleOutput = foundExec.value; + break; + } + } + newActionList.push({ + type: "execution_variable", + name: variable.name, + value: variable.value, + id: variable.id, + autocomplete: variable.name.split(" ").join("_"), + example: exampleOutput, + }); + } + } - if (parents.length > 1) { - var labels = [] - //for (let [parentkey, parentkeyval] in Object.entries(parents)) { - for (let parentkey in parents) { - const parentNode = parents[parentkey] - if (parentNode.label === "Execution Argument") { - continue - } + // Process parent actions if getParents is provided + if (getParents) { + const parents = getParents(selectedAction); + if (parents.length > 1) { + const labels = []; + for (let parentNode of parents) { + if (parentNode.label !== "Execution 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; + } + } + } + } + newActionList.push({ + type: "action", + id: parentNode.id, + name: parentNode.label, + autocomplete: parentNode.label.split(" ").join("_"), + example: exampleData, + }); + } + } + } + } - //if (labels.includes(item.label)) { - // continue - //} - - labels.push(parentNode.label) - - var exampledata = parentNode.example === undefined || parentNode.example === null ? "" : parentNode.example - // Find previous execution and their variables - //exampledata === "" && - if (workflowExecutions.length > 0) { - // Look for the ID - const found = false; - for (let wfkey in workflowExecutions) { - if (workflowExecutions[wfkey].results === undefined || workflowExecutions[wfkey].results === null) { - - continue; - } - - var foundResult = workflowExecutions[wfkey].results.find((result) => result.action.id === parentNode.id) - - if (foundResult === undefined || foundResult === null) { - continue - } - - if (foundResult.result !== undefined && foundResult.result !== null) { - foundResult = foundResult.result - } - - const valid = validateJson(foundResult) - if (valid.valid) { - if (valid.result.success === false) { - //console.log("Skipping success false autocomplete") - } else { - - // FIXME: Have a merge system to allow to use kind of any key from that node in the last 10-20 execs - //if (exampledata.length > 0) { - // exampledata = valid.result - //} else { - // exampledata = valid.result - //} - - exampledata = valid.result - break - } - } else { - exampledata = foundResult - } - } - } - - // 1. Take - const itemlabelComplete = parentNode.label === null || parentNode.label === undefined ? "" : parentNode.label.split(" ").join("_"); - - const actionvalue = { - type: "action", - id: parentNode.id, - name: parentNode.label, - autocomplete: itemlabelComplete, - example: exampledata, - } - - actionlist.push(actionvalue) - } - } - - setActionlist(actionlist); + // Update the actionlist state + setActionlist(newActionList); + }, [workflow.execution_variables, workflow.workflow_variables, workflowExecutions, workflow, selectedAction, listCache, getParents]); + + useEffect(() => { + selectedNameChange(appActionName) + actionDelayChange(delay) + },[appActionName,delay]) + + const handleParamChange = (event, count,data) => { + const newParams = [...paramValues]; + newParams.map((param) => { + if (param.name === data.name) { + param.value = event.target.value; + } + }) + setParamValues(newParams); + changeActionParameter(event, count, data) } - } - }); - - const calculateHelpertext = (input_data) => { var helperText = "" var looperText = "" @@ -843,9 +800,9 @@ const ParsedAction = (props) => { //console.log("CHANGING ACTION COUNT !") selectedActionParameters[count].autocompleted = false - selectedAction.parameters[count].autocompleted = false - selectedActionParameters[count].value = event.target.value; - selectedAction.parameters[count].value = event.target.value; + selectedAction.parameters[count].autocompleted = false + selectedActionParameters[count].value = event.target.value; + selectedAction.parameters[count].value = event.target.value; var forceUpdate = false if (isCloud && (selectedAction.app_name === "Shuffle Tools" || selectedAction.app_name === "email") && (selectedAction.name === "send_email_shuffle" || selectedAction.name === "send_sms_shuffle") && data.name === "apikey") { @@ -1185,7 +1142,7 @@ const ParsedAction = (props) => { } // FIXME: Issue #40 - selectedActionParameters not reset - if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { + if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { var wrapperapp = { "id": "", @@ -1212,13 +1169,1209 @@ const ParsedAction = (props) => { var authWritten = false; var noAppSelected = false - const paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name") + var paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name") if (paramIndex === -1 || selectedAction.parameters[paramIndex].value === "" || selectedAction.parameters[paramIndex].value === "noapp") { // Check the actual value and if it's the same noAppSelected = true } - return ( -
    + } + + + const ActionSelectOption = (actionprops) => { + const { option, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops; + const [hover, setHover] = React.useState(false); + + return ( + +
    setHover(true)} onMouseLeave={() => setHover(false)} + onClick={(event) => { + // event.preventDefault() + //setSelectedAction(actionprops) + //setShowActionList(false) + //setUpdate(Math.random()) + // + if (option !== undefined && option !== null) { + setNewSelectedAction({ + target: { + value: option.name + } + }); + } + document.activeElement.blur(); + }} + > +
    + + {useIcon} + + {newActionname} +
    + {extraDescription.length > 0 ? + + {extraDescription} + + : null} +
    +
    + ) + } + + const sortByCategoryLabel = (a, b) => { + const aHasCategoryLabel = a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0 + const bHasCategoryLabel = b.category_label !== undefined && b.category_label !== null && b.category_label.length > 0 + + // Sort by existence and length of "category_label" + if (aHasCategoryLabel && !bHasCategoryLabel) { + return -1 + } else if (!aHasCategoryLabel && bHasCategoryLabel) { + return 1 + } else { + return 0 + } + } + + // Function to deduplicate based on the "name" field + const deduplicateByName = (array) => { + const uniqueNames = {}; + return array.filter(item => { + if (!item.hasOwnProperty('name') || !item.name.length) { + return true + } + if (!uniqueNames[item.name]) { + uniqueNames[item.name] = true + return true + } + return false + }) + } + + // Gets the most important actions first + const renderedActionOptions = deduplicateByName(( + selectedApp.actions === undefined || selectedApp.actions === null ? [] : + selectedApp.actions.filter((a) => + a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0).concat(sortByKey(selectedApp.actions, "label")) + ).sort(sortByCategoryLabel)) + + + const selectedAppIcon = selectedAction.large_image + var baselabel = selectedAction.label + return ( +
    + + {hideExtraTypes === true ? null : ( + +
    +
    +
    { + //window.open("/apps/${selectedAction.app_id}", "_blank") + }} + > + + + +

    + {( + selectedAction.app_name.charAt(0).toUpperCase() + + selectedAction.app_name.substring(1) + ).replaceAll("_", " ")} +

    +
    +
    + { + if (workflowExecutions.length > 0) { + // Look for the ID + var found = false; + for (let [key,keyval] in Object.entries(workflowExecutions)) { + if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { + continue; + } + + var foundResult = workflowExecutions[key].results.find( + (result) => result.action.id === selectedAction.id + ) + + if (foundResult === undefined || foundResult === null) { + continue; + } + + const oldstartnode = cy.getElementById(selectedAction.id); + if (oldstartnode !== undefined && oldstartnode !== null) { + const foundname = oldstartnode.data("label") + if (foundname !== undefined && foundname !== null) { + foundResult.action.label = foundname + } + } + + setSelectedResult(foundResult); + if (setCodeModalOpen !== undefined) { + setCodeModalOpen(true); + + found = true + } + + break; + } + + if (!found) { + toast("No result for this action yet. Please run the workflow first.") + } + } + }} + > + + + + + { + setAuthenticationModalOpen(true) + }} + > + + + + + {/* + {}} + > + + + + + + + */} + {/* + { + //setAuthenticationModalOpen(true); + console.log("Should enable/disable magic!") + console.log("Action: ", selectedAction) + if (selectedAction.run_magic_output === undefined) { + selectedAction.run_magic_output = true + } else { + if (selectedAction.run_magic_output === true) { + selectedAction.run_magic_output = false + } else { + selectedAction.run_magic_output = true + } + } + + setSelectedAction(selectedAction) + setUpdate(Math.random()); + }} + > + + + + + */} + {/* + { + }} + > + + + + + + + */} + { + //if (setAiQueryModalOpen !== undefined) { + // setAiQueryModalOpen(true) + //} else { + aiSubmit("Fill based on previous values", undefined, undefined, selectedAction) + //} + setAutocompleting(true) + }} + > + + {autoCompleting ? + + : + + } + + +
    +
    +
    + {/*selectedAction.id === workflow.start ? null : + + + + + */} + {selectedApp.versions !== null && + selectedApp.versions !== undefined && + selectedApp.versions.length > 1 ? ( + + ) : null} +
    +
    +
    +
    + Name + { + let newValue = event.target.value + newValue = newValue.replaceAll(" ", "_") + setAppActionName(newValue) + } + } + onBlur={(e) => { + // Copy the name value + const name = appActionName + const parsedBaseLabel = "$"+baselabel.toLowerCase().replaceAll(" ", "_") + const newname = "$"+name.toLowerCase().replaceAll(" ", "_") + + // Check if it's the same as the current name in use + //if (name === selectedAction.label) { + // console.log("Returning from name thing") + // return + //} + + // Change in actions, triggers & conditions + // Highlight the changes somehow with a glow? + if (workflow.branches !== undefined && workflow.branches !== null) { + for (let [key,keyval] in Object.entries(workflow.branches)) { + if (workflow.branches[key].conditions !== undefined && workflow.branches[key].conditions !== null) { + for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) { + const condition = workflow.branches[key].conditions[subkey] + const sourceparam = condition.source + const destinationparam = condition.destination + + // Should have a smarter way of discovering node names + // Finding index(es) and replacing at the location + if (sourceparam.value.includes("$")) { + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (sourceparam.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value) + const extralength = newname.length-parsedBaseLabel.length + sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length) + + console.log("New: ", workflow.branches[key].conditions[subkey].source.value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + + if (destinationparam.value.includes("$")) { + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (destinationparam.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value) + const extralength = newname.length-parsedBaseLabel.length + destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length) + + console.log("New: ", workflow.branches[key].conditions[subkey].destination.value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + } + } + } + } + + for (let [key,keyval] in Object.entries(workflow.actions)) { + if (workflow.actions[key].id === selectedAction.id) { + continue + } + + const params = workflow.actions[key].parameters + console.log(params) + if (params === null || params === undefined) { + continue + } + + for (let [subkey, subkeyval] in Object.entries(params)) { + const param = workflow.actions[key].parameters[subkey]; + if (!param.value.includes("$")) { + continue + } + + // Should have a smarter way of discovering node names + // Do regex? + // Finding index(es) and replacing at the location + // + + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 + + const foundindex = param.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (param.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = param.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.actions[key].parameters[subkey].value) + const extralength = newname.length-parsedBaseLabel.length + param.value = param.value.substring(0, foundindex) + newname + param.value.substring(foundindex-extralength+newname.length, param.value.length) + + console.log("New: ", workflow.actions[key].parameters[subkey].value) + } else { + break + } + + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) + } + } + } + + setWorkflow(workflow); + setUpdate(Math.random()); + baselabel = name + }} + /> +
    + {/*!isCloud ? null :*/} +
    + + + Delay + { + setDelay(event.target.value) + }} + /> + + +
    + {/**/} +
    +
    + )} + {selectedApp.name !== undefined && + selectedAction.authentication !== null && + selectedAction.authentication !== undefined && + selectedAction.authentication.length === 0 && + requiresAuthentication ? ( +
    + + + + + +
    + ) : null} + + {selectedAction.authentication !== undefined && + selectedAction.authentication !== null && + selectedAction.authentication.length > 0 ? ( +
    + Authentication +
    + + + {/* + + + curaction.authentication = authenticationOptions + if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") + */} + + { + setAuthenticationModalOpen(true); + }} + > + + + +
    +
    + ) : null} + + {showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? ( +
    + Environment + +
    + ) : null} + {workflow.execution_variables !== undefined && + workflow.execution_variables !== null && + workflow.execution_variables.length > 0 ? ( +
    + Execution variable (optional) + +
    + ) : null} + + +
    + {/*hideExtraTypes ? null : +
    + Actions +
    + */} + + {setNewSelectedAction !== undefined ? ( + { + // Most popular + // Is categorized + // Uncategorized + return option.category_label !== undefined && option.category_label !== null && option.category_label.length > 0 ? "Most used" : "All Actions"; + }} + renderGroup={(params) => { + + return ( +
  • + {params.group} + {params.children} +
  • + ) + }} + options={renderedActionOptions} + ListboxProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + }, + }} + filterOptions={(options, { inputValue }) => { + //console.log("Option contains?: ", inputValue, options) + const lowercaseValue = inputValue.toLowerCase() + options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue)) + + return options + }} + getOptionLabel={(option) => { + if (option === undefined || option === null || option.name === undefined || option.name === null ) { + return null; + } + + const newname = ( + option.name.charAt(0).toUpperCase() + option.name.substring(1) + ).replaceAll("_", " "); + + return newname; + }} + fullWidth + style={{ + backgroundColor: theme.palette.inputColor, + height: 50, + borderRadius: theme.palette.borderRadius, + }} + onChange={(event, newValue) => { + // Workaround with event lol + if (newValue !== undefined && newValue !== null) { + setNewSelectedAction({ + target: { + value: newValue.name + } + }); + } + }} + renderOption={(props, option, state) => { + var newActionname = option.name; + if (option.label !== undefined && option.label !== null && option.label.length > 0) { + newActionname = option.label; + } + + var newActiondescription = option.description; + //console.log("DESC: ", newActiondescription) + if (option.description === undefined || option.description === null) { + newActiondescription = "Description: No description defined for this action" + } else { + newActiondescription = "Description: "+newActiondescription + } + + const iconInfo = GetIconInfo({ name: option.name }); + const useIcon = iconInfo.originalIcon; + + if (newActionname === undefined || newActionname === null) { + newActionname = "No name" + option.name = "No name" + option.label = "No name" + } + + newActionname = (newActionname.charAt(0).toUpperCase() + newActionname.substring(1)).replaceAll("_", " "); + + var method = "" + var extraDescription = "" + if (option.name.includes("get_")) { + method = "GET" + } else if (option.name.includes("post_")) { + method = "POST" + } else if (option.name.includes("put_")) { + method = "PUT" + } else if (option.name.includes("patch_")) { + method = "PATCH" + } else if (option.name.includes("delete_")) { + method = "DELETE" + } else if (option.name.includes("options_")) { + method = "OPTIONS" + } else if (option.name.includes("connect_")) { + method = "CONNECT" + } + + // FIXME: Should it require a base URL? + if (method.length > 0 && option.description !== undefined && option.description !== null && option.description.includes("http")) { + var extraUrl = "" + const descSplit = option.description.split("\n") + // Last line of descSplit + if (descSplit.length > 0) { + extraUrl = descSplit[descSplit.length-1] + } + + //for (let [line,lineval] in Object.entries(descSplit)) { + // if (descSplit[line].includes("http") && descSplit[line].includes("://")) { + // const urlsplit = descSplit[line].split("/") + // try { + // extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/") + // } catch (e) { + // //console.log("Failed - running with -1") + // extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/") + // } + + + // //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line]) + // //break + // } + //} + + if (extraUrl.length > 0) { + if (extraUrl.includes(" ")) { + extraUrl = extraUrl.split(" ")[0] + } + + if (extraUrl.includes("#")) { + extraUrl = extraUrl.split("#")[0] + } + + extraDescription = `${method} ${extraUrl}` + } else { + //console.log("No url found. Check again :)") + } + } + + return ( + + ); + }} + renderInput={(params) => { + if (params.inputProps?.value) { + const prefixes = ["Post", "Put", "Patch"]; + for (let prefix of prefixes) { + if (params.inputProps.value.startsWith(prefix)) { + let newValue = params.inputProps.value.replace(prefix + " ", ""); + if (newValue.length > 1) { + newValue = newValue.charAt(0).toUpperCase() + newValue.substring(1); + } + // Set the new value without mutating inputProps + params = { ...params, inputProps: { ...params.inputProps, value: newValue } }; + break; + } + } + // Check if it starts with "Get List" and method is "Get" + if (params.inputProps.value.startsWith("Get List")) { + console.log("Get List"); + } + } + + return ( + + ); + }} + /> + ) : null} + + {/*setNewSelectedAction !== undefined ? + + : null*/} +
    { + Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0 ? +
    {isIntegration ? apps !== undefined && apps !== null && apps.length > 0 ?
    @@ -1589,9 +2742,9 @@ const ParsedAction = (props) => { placeholder = data.example; - if (data.name === "url" && data.value !== undefined && data.value !== null && data.value.length === 0) { - data.value = data.example; - } + // if (data.name === "url") { + // data.value = data.example; + // } // In case of data.example if (data.value === undefined || data.value === null) { data.value = "" @@ -1671,186 +2824,181 @@ const ParsedAction = (props) => { //setSelectedActionParameters(selectedActionParameters) } - var hideBodyButton = ""; - const hideBodyButtonValue = ( -
    - - { - var tag = "TOGGLED" - if (hideBody) { - tag = "UNTOGGLED" - } + var hideBodyButton = ""; + const hideBodyButtonValue = ( +
    + + { + const newHideBody = !hideBody; + setHideBody(newHideBody) + + const updatedParameters = selectedActionParameters.map((param) => { + if (param.name === "body") { + return { ...param, id: newHideBody ? "UNTOGGLED" : "TOGGLED" }; + } + if (param.description === openApiFieldDesc) { + return { ...param, field_active: newHideBody }; + } + return param; + }); + + setSelectedActionParameters(updatedParameters); + + /* + setTimeout(() => { + const element = document.getElementById("hide_body_button"); + if (element) { + element.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + } + }, 100); + */ + }} + name="requires_unique" + /> + } + label={hideBody ? "Show Body" : "Hide Body"} + /> + +
    + ); - setHideBody(!hideBody) - for (let paramkey in Object.entries(selectedActionParameters)) { - var currentItem = selectedActionParameters[paramkey]; - if (currentItem.name === "ssl_verify") { + if (selectedApp.generated && data.name === "body") { + const regex = /\${(\w+)}/g; + const found = placeholder.match(regex); - } + hideBodyButton = hideBodyButtonValue; + if (found === null || !hideBody) { + if (found === null) { - if (currentItem.name === "body") { - currentItem.id = tag - } + if (activateHidingBodyButton !== true) { + setActivateHidingBodyButton(true) + } - if (currentItem.description === openApiFieldDesc) { - currentItem.field_active = !hideBody - } - } - - - // Scroll to hide_body_button - setTimeout(() => { - var element = document.getElementById("hide_body_button") - if (element !== undefined && element !== null) { - // Keep the button a little below the top - element.scrollIntoView({ - behavior: "smooth", - block: "center", - }) - } - }, 100) - + } else { + //console.log("In found: ", found, hideBody) + } + } else { - }} - name="requires_unique" - /> - } - label={hideBody ? "Show Body" : "Hide Body"} - /> -
    -
    - ) + rows = "1"; + disabled = true; + openApiHelperText = "OpenAPI spec: fill the following fields."; - if (selectedApp.generated && data.name === "body") { - const regex = /\${(\w+)}/g; - const found = placeholder.match(regex); + var changed = false; + var tempArray = [] + for (let specKey in found) { + const tmpitem = found[specKey]; + var skip = false; - // setActivateHidingBodyButton(false) - // - hideBodyButton = hideBodyButtonValue; - if (found === null || !hideBody) { - if (found === null) { - setActivateHidingBodyButton(true); - } else { - //console.log("In found: ", found, hideBody) - } - } else { + for (let innerkey in selectedActionParameters) { + if (selectedActionParameters[innerkey].name === tmpitem) { + skip = true; + break; + } + } - rows = "1"; - disabled = true; - openApiHelperText = "OpenAPI spec: fill the following fields."; + if (skip) { + //console.log("SKIPPING ", tmpitem) + continue; + } - var changed = false; - var tempArray = [] - for (let specKey in found) { - const tmpitem = found[specKey]; - var skip = false; + changed = true; + var isRequired = false + // Check if original field name is in the selectedAction.required_body_fields + if (selectedAction.required_body_fields !== undefined && selectedAction.required_body_fields !== null) { + for (let innerkey in selectedAction.required_body_fields) { + if (selectedAction.required_body_fields[innerkey] === tmpitem) { + isRequired = true + break + } + } + } - for (let innerkey in selectedActionParameters) { - if (selectedActionParameters[innerkey].name === tmpitem) { - skip = true; - break; - } - } + tempArray.push({ + action_field: "", + configuration: false, + description: openApiFieldDesc, + example: "", + id: "", + multiline: true, + name: tmpitem, + options: null, + required: isRequired, + schema: { type: "string" }, + skip_multicheck: false, + tags: null, + value: "", + variant: "STATIC_VALUE", + field_active: true, + + autocompleted: true, + }); + } + + var required = selectedActionParameters.filter(item => item.required === true) + var notRequired = selectedActionParameters.filter(item => item.required === false) - if (skip) { - //console.log("SKIPPING ", tmpitem) - continue; - } + if (tempArray.length > 0) { + // Sort tempArray based on tempArray.required + tempArray.sort((a, b) => (a.required < b.required) ? 1 : -1) + // Add all items to the selectedActionParameters array + for (let innerkey in tempArray) { + tempArray[innerkey].id = "ADDED" - changed = true; - var isRequired = false - // Check if original field name is in the selectedAction.required_body_fields - if (selectedAction.required_body_fields !== undefined && selectedAction.required_body_fields !== null) { - for (let innerkey in selectedAction.required_body_fields) { - if (selectedAction.required_body_fields[innerkey] === tmpitem) { - isRequired = true - break - } - } - } + if (tempArray[innerkey].required === true) { + required.push(tempArray[innerkey]) + } else { + notRequired.push(tempArray[innerkey]) + } + } + } + //selectedActionParameters + if (changed) { + // Sort selectedActionParameters based on selectedActionParameters.required + //selectedActionParameters.sort((a, b) => (a.required < b.required) ? 1 : -1) + // Find the "headers" and "queries" field names and put them on the first indexes anyway + var newArray = required.concat(notRequired) + - tempArray.push({ - action_field: "", - configuration: false, - description: openApiFieldDesc, - example: "", - id: "", - multiline: true, - name: tmpitem, - options: null, - required: isRequired, - schema: { type: "string" }, - skip_multicheck: false, - tags: null, - value: "", - variant: "STATIC_VALUE", - field_active: true, - - autocompleted: true, - }); - } - - console.log("TEMP ARRAY: ", tempArray) - var required = selectedActionParameters.filter(item => item.required === true) - var notRequired = selectedActionParameters.filter(item => item.required === false) + setSelectedActionParameters(newArray) + } - if (tempArray.length > 0) { - // Sort tempArray based on tempArray.required - tempArray.sort((a, b) => (a.required < b.required) ? 1 : -1) - // Add all items to the selectedActionParameters array - for (let innerkey in tempArray) { - tempArray[innerkey].id = "ADDED" + return hideBodyButton; + } + } - if (tempArray[innerkey].required === true) { - required.push(tempArray[innerkey]) - } else { - notRequired.push(tempArray[innerkey]) - } - } - } - //selectedActionParameters - - if (changed) { - // Sort selectedActionParameters based on selectedActionParameters.required - //selectedActionParameters.sort((a, b) => (a.required < b.required) ? 1 : -1) - // Find the "headers" and "queries" field names and put them on the first indexes anyway - var newArray = required.concat(notRequired) - - - setSelectedActionParameters(newArray) - } - - return hideBodyButton; - } - } - - if (activateHidingBodyButton === true) { - hideBodyButton = ""; - } + if (activateHidingBodyButton === true) { + hideBodyButton = ""; + } const clickedFieldId = "rightside_field_" + count; @@ -1962,8 +3110,12 @@ const ParsedAction = (props) => { id={clickedFieldId} rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows} color="primary" - defaultValue={data.value} - //value={data.value} + // defaultValue={data.value} + value={ + paramValues.find((param) => param.name === data.name) !== undefined + ? paramValues.find((param) => param.name === data.name).value + : "" + } //options={{ // theme: 'gruvbox-dark', // keyMap: 'sublime', @@ -1983,7 +3135,8 @@ const ParsedAction = (props) => { placeholder={placeholder} onChange={(event) => { //changeActionParameterCodemirror(event, count, data) - changeActionParameter(event, count, data); + // changeActionParameter(event, count, data); + handleParamChange(event, count, data) }} helperText={returnHelperText(data.name, data.value)} onBlur={(event) => { @@ -2378,7 +3531,6 @@ const ParsedAction = (props) => { }; const handleItemClick = (values) => { - console.log("In normal itemclick") if (values === undefined ||values === null ||values.length === 0) { return; } @@ -2401,31 +3553,28 @@ const ParsedAction = (props) => { // Handles the fields under OpenAPI body to be parsed. if (data.name.startsWith("${") && data.name.endsWith("}")) { - console.log("INSIDE VALUE REPLACE: ", data.name, toComplete); - // PARAM FIX - Gonna use the ID field, even though it's a hack const paramcheck = selectedAction.parameters.find( (param) => param.name === "body" - ); + ) + if (paramcheck !== undefined) { - if ( - paramcheck["value_replace"] === undefined || - paramcheck["value_replace"] === null - ) { + if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) { paramcheck["value_replace"] = [ { key: data.name, value: toComplete, }, - ]; + ] } else { - const subparamindex = paramcheck[ - "value_replace" - ].findIndex((param) => param.key === data.name); + const subparamindex = paramcheck["value_replace"] + .findIndex((param) => param.key === data.name); + if (subparamindex === -1) { paramcheck["value_replace"].push({ key: data.name, value: toComplete, - }); + }) + } else { paramcheck["value_replace"][subparamindex]["value"] += toComplete; @@ -2433,7 +3582,9 @@ const ParsedAction = (props) => { } selectedActionParameters[count]["value_replace"] = paramcheck; - selectedAction.parameters[count]["value_replace"] = paramcheck; + + selectedAction.parameters = selectedActionParameters + //selectedAction.parameters[count]["value_replace"] = paramcheck; setSelectedAction(selectedAction); setUpdate(Math.random()); @@ -2450,7 +3601,7 @@ const ParsedAction = (props) => { //selectedAction.parameters[count].value = selectedActionParameters[count].value; //setSelectedAction(selectedAction); //setUpdate(Math.random()); - + setShowDropdown(false); setMenuPosition(null); }; @@ -2753,10 +3904,16 @@ const ParsedAction = (props) => { } const buttonTitle = `Authenticate ${selectedApp.name.replaceAll("_", " ")}` - const hasAutocomplete = data.autocompleted === true + const hasAutocomplete = data?.autocompleted === true + + + if (data.variant === undefined || data.variant === null) { + data.variant = "STATIC_VALUE" + } + return (
    - {hideBodyButton} + {/* {hideBodyButton} */}
    @@ -2890,9 +4047,9 @@ const ParsedAction = (props) => { Autocomplete { - const newversion = selectedApp.versions.find( - (tmpApp) => tmpApp.version == event.target.value - ) - - if (newversion !== undefined && newversion !== null) { - getApp(newversion.id, true) - } - - // Change in all actions in the workflow at the same time and add a toast.success() about it - for (var actionkey in workflow.actions) { - const action = workflow.actions[actionkey] - if (action.app_name === selectedAction.app_name) { - workflow.actions[actionkey].app_version = event.target.value - } - } - - toast.success("Changed version of all nodes to "+event.target.value) - }} - style={{ - marginTop: 10, - backgroundColor: theme.palette.surfaceColor, - backgroundColor: theme.palette.inputColor, - color: "white", - height: 35, - marginleft: 10, - borderRadius: theme.palette.borderRadius, - }} - SelectDisplayProps={{ - style: { - }, - }} - > - {selectedApp.versions.map((data, index) => { - return ( - - {data.version} - - ); - })} - - ) : null} -
    -
    -
    -
    - Name - { - // Copy the name value - const name = e.target.value - const parsedBaseLabel = "$"+baselabel.toLowerCase().replaceAll(" ", "_") - const newname = "$"+name.toLowerCase().replaceAll(" ", "_") - - // Check if it's the same as the current name in use - //if (name === selectedAction.label) { - // console.log("Returning from name thing") - // return - //} - - // Change in actions, triggers & conditions - // Highlight the changes somehow with a glow? - if (workflow.branches !== undefined && workflow.branches !== null) { - for (let [key,keyval] in Object.entries(workflow.branches)) { - if (workflow.branches[key].conditions !== undefined && workflow.branches[key].conditions !== null) { - for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) { - const condition = workflow.branches[key].conditions[subkey] - const sourceparam = condition.source - const destinationparam = condition.destination - - // Should have a smarter way of discovering node names - // Finding index(es) and replacing at the location - if (sourceparam.value.includes("$")) { - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (sourceparam.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } - - console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value) - const extralength = newname.length-parsedBaseLabel.length - sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length) - - console.log("New: ", workflow.branches[key].conditions[subkey].source.value) - } else { - break - } - - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } - - } - } catch (e) { - console.log("Failed value replacement based on index: ", e) - } - } - - if (destinationparam.value.includes("$")) { - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (destinationparam.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } - - console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value) - const extralength = newname.length-parsedBaseLabel.length - destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length) - - console.log("New: ", workflow.branches[key].conditions[subkey].destination.value) - } else { - break - } - - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } - - } - } catch (e) { - console.log("Failed value replacement based on index: ", e) - } - } - } - } - } - } - - for (let [key,keyval] in Object.entries(workflow.actions)) { - if (workflow.actions[key].id === selectedAction.id) { - continue - } - - const params = workflow.actions[key].parameters - console.log(params) - if (params === null || params === undefined) { - continue - } - - for (let [subkey, subkeyval] in Object.entries(params)) { - const param = workflow.actions[key].parameters[subkey]; - if (!param.value.includes("$")) { - continue - } - - // Should have a smarter way of discovering node names - // Do regex? - // Finding index(es) and replacing at the location - // - - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = param.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (param.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = param.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } - - console.log("Old found: ", workflow.actions[key].parameters[subkey].value) - const extralength = newname.length-parsedBaseLabel.length - param.value = param.value.substring(0, foundindex) + newname + param.value.substring(foundindex-extralength+newname.length, param.value.length) - - console.log("New: ", workflow.actions[key].parameters[subkey].value) - } else { - break - } - - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } - - } - } catch (e) { - console.log("Failed value replacement based on index: ", e) - } - } - } - - setWorkflow(workflow); - setUpdate(Math.random()); - baselabel = name - }} - /> -
    - {/*!isCloud ? null :*/} -
    - - - Delay - { - if (actionDelayChange !== undefined) { - actionDelayChange(event) - } - }} - /> - - -
    - {/**/} -
    - - )} - {selectedApp.name !== undefined && - selectedAction.authentication !== null && - selectedAction.authentication !== undefined && - selectedAction.authentication.length === 0 && - requiresAuthentication ? ( -
    - - - - - -
    - ) : null} - - {selectedAction.authentication !== undefined && - selectedAction.authentication !== null && - selectedAction.authentication.length > 0 ? ( -
    - Authentication -
    - - - {/* - - - curaction.authentication = authenticationOptions - if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") - */} - - { - setAuthenticationModalOpen(true); - }} - > - - - -
    -
    - ) : null} - - - {showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? ( -
    - Environment - -
    - ) : null} - - {workflow.execution_variables !== undefined && - workflow.execution_variables !== null && - workflow.execution_variables.length > 0 ? ( -
    - Execution variable (optional) - -
    - ) : null} - - -
    - {/*hideExtraTypes ? null : -
    - Actions -
    - */} - - {setNewSelectedAction !== undefined ? ( - { - // Most popular - // Is categorized - // Uncategorized - return option.category_label !== undefined && option.category_label !== null && option.category_label.length > 0 ? "Most used" : "All Actions"; - }} - renderGroup={(params) => { - - return ( -
  • - {params.group} - {params.children} -
  • - ) - }} - options={renderedActionOptions} - ListboxProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - }, - }} - filterOptions={(options, { inputValue }) => { - //console.log("Option contains?: ", inputValue, options) - const lowercaseValue = inputValue.toLowerCase() - options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue)) - - return options - }} - getOptionLabel={(option) => { - if (option === undefined || option === null || option.name === undefined || option.name === null ) { - return null; - } - - const newname = ( - option.name.charAt(0).toUpperCase() + option.name.substring(1) - ).replaceAll("_", " "); - - return newname; - }} - fullWidth - style={{ - backgroundColor: theme.palette.inputColor, - height: 50, - borderRadius: theme.palette.borderRadius, - }} - onChange={(event, newValue) => { - // Workaround with event lol - if (newValue !== undefined && newValue !== null) { - setNewSelectedAction({ - target: { - value: newValue.name - } - }); - } - }} - renderOption={(props, data, state) => { - var newActionname = data.name; - if (data.label !== undefined && data.label !== null && data.label.length > 0) { - newActionname = data.label; - } - - var newActiondescription = data.description; - //console.log("DESC: ", newActiondescription) - if (data.description === undefined || data.description === null) { - newActiondescription = "Description: No description defined for this action" - } else { - newActiondescription = "Description: "+newActiondescription - } - - const iconInfo = GetIconInfo({ name: data.name }); - const useIcon = iconInfo.originalIcon; - - if (newActionname === undefined || newActionname === null) { - newActionname = "No name" - data.name = "No name" - data.label = "No name" - } - - newActionname = (newActionname.charAt(0).toUpperCase() + newActionname.substring(1)).replaceAll("_", " "); - - var method = "" - var extraDescription = "" - if (data.name.includes("get_")) { - method = "GET" - } else if (data.name.includes("post_")) { - method = "POST" - } else if (data.name.includes("put_")) { - method = "PUT" - } else if (data.name.includes("patch_")) { - method = "PATCH" - } else if (data.name.includes("delete_")) { - method = "DELETE" - } else if (data.name.includes("options_")) { - method = "OPTIONS" - } else if (data.name.includes("connect_")) { - method = "CONNECT" - } - - // FIXME: Should it require a base URL? - if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) { - var extraUrl = "" - const descSplit = data.description.split("\n") - // Last line of descSplit - if (descSplit.length > 0) { - extraUrl = descSplit[descSplit.length-1] - } - - //for (let [line,lineval] in Object.entries(descSplit)) { - // if (descSplit[line].includes("http") && descSplit[line].includes("://")) { - // const urlsplit = descSplit[line].split("/") - // try { - // extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/") - // } catch (e) { - // //console.log("Failed - running with -1") - // extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/") - // } - - - // //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line]) - // //break - // } - //} - - if (extraUrl.length > 0) { - if (extraUrl.includes(" ")) { - extraUrl = extraUrl.split(" ")[0] - } - - if (extraUrl.includes("#")) { - extraUrl = extraUrl.split("#")[0] - } - - extraDescription = `${method} ${extraUrl}` - } else { - //console.log("No url found. Check again :)") - } - } - - return ( - - ); - }} - renderInput={(params) => { - if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) { - const prefixes = ["Post", "Put", "Patch"] - for (let [key,keyval] in Object.entries(prefixes)) { - if (params.inputProps.value.startsWith(prefixes[key])) { - params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1) - if (params.inputProps.value.length > 1) { - params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1) - } - break - } - } - - // Check if it starts with "Get List" and method is "Get" - if (params.inputProps.value.startsWith("Get List")) { - console.log("Get List") - } - } - - return ( - - ); - }} - /> - ) : null} - - {/*setNewSelectedAction !== undefined ? - - : null*/} - -
    - +
    diff --git a/frontend/src/components/WorkflowGrid.jsx b/frontend/src/components/WorkflowGrid.jsx index e10d2131..4c4d112e 100644 --- a/frontend/src/components/WorkflowGrid.jsx +++ b/frontend/src/components/WorkflowGrid.jsx @@ -194,7 +194,7 @@ const AppGrid = props => { // Don't return anything unless refinement works return null } - + return ( {onlyResults !== true ? diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 7197f7d6..807cf2f9 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1,5 +1,5 @@ /* eslint-disable react/no-multi-comp */ -import React, { useState, useEffect, useLayoutEffect } from "react"; +import React, { useState, useEffect, useLayoutEffect, memo, useMemo, useRef } from "react"; import ReactDOM from "react-dom" import theme from "../theme.jsx"; @@ -22,7 +22,6 @@ import { CodeHandler, Img, OuterLink, } from "../views/Docs.jsx"; import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; import algoliasearch from 'algoliasearch/lite'; - import { Zoom, Fade, @@ -554,7 +553,7 @@ const AngularWorkflow = (defaultprops) => { }, [editWorkflowModalOpen]) // New for generated stuff - const releaseToConnectLabel = "Release to Connect" +const releaseToConnectLabel = "Release to Connect" const integrationApps = [{ "id": "integration", "name": "Integration Framework", @@ -1447,18 +1446,18 @@ const AngularWorkflow = (defaultprops) => { trigger.parameters = [] - const topic = document.getElementById('topic')?.value - const bootstrapServers = document.getElementById('bootstrap_servers')?.value - const groupId = document.getElementById('group_id')?.value + const topic = document.getElementById('topic')?.value; + const bootstrapServers = document.getElementById('bootstrap_servers')?.value; + const groupId = document.getElementById('group_id')?.value; //const autoOffsetReset = document.getElementById('auto_offset_reset')?.value; if(topic) { trigger.parameters.push({ name: "topic", value: topic - }) + }); } else { - toast("Please enter the topic name"); + toast("please enter the topic name"); return; } @@ -3296,19 +3295,19 @@ const AngularWorkflow = (defaultprops) => { // don't redirect if it exists const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search; var execFound = new URLSearchParams(cursearch).get("execution_id"); - var sessionToken = new URLSearchParams(cursearch).get("session_token"); + 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..`) - setTimeout(() => { - window.location.pathname = "/workflows"; - }, 2000); - } else if (sessionToken !== null && workflow_id === "3abdfb21-b40f-4e50-b855-ac0d62f83cbe") { + toast(`You don't have access to this workflow or loading failed. Redirecting to workflows in a few seconds..`) + setTimeout(() => { + window.location.pathname = "/workflows"; + }, 2000); +} else if (sessionToken !== null && workflow_id === "3abdfb21-b40f-4e50-b855-ac0d62f83cbe") { toast(`Injecting session token and reloading workflow..`) setTimeout(() => { setCookie("session_token", sessionToken, { path: "/" }); window.location.href = "https://shuffler.io/workflows/3abdfb21-b40f-4e50-b855-ac0d62f83cbe"; }, 2000); - } + } } } @@ -3793,12 +3792,11 @@ const AngularWorkflow = (defaultprops) => { const onNodeDragStop = (event, selectedAction) => { const nodedata = event.target.data(); if (nodedata.id === selectedAction.id) { - //console.log("Same node, return") + //console.log("Same node, return") return } if (nodedata.finished === false) { - //console.log("Node is not finished, return") return } @@ -4134,8 +4132,8 @@ const AngularWorkflow = (defaultprops) => { // Check plus minus 15 in distance from mindistance if (distance > minDistance - 15 && distance < minDistance + 15) { console.log("Within distance of 15, add to existing edge") - } else { - console.log("Outside distance of 15, remove old edge and add new") + } else { + console.log("Outside distance of 15, remove old edge and add new") } } @@ -4160,8 +4158,8 @@ const AngularWorkflow = (defaultprops) => { conditions: [], } }) - } - } + } + } } } @@ -4239,7 +4237,286 @@ const AngularWorkflow = (defaultprops) => { document.removeEventListener("paste", handlePaste, true); } } - }) + }); + + // Should get AI autocompletes + const aiSubmit = (value, setResponseMsg, setSuggestionLoading, inputAction) => { + if (setResponseMsg !== undefined) { + setResponseMsg("") + } + + if (value === undefined || value === "") { + console.log("No value input!") + return + } + + if (setSuggestionLoading !== undefined) { + setSuggestionLoading(true) + } + + console.log("Submit conversation with value: ", value); + + // This is to find sample response and parse it as string + + var AppContext = [] + if (inputAction !== undefined && inputAction !== null) { + const parents = getParents(inputAction) + + console.log("Parents: ", parents) + var actionlist = [] + if (parents.length > 1) { + for (let [key,keyval] in Object.entries(parents)) { + const item = parents[key]; + if (item.label === "Execution Argument") { + continue; + } + + var exampledata = item.example === undefined || item.example === null ? "" : item.example; + // Find previous execution and their variables + //exampledata === "" && + if (workflowExecutions.length > 0) { + // Look for the ID + const found = false; + for (let [key,keyval] in Object.entries(workflowExecutions)) { + if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) { + continue; + } + + var foundResult = workflowExecutions[key].results.find((result) => result.action.id === item.id); + if (foundResult === undefined || foundResult === null) { + continue; + } + + if (foundResult.result !== undefined && foundResult.result !== null) { + foundResult = foundResult.result + } + + const valid = validateJson(foundResult, true) + if (valid.valid) { + if (valid.result.success === false) { + //console.log("Skipping success false autocomplete") + } else { + exampledata = valid.result; + break; + } + } else { + exampledata = foundResult; + } + } + } + + // 1. Take + const itemlabelComplete = item.label === null || item.label === undefined ? "" : item.label.split(" ").join("_"); + + const actionvalue = { + app_name: item.app_name, + action_name: item.name, + label: item.label, + + type: "action", + id: item.id, + name: item.label, + autocomplete: itemlabelComplete, + example: exampledata, + }; + + actionlist.push(actionvalue); + } + } + + var fixedResults = [] + for (var i = 0; i < actionlist.length; i++) { + const item = actionlist[i]; + const responseFix = SetJsonDotnotation(item.example, "") + + // Check if json + const validated = validateJson(responseFix) + var exampledata = responseFix; + if (validated.valid) { + exampledata = JSON.stringify(validated.result) + } + + AppContext.push({ + "app_name": item.app_name, + "action_name": item.action_name, + "label": item.label, + "example": exampledata, + "example_response": exampledata, + }) + } + } + + var conversationData = { + "query": value, + "output_format": "action", + "app_context": AppContext, + + "workflow_id": workflow.id, + } + + if (inputAction !== undefined) { + console.log("Add app context! This should them get parameters directly") + conversationData.output_format = "action_parameters" + + conversationData.app_id = inputAction.app_id + conversationData.app_name = inputAction.app_name + conversationData.action_name = inputAction.name + conversationData.parameters = inputAction.parameters + + if (!value.includes(inputAction.label)) { + conversationData.query = inputAction.label.replaceAll("_", " ") + } + } + + // Onprem not available yet (April 2023) + // Should: Make OpenAI work for them with their own key + //fetch("https://shuffler.io/api/v1/conversation", { + fetch(`${globalUrl}/api/v1/conversation`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(conversationData), + credentials: "include", + }) + .then((response) => { + if (setSuggestionLoading !== undefined) { + setSuggestionLoading(false) + } + + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + console.log("Conversation response: ", responseJson) + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + if (setResponseMsg !== undefined) { + setResponseMsg(responseJson.reason) + } + } + + return + } + + if (inputAction !== undefined) { + console.log("In input action! Should check params if they match, and add suggestions") + + if (responseJson.parameters === undefined || responseJson.parameters.length === 0) { + return + } + + var changed = false + + for (let paramkey in inputAction.parameters) { + const actionParam = inputAction.parameters[paramkey] + + if (actionParam.autocompleted === true) { + continue + } + + if (actionParam.configuration === true && actionParam.name !== "url") { + continue + } + + if (actionParam.value !== "" && actionParam.value !== actionParam.example) { + console.log("Skipping: ", actionParam) + continue + } + + for (let respParam of responseJson.parameters) { + if (respParam.name === actionParam.name) { + console.log("Found match for param: ", respParam) + + if (respParam.value === "") { + break + } + + changed = true + + inputAction.parameters[paramkey].autocompleted = true + inputAction.parameters[paramkey].value = respParam.value + break + } + } + } + + if (changed === true) { + console.log("Setting action! Force update pls :)") + setUpdate(Math.random()) + setSelectedAction(inputAction) + } + + return + } + + console.log("Suggestionbox location: ", suggestionBox) + + // Add action + if (responseJson.app_name !== undefined && responseJson.app_name !== null) { + // Always added to 0, 0 + // Should use suggestionBox.position.x, suggestionBox.position.y + var newitem = { + "data": responseJson, + "position": { + "x": suggestionBox.node_position.x !== undefined ? suggestionBox.node_position.x : 0, + "y": suggestionBox.node_position.y !== undefined ? suggestionBox.node_position.y + 100 : 0, + }, + "group": "nodes", + } + + newitem.type = "ACTION" + newitem.isStartNode = false + newitem.data.id = uuidv4() + newitem.data.type = "ACTION" + newitem.data.isStartNode = false + + newitem.data.is_valid = true + newitem.data.isValid = true + + cy.add({ + group: newitem.group, + data: newitem.data, + position: newitem.position, + }); + + // Add edge + const newId = uuidv4() + cy.add({ + group: "edges", + data: { + id: newId, + _id: newId, + source: suggestionBox.attachedTo, + target: newitem.data.id, + } + }) + //label: "Generated", + + setSuggestionBox({ + "position": { + "top": 500, + "left": 500, + }, + "open": false, + "attachedTo": "", + }); + } + }) + .catch((error) => { + if (setSuggestionLoading !== undefined) { + setSuggestionLoading(false) + } + + console.log("Conv response error: ", error); + }); + } + + // Nodeselectbatching: // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once @@ -4250,15 +4527,12 @@ const AngularWorkflow = (defaultprops) => { //const data = JSON.parse(JSON.stringify(event.target.data())) const data = event.target.data() - - console.log("NODE SELECT: ", data) - if (data.app_name === "Shuffle Workflow") { - console.log("Shuffle Workflow selected") - if ((data.parameters !== undefined) && (data.parameters.length > 0)) { - getWorkflowApps(data.parameters[0].value) + if (data.app_name === "Shuffle Workflow") { + if ((data.parameters !== undefined) && (data.parameters.length > 0)) { + getWorkflowApps(data.parameters[0].value) + } } - } if (data.buttonType == "ACTIONSUGGESTION") { const attachedToId = data.attachedTo @@ -4323,6 +4597,7 @@ const AngularWorkflow = (defaultprops) => { workflow.actions[foundindex].name = curaction.name setWorkflow(workflow) + console.log(workflow) } break } @@ -5363,10 +5638,11 @@ const AngularWorkflow = (defaultprops) => { // Checks for errors in edges when they're added const onEdgeAdded = (event) => { - const edge = event.target.data() - //console.log("EDGE ADDED!: ", edge) + setLastSaved(false); + const edge = event.target.data(); + + //console.log("edge added: ", edge) if (edge.source === undefined && edge.target === undefined) { - console.log("Edge source and target is undefined") return } @@ -5380,7 +5656,6 @@ const AngularWorkflow = (defaultprops) => { const sourcenode = cy.getElementById(edge.source) const destinationnode = cy.getElementById(edge.target) if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { - console.log("Source or destination node is undefined") } else { //console.log("Edge added: Is it a trigger? If so, check if it already has a branch and remove it: ", sourcenode.data()) if (sourcenode.data("type") === "TRIGGER") { @@ -5393,10 +5668,10 @@ const AngularWorkflow = (defaultprops) => { console.log("Node: ", targetedge) if (targetedge !== -1) { + event.target.remove() //console.log("Found branch already!") toast.error("Triggers can have exactly one target node") - event.target.remove() return @@ -5417,10 +5692,6 @@ const AngularWorkflow = (defaultprops) => { } } - if (edge.decorator === true) { - console.log("Doing nothing to branch because decorator") - return - } var targetnode = workflow.triggers.findIndex( (data) => data.id === edge.target @@ -5460,14 +5731,15 @@ const AngularWorkflow = (defaultprops) => { } } - if (eventTarget.data("isDescriptor") === true || eventTarget.data("type") === "COMMENT") { + if ( + eventTarget.data("isDescriptor") === true || + eventTarget.data("type") === "COMMENT" + ) { console.log("Removing because of descriptor or comment") - event.target.remove() - return + event.target.remove(); + return; } - - setLastSaved(false) targetnode = -1; // Check if: @@ -5475,51 +5747,38 @@ const AngularWorkflow = (defaultprops) => { // dest == dest && source == source // backend: check all children? to stop recursion var found = false; - const branches = cy.edges().jsons() - - const startNode = cy.nodes().jsons().find((node) => node.data.isStartNode === true) - var startnodeId = workflow.start - if (startNode !== undefined && startNode !== null) { - startnodeId = startNode.data.id - } - - //for (let branchkey in workflow.branches) { - for (let branchkey in branches) { - const branch = branches[branchkey].data - - //if (workflow.branches[branchkey].destination_id === edge.source && workflow.branches[branchkey].source_id === edge.target) { - if (branch.target === edge.source && branch.source === edge.target) { - toast("A branch in the opposite direction already exists") - event.target.remove() - found = true - break - - //} else if (workflow.branches[branchkey].destination_id === edge.target && workflow.branches[branchkey].source_id === edge.source) { - } else if (branch.target === edge.target && branch.source === edge.source) { - - if (branch.conditions === undefined) { - // Edgehandles - } else { - console.log("Removing because the same branch already exists") - event.target.remove() - - found = true - break - } - } else if (edge.target === startnodeId) { - targetnode = workflow.triggers.findIndex((data) => data.id === edge.source) + 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; + } else if ( + workflow.branches[branchkey].destination_id === edge.target && + workflow.branches[branchkey].source_id === edge.source + ) { + //toast("That branch already exists"); + event.target.remove(); + found = true; + break; + } else if (edge.target === workflow.start) { + targetnode = workflow.triggers.findIndex( + (data) => data.id === edge.source + ); if (targetnode === -1) { if (targetnode.type !== "TRIGGER") { - toast("Can't make arrow to starting node") - event.target.remove() - break + toast("Can't make arrow to starting node"); + event.target.remove(); + break; } found = true; } - //} else if (edge.source === workflow.branches[branchkey].source_id) { - } else if (edge.source === branch.source) { + } else if (edge.source === workflow.branches[branchkey].source_id) { // FIXME: Verify multi-target for triggers // 1. Check if destination exists // 2. Check if source is a trigger @@ -5563,6 +5822,7 @@ const AngularWorkflow = (defaultprops) => { newdst !== null ) { const dstdata = RunAutocompleter(newdst.data()); + //console.log("DST Autocompleter: ", dstdata); } var newbranch = { @@ -6796,8 +7056,6 @@ const AngularWorkflow = (defaultprops) => { const allNodes = cy.nodes().jsons(); if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") { - console.log("In this :)") - var found = false; for (let nodekey in allNodes) { const currentNode = allNodes[nodekey]; @@ -8305,11 +8563,15 @@ const AngularWorkflow = (defaultprops) => { ); }; - const handleSetTab = (event, newValue) => { - setCurrentView(newValue); - }; + const HandleLeftView = () => { + // console.log("HandleLeftView Rendered!") + + const handleSetTab = (event, newValue) => { + setCurrentView(newValue); + }; + // Defaults to apps. var thisview = ( { const AppView = (props) => { const { allApps, prioritizedApps, filteredApps, extraApps } = props; - + // console.log("AppView Rendered!") //extraApps, const [visibleApps, setVisibleApps] = React.useState( Array.prototype.concat.apply( @@ -9577,7 +9839,9 @@ const AngularWorkflow = (defaultprops) => {
    ); - }; +} + + const getNextActionName = (appName) => { var highest = ""; @@ -9788,7 +10052,6 @@ const AngularWorkflow = (defaultprops) => { } } - console.log("New selected action: ", newSelectedAction) setSelectedAction(newSelectedAction) setUpdate(Math.random()) @@ -9824,44 +10087,43 @@ const AngularWorkflow = (defaultprops) => { // appname & version // description // ACTION select - const selectedNameChange = (event) => { - event.target.value = event.target.value.replaceAll("(", ""); - event.target.value = event.target.value.replaceAll(")", ""); - event.target.value = event.target.value.replaceAll("]", ""); - event.target.value = event.target.value.replaceAll("[", ""); - event.target.value = event.target.value.replaceAll("{", ""); - event.target.value = event.target.value.replaceAll("}", ""); - event.target.value = event.target.value.replaceAll("*", ""); - event.target.value = event.target.value.replaceAll("!", ""); - event.target.value = event.target.value.replaceAll("@", ""); - event.target.value = event.target.value.replaceAll("#", ""); - event.target.value = event.target.value.replaceAll("$", ""); - event.target.value = event.target.value.replaceAll("%", ""); - event.target.value = event.target.value.replaceAll("&", ""); - event.target.value = event.target.value.replaceAll("#", ""); - event.target.value = event.target.value.replaceAll(".", ""); - event.target.value = event.target.value.replaceAll(",", ""); - event.target.value = event.target.value.replaceAll(" ", "_"); - event.target.value = event.target.value.replaceAll("^", "_"); - event.target.value = event.target.value.replaceAll("'", "_"); - event.target.value = event.target.value.replaceAll("\"", "_"); - event.target.value = event.target.value.replaceAll("\"", "_"); - event.target.value = event.target.value.replaceAll(":", "_"); - event.target.value = event.target.value.replaceAll(";", "_"); - event.target.value = event.target.value.replaceAll("=", "_"); - event.target.value = event.target.value.replaceAll("+", "_"); - - selectedAction.label = event.target.value; + const selectedNameChange = (appActionName) => { + appActionName = appActionName.replaceAll("(", ""); + appActionName = appActionName.replaceAll(")", ""); + appActionName = appActionName.replaceAll("]", ""); + appActionName = appActionName.replaceAll("[", ""); + appActionName = appActionName.replaceAll("{", ""); + appActionName = appActionName.replaceAll("}", ""); + appActionName = appActionName.replaceAll("*", ""); + appActionName = appActionName.replaceAll("!", ""); + appActionName = appActionName.replaceAll("@", ""); + appActionName = appActionName.replaceAll("#", ""); + appActionName = appActionName.replaceAll("$", ""); + appActionName = appActionName.replaceAll("%", ""); + appActionName = appActionName.replaceAll("&", ""); + appActionName = appActionName.replaceAll("#", ""); + appActionName = appActionName.replaceAll(".", ""); + appActionName = appActionName.replaceAll(",", ""); + appActionName = appActionName.replaceAll(" ", "_"); + appActionName = appActionName.replaceAll("^", "_"); + appActionName = appActionName.replaceAll("'", "_"); + appActionName = appActionName.replaceAll("\"", "_"); + appActionName = appActionName.replaceAll("\"", "_"); + appActionName = appActionName.replaceAll(":", "_"); + appActionName = appActionName.replaceAll(";", "_"); + appActionName = appActionName.replaceAll("=", "_"); + appActionName = appActionName.replaceAll("+", "_"); + selectedAction.label = appActionName; setSelectedAction(selectedAction); }; - const actionDelayChange = (event) => { - if (isNaN(event.target.value)) { - console.log("NAN: ", event.target.value) + const actionDelayChange = (delay) => { + if (isNaN(delay)) { + console.log("NAN: ", delay) return } - const parsedNumber = parseInt(event.target.value) + const parsedNumber = parseInt(delay) if (parsedNumber > 86400) { console.log("Max number is 1 day (86400)") return @@ -11297,11 +11559,11 @@ const AngularWorkflow = (defaultprops) => { {/* Check if dest is the same as start */} - {conditionsDisabled ? - - Conditions are unavailable between triggers and the startnode. - - : null} + {conditionsDisabled ? + + Conditions are unavailable between triggers and the startnode. + + : null}
    {/* @@ -12155,7 +12417,7 @@ const AngularWorkflow = (defaultprops) => { return transformedData; - } + }; const AppAuthSelector = ({ appAuthData }) => { const [selectedAuth, setSelectedAuth] = useState(""); @@ -12168,8 +12430,11 @@ const AngularWorkflow = (defaultprops) => { const handleShowingValue = (appName) => { let mappingWithName = {} let listWithValues = workflow.triggers[selectedTriggerIndex].parameters[5]?.value.split(";").filter(e => e).map(e => e.split("=")) - console.log("LIST WITH VALUES: ", listWithValues) + if (listWithValues === undefined || listWithValues === null || listWithValues.length === 0) { + return "no-overrides"; + } + for (let i = 0; i < listWithValues.length; i++) { mappingWithName[listWithValues[i][0]] = listWithValues[i][1] } @@ -12397,9 +12662,6 @@ const AngularWorkflow = (defaultprops) => { setActionlist(actionlist); } - // Shows nested list of nodes > their JSON lists - const ActionlistWrapper = (props) => { - const { data } = props; const handleMenuClose = () => { setUpdate(Math.random()); @@ -12445,302 +12707,7 @@ const AngularWorkflow = (defaultprops) => { marginRight: 15, }; - return ( - { - handleMenuClose(); - }} - open={!!menuPosition} - style={{ - border: `2px solid #f85a3e`, - color: "white", - marginTop: 2, - }} - > - {actionlist.map((innerdata) => { - const icon = - innerdata.type === "action" ? ( - - ) : innerdata.type === "workflow_variable" || - innerdata.type === "execution_variable" ? ( - - ) : ( - - ); - - const handleExecArgumentHover = (inside) => { - var exec_text_field = document.getElementById( - "execution_argument_input_field" - ); - if (exec_text_field !== null) { - if (inside) { - exec_text_field.style.border = "2px solid #f85a3e"; - } else { - exec_text_field.style.border = ""; - } - } - - // Also doing arguments - if ( - workflow.triggers !== undefined && - workflow.triggers !== null && - workflow.triggers.length > 0 - ) { - for (let triggerkey in workflow.triggers) { - const item = workflow.triggers[triggerkey]; - - if (cy !== undefined) { - var node = cy.getElementById(item.id); - if (node.length > 0) { - if (inside) { - node.addClass("shuffle-hover-highlight"); - } else { - node.removeClass("shuffle-hover-highlight"); - } - } - } - } - } - } - - const handleActionHover = (inside, actionId) => { - if (cy !== undefined) { - var node = cy.getElementById(actionId); - if (node.length > 0) { - if (inside) { - node.addClass("shuffle-hover-highlight"); - } else { - node.removeClass("shuffle-hover-highlight"); - } - } - } - }; - - const handleMouseover = () => { - if (innerdata.type === "Execution Argument") { - handleExecArgumentHover(true); - } else if (innerdata.type === "action") { - handleActionHover(true, innerdata.id); - } - }; - - const handleMouseOut = () => { - if (innerdata.type === "Execution Argument") { - handleExecArgumentHover(false); - } else if (innerdata.type === "action") { - handleActionHover(false, innerdata.id); - } - }; - - var parsedPaths = []; - console.log("Found example data: ", innerdata.example) - if (typeof innerdata.example === "object") { - parsedPaths = GetParsedPaths(innerdata.example, ""); - } - - const coverColor = "#82ccc3" - - return parsedPaths.length > 0 ? ( - - {/* - - {icon} {innerdata.name} -
    - } - parentMenuOpen={!!menuPosition} - style={{ - backgroundColor: theme.palette.inputColor, - color: "white", - minWidth: 250, - }} - onClick={() => { - handleItemClick([innerdata]); - }} - > - {parsedPaths.map((pathdata, index) => { - // FIXME: Should be recursive in here - const icon = - pathdata.type === "value" ? ( - - ) : pathdata.type === "list" ? ( - - ) : ( - - ) - - return ( - { }} - onClick={() => { - handleItemClick([innerdata, pathdata]); - }} - > - -
    - {icon} {pathdata.name} -
    -
    -
    - ); - })} - - */} - - - {icon} {innerdata.name} -
    - } - parentMenuOpen={!!menuPosition} - style={{ - color: "white", - minWidth: 250, - maxWidth: 250, - maxHeight: 50, - overflow: "hidden", - }} - onClick={() => { - console.log("CLICKED: ", innerdata); - console.log(innerdata.example) - handleItemClick([innerdata]); - }} - > - - - { - //console.log("HOVER: ", pathdata); - }} - onClick={() => { - handleItemClick([innerdata]); - }} - > - - {innerdata.name} - - - - {parsedPaths.map((pathdata, index) => { - // FIXME: Should be recursive in here - // - const icon = - pathdata.type === "value" ? ( - - ) : pathdata.type === "list" ? ( - - ) : ( - - ); - // - - const indentation_count = (pathdata.name.match(/\./g) || []).length+1 - const baseIndent =
    - //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0 - const boxPadding = 0 - const namesplit = pathdata.name.split(".") - const newname = namesplit[namesplit.length-1] - return ( - { - //console.log("HOVER: ", pathdata); - }} - onClick={() => { - handleItemClick([innerdata, pathdata]); - }} - > - -
    - {Array(indentation_count).fill().map((subdata, subindex) => { - return ( - baseIndent - ) - })} - {icon} {newname} - {pathdata.type === "list" ? { - - }} /> : null} -
    -
    -
    - ); - })} - - - - ) : ( - handleMouseover()} - onMouseOut={() => { - handleMouseOut(); - }} - onClick={() => { - handleItemClick([innerdata]); - }} - > - -
    - {icon} {innerdata.name} -
    -
    -
    - ); - })} - - ); - }; - + if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { if (workflow.triggers[selectedTriggerIndex] === undefined) { return null; @@ -12851,7 +12818,7 @@ const AngularWorkflow = (defaultprops) => { data: newbranch, }; - cy.add(cybranch) + cy.add(cybranch); } console.log("Value to be set: ", e.target.value); @@ -13353,10 +13320,298 @@ const AngularWorkflow = (defaultprops) => { }} /> {!showDropdown ? null : - + { + handleMenuClose(); + }} + open={!!menuPosition} + style={{ + border: `2px solid #f85a3e`, + color: "white", + marginTop: 2, + }} + > + {actionlist.map((innerdata) => { + const icon = + innerdata.type === "action" ? ( + + ) : innerdata.type === "workflow_variable" || + innerdata.type === "execution_variable" ? ( + + ) : ( + + ); + + const handleExecArgumentHover = (inside) => { + var exec_text_field = document.getElementById( + "execution_argument_input_field" + ); + if (exec_text_field !== null) { + if (inside) { + exec_text_field.style.border = "2px solid #f85a3e"; + } else { + exec_text_field.style.border = ""; + } + } + + // Also doing arguments + if ( + workflow.triggers !== undefined && + workflow.triggers !== null && + workflow.triggers.length > 0 + ) { + for (let triggerkey in workflow.triggers) { + const item = workflow.triggers[triggerkey]; + + if (cy !== undefined) { + var node = cy.getElementById(item.id); + if (node.length > 0) { + if (inside) { + node.addClass("shuffle-hover-highlight"); + } else { + node.removeClass("shuffle-hover-highlight"); + } + } + } + } + } + } + + const handleActionHover = (inside, actionId) => { + if (cy !== undefined) { + var node = cy.getElementById(actionId); + if (node.length > 0) { + if (inside) { + node.addClass("shuffle-hover-highlight"); + } else { + node.removeClass("shuffle-hover-highlight"); + } + } + } + }; + + const handleMouseover = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(true); + } else if (innerdata.type === "action") { + handleActionHover(true, innerdata.id); + } + }; + + const handleMouseOut = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(false); + } else if (innerdata.type === "action") { + handleActionHover(false, innerdata.id); + } + }; + + var parsedPaths = []; + console.log("Found example data: ", innerdata.example) + if (typeof innerdata.example === "object") { + parsedPaths = GetParsedPaths(innerdata.example, ""); + } + + const coverColor = "#82ccc3" + + return parsedPaths.length > 0 ? ( + + {/* + + {icon} {innerdata.name} +
    + } + parentMenuOpen={!!menuPosition} + style={{ + backgroundColor: theme.palette.inputColor, + color: "white", + minWidth: 250, + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ) + + return ( + { }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
    + {icon} {pathdata.name} +
    +
    +
    + ); + })} + + */} + + + {icon} {innerdata.name} +
    + } + parentMenuOpen={!!menuPosition} + style={{ + color: "white", + minWidth: 250, + maxWidth: 250, + maxHeight: 50, + overflow: "hidden", + }} + onClick={() => { + console.log("CLICKED: ", innerdata); + console.log(innerdata.example) + handleItemClick([innerdata]); + }} + > + + + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + + {innerdata.name} + + + + {parsedPaths.map((pathdata, index) => { + // FIXME: Should be recursive in here + // + const icon = + pathdata.type === "value" ? ( + + ) : pathdata.type === "list" ? ( + + ) : ( + + ); + // + + const indentation_count = (pathdata.name.match(/\./g) || []).length+1 + const baseIndent =
    + //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0 + const boxPadding = 0 + const namesplit = pathdata.name.split(".") + const newname = namesplit[namesplit.length-1] + return ( + { + //console.log("HOVER: ", pathdata); + }} + onClick={() => { + handleItemClick([innerdata, pathdata]); + }} + > + +
    + {Array(indentation_count).fill().map((subdata, subindex) => { + return ( + baseIndent + ) + })} + {icon} {newname} + {pathdata.type === "list" ? { + + }} /> : null} +
    +
    +
    + ); + })} + + + + ) : ( + handleMouseover()} + onMouseOut={() => { + handleMouseOut(); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + +
    + {icon} {innerdata.name} +
    +
    +
    + ); + })} + } {/*
    {
    - {/*
    -
    -
    -
    - Auth Override -
    -
    +
    +
    +
    + Auth Override +
    +
    -
    -
    - -
    -
    -
    +
    +
    + +
    +
    +
    - */}
    - ) + ); } return null; @@ -13669,7 +13922,48 @@ const AngularWorkflow = (defaultprops) => { workflow.triggers[selectedTriggerIndex].parameters.length > 2 ? workflow.triggers[selectedTriggerIndex].parameters[2].value : ""; - } + }else if( + selectedTrigger.trigger_type === "USERINPUT" + ){ + if ( + workflow.triggers[selectedTriggerIndex].parameters === undefined || + workflow.triggers[selectedTriggerIndex].parameters === null || + workflow.triggers[selectedTriggerIndex].parameters.length === 0 + ) { + workflow.triggers[selectedTriggerIndex].parameters = []; + workflow.triggers[selectedTriggerIndex].parameters[0] = { + name: "alertinfo", + value: "Do you want to continue the workflow? Start parameters: $exec", + }; + + // boolean, + workflow.triggers[selectedTriggerIndex].parameters[1] = { + name: "options", + value: "boolean", + }; + + // email,sms,app ... + workflow.triggers[selectedTriggerIndex].parameters[2] = { + name: "type", + value: "subflow", + }; + + workflow.triggers[selectedTriggerIndex].parameters[3] = { + name: "email", + value: "test@test.com", + }; + workflow.triggers[selectedTriggerIndex].parameters[4] = { + name: "sms", + value: "0000000", + }; + workflow.triggers[selectedTriggerIndex].parameters[5] = { + name: "subflow", + value: "", + }; + + setWorkflow(workflow); + } + } } const WebhookSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "WEBHOOK" ? null : @@ -13793,6 +14087,7 @@ const AngularWorkflow = (defaultprops) => { setUpdate(Math.random()); } + document.activeElement.blur(); }} >
    @@ -14464,48 +14759,7 @@ const AngularWorkflow = (defaultprops) => { }) } - const UserinputSidebar = () => { - if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers[selectedTriggerIndex] !== undefined) { - if ( - workflow.triggers[selectedTriggerIndex].parameters === undefined || - workflow.triggers[selectedTriggerIndex].parameters === null || - workflow.triggers[selectedTriggerIndex].parameters.length === 0 - ) { - workflow.triggers[selectedTriggerIndex].parameters = []; - workflow.triggers[selectedTriggerIndex].parameters[0] = { - name: "alertinfo", - value: "Do you want to continue the workflow? Start parameters: $exec", - }; - - // boolean, - workflow.triggers[selectedTriggerIndex].parameters[1] = { - name: "options", - value: "boolean", - }; - - // email,sms,app ... - workflow.triggers[selectedTriggerIndex].parameters[2] = { - name: "type", - value: "subflow", - }; - - workflow.triggers[selectedTriggerIndex].parameters[3] = { - name: "email", - value: "test@test.com", - }; - workflow.triggers[selectedTriggerIndex].parameters[4] = { - name: "sms", - value: "0000000", - }; - workflow.triggers[selectedTriggerIndex].parameters[5] = { - name: "subflow", - value: "", - }; - - setWorkflow(workflow); - } - - return ( + const UserinputSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "USERINPUT" ? null :

    {selectedTrigger.app_name} @@ -14703,6 +14957,7 @@ const AngularWorkflow = (defaultprops) => { onChange={(event, newValue) => { console.log("Changed autocomplete!") handleWorkflowSelectionUpdate({ target: { value: newValue } }, true) + event.target.blur(); }} renderOption={(props, data, state) => { if (data.id === workflow.id) { @@ -14731,6 +14986,7 @@ const AngularWorkflow = (defaultprops) => { value: data, }}, true) + document.activeElement.blur(); }} > @@ -14842,12 +15098,6 @@ const AngularWorkflow = (defaultprops) => {

    - ) - } - - return null - } - const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "SCHEDULE" ? null :

    @@ -15372,8 +15622,12 @@ const AngularWorkflow = (defaultprops) => { right: 0, left: isMobile ? 20 : leftBarSize + 20, top: isMobile ? 30 : appBarSize + 20, + pointerEvents: "none", } + + + const TopCytoscapeBar = (props) => { if (workflow.public === true) { return null @@ -15389,7 +15643,7 @@ const AngularWorkflow = (defaultprops) => {
    {

    {workflow.name}

    @@ -16154,8 +16409,6 @@ const AngularWorkflow = (defaultprops) => { } } - /* - // Infinitely annoying. Need a new bind if (( event.ctrlKey || event.metaKey ) && event.shiftKey) { console.log("Shift key pressed") if (!workflow.public && executionModalOpen) { @@ -16167,8 +16420,7 @@ const AngularWorkflow = (defaultprops) => { setExecutionModalView(0); } } - */ - } + }; document.addEventListener('keydown', handleKeyDown); @@ -16715,115 +16967,13 @@ const AngularWorkflow = (defaultprops) => { }; const RightSideBar = (props) => { - const { - //workflow, - //setWorkflow, - //setSelectedAction, - //setUpdate, - //selectedApp, - //workflowExecutions, - //setSelectedResult, - //selectedAction, - //setSelectedApp, - //setSelectedTrigger, - //setSelectedEdge, - //setCurrentView, - //cy, - //setAuthenticationModalOpen, - //setVariablesModalOpen, - //setCodeModalOpen, - //selectedNameChange, - //rightsidebarStyle, - //showEnvironment, - //selectedActionEnvironment, - //environments, - //setNewSelectedAction, - //appApiViewStyle, - //globalUrl, - //setSelectedActionEnvironment, - //requiresAuthentication, - //scrollConfig, - //setScrollConfig, - } = props; - - if (!rightSideBarOpen) { - return null; - } var defaultReturn = null - if (Object.getOwnPropertyNames(selectedAction).length > 0) { - if (Object.getOwnPropertyNames(selectedAction).length === 0) { - return null; - } - - defaultReturn = - - } else if (Object.getOwnPropertyNames(selectedComment).length > 0) { + if (Object.getOwnPropertyNames(selectedComment).length > 0) { defaultReturn = } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { - if (selectedTrigger.trigger_type === "SCHEDULE") { - // Handled elsewhere as an experiment - defaultReturn = null - } else if (selectedTrigger.trigger_type === "WEBHOOK") { - defaultReturn = null - } else if (selectedTrigger.trigger_type === "SUBFLOW") { - defaultReturn = - } else if (selectedTrigger.trigger_type === "EMAIL") { + if (selectedTrigger.trigger_type === "EMAIL") { defaultReturn = - } else if (selectedTrigger.trigger_type === "USERINPUT") { - defaultReturn = } else if (selectedTrigger.trigger_type === undefined) { //defaultReturn = return null; @@ -17674,7 +17824,7 @@ const AngularWorkflow = (defaultprops) => { console.log("IN useeffectt (2)" + collapsed) return; } - }) + },[]) /* componentWillUpdate = (nextProps, nextState) => { console.log(nextProps, nextState) @@ -18137,7 +18287,7 @@ const AngularWorkflow = (defaultprops) => {
    {foundnotifications > 0 ? - + { @@ -19196,11 +19346,10 @@ const AngularWorkflow = (defaultprops) => { return "The queries or data sent to the API is most likely wrong (400). Check the body of the result for more information." } - /* if (result.status === 200 || result.status === 201 || result.status === 204) { return "It looks like the result was successful! If it didn't work, make sure to check if the body you are sending was correct." } - */ + // Validate and check for newlines if (result.success !== false) { @@ -19684,12 +19833,68 @@ const AngularWorkflow = (defaultprops) => {
    {executionModal} - - + + { + rightSideBarOpen && Object.getOwnPropertyNames(selectedAction).length > 0 ? +
    + +
    : null + } {/* Looks for triggers" */} {/* Only fixed the ones that require scrolling on a small screen */} {/* Most important: Actions. But these are a lot more complex */} - {rightSideBarOpen && (selectedTrigger.trigger_type === "SCHEDULE" || selectedTrigger.trigger_type === "WEBHOOK" || selectedTrigger.trigger_type === "PIPELINE") ? + {rightSideBarOpen && (selectedTrigger.trigger_type === "SCHEDULE" || selectedTrigger.trigger_type === "WEBHOOK" || selectedTrigger.trigger_type === "PIPELINE" || selectedTrigger.trigger_type === "USERINPUT") ?
    {Object.getOwnPropertyNames(selectedTrigger).length > 0 ? selectedTrigger.trigger_type === "SCHEDULE" ? @@ -19698,10 +19903,19 @@ const AngularWorkflow = (defaultprops) => { PipelineSidebar : selectedTrigger.trigger_type === "WEBHOOK" ? WebhookSidebar + : selectedTrigger.trigger_type === "USERINPUT" ? + UserinputSidebar : null : null}
    : null} + + { + rightSideBarOpen && selectedTrigger.trigger_type === "SUBFLOW"&& Object.getOwnPropertyNames(selectedTrigger).length > 0 ? +
    + +
    : null + } {/* {
    Configuration options for {selectedOption}
    - - {selectedOption === "Kafka Queue" ? -
    + {selectedOption === "Kafka Queue" && ( + <> Topic { placeholder={"earliest"} defaultValue={(selectedTrigger?.parameters?.find(param => param.name === "auto_offset_reset")?.value) || ''} /> */} -
    - : null} - - param.name === "bootstrap_servers")?.value) || ''} - /> - + + )}
    + + + */} + +

    ); }; @@ -12212,6 +12334,10 @@ const releaseToConnectLabel = "Release to Connect" let mappingWithName = {} let listWithValues = workflow.triggers[selectedTriggerIndex].parameters[5]?.value.split(";").filter(e => e).map(e => e.split("=")) console.log("LIST WITH VALUES: ", listWithValues) + if (listWithValues === undefined || listWithValues === null || listWithValues.length === 0) { + return "no-overrides"; + } + for (let i = 0; i < listWithValues.length; i++) { mappingWithName[listWithValues[i][0]] = listWithValues[i][1] } @@ -14875,7 +15001,6 @@ const releaseToConnectLabel = "Release to Connect"
    - const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "SCHEDULE" ? null :

    @@ -16746,7 +16871,6 @@ const releaseToConnectLabel = "Release to Connect" const RightSideBar = (props) => { - var defaultReturn = null if (Object.getOwnPropertyNames(selectedComment).length > 0) { defaultReturn = From fab2c9569661aa9369cbfa198e736b89271fbd9d Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 20 Jun 2024 23:59:15 +0200 Subject: [PATCH 35/46] Minor fixes for admin & oauth2 --- frontend/src/components/Oauth2Auth.jsx | 15 +- frontend/src/views/Admin.jsx | 2 +- frontend/src/views/AngularWorkflow.jsx | 285 ++++++++++++++++--------- frontend/src/views/LoginPage.jsx | 2 +- frontend/src/views/Workflows.jsx | 45 +++- 5 files changed, 231 insertions(+), 118 deletions(-) diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index 1ebf81dd..f0c20a64 100755 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -476,15 +476,15 @@ const AuthenticationOauth2 = (props) => { var defaultPrompt = "login" if (prompt !== undefined && prompt !== null && prompt.length > 0) { defaultPrompt = prompt - } + } - var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=${defaultPrompt}&scope=${resources}&state=${state}&access_type=offline`; + var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=${defaultPrompt}&scope=${resources}&state=${state}&access_type=offline`; - if (admin_consent === true) { - console.log("Running Oauth2 WITH admin consent") - //url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`; - url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`; - } + if (admin_consent === true) { + console.log("Running Oauth2 WITH admin consent") + //url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`; + url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`; + } // Force new consent //const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`; @@ -997,7 +997,6 @@ const AuthenticationOauth2 = (props) => { color: "white", padding: 5, minWidth: 300, - maxWidth: 300, }} onChange={(e, value) => { //handleScopeChange(e) diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 3fb82bc3..8546e887 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -3745,7 +3745,7 @@ If you're interested, please let me know a time that works for you, or set up a aria-label="disabled tabs example" > Edit Details /> - Cloud Synchronization /> + Org Limits & Cloud Sync /> Priorities /> Billing & Stats /> Branding (Beta) /> diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index bc863f68..f9218bc2 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -2144,23 +2144,25 @@ const releaseToConnectLabel = "Release to Connect" const executeWorkflow = (executionArgument, startNode, hasSaved) => { + if (hasSaved === false) { + setExecutionRequestStarted(true) + saveWorkflow(workflow, executionArgument, startNode); + //console.log("FIXME: Might have forgotten to save before executing."); + return; + } + + if (workflow.public) { + toast("Save it to get a new version"); + } + + var returncheck = monitorUpdates(); + if (!returncheck) { + toast("No startnode set."); + return; + } + ReactDOM.unstable_batchedUpdates(() => { - if (hasSaved === false) { - setExecutionRequestStarted(true); - saveWorkflow(workflow, executionArgument, startNode); - //console.log("FIXME: Might have forgotten to save before executing."); - return; - } - if (workflow.public) { - toast("Save it to get a new version"); - } - - var returncheck = monitorUpdates(); - if (!returncheck) { - toast("No startnode set."); - return; - } setVisited([]) setExecutionRequest({}) @@ -2168,38 +2170,49 @@ const releaseToConnectLabel = "Release to Connect" // FIXME: Check if any node contains $exec in a param // If they do, show a popup asking if they want to execute it without an execution argument, or to use a previous one - if (executionArgument === undefined || executionArgument === null || executionArgument.length === 0 && workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) { - var foundmissing = false - for (let actionkey in workflow.actions) { - if (workflow.actions[actionkey].parameters === undefined || workflow.actions[actionkey].parameters === null || workflow.actions[actionkey].parameters.length === 0) { - continue - } + if (executionArgument === undefined || executionArgument === null || executionArgument.length === 0) - for (let paramkey in workflow.actions[actionkey].parameters) { - const param = workflow.actions[actionkey].parameters[paramkey] - if (param.value === undefined || param.value === null || param.value.length === 0) { + if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) { + var foundmissing = false + for (let actionkey in workflow.actions) { + if (workflow.actions[actionkey].parameters === undefined || workflow.actions[actionkey].parameters === null || workflow.actions[actionkey].parameters.length === 0) { continue } - if (param.value.indexOf("$exec") !== -1) { - foundmissing = true + for (let paramkey in workflow.actions[actionkey].parameters) { + const param = workflow.actions[actionkey].parameters[paramkey] + if (param.value === undefined || param.value === null || param.value.length === 0) { + continue + } + + if (param.value.indexOf("$exec") !== -1) { + foundmissing = true + break + } + } + + if (foundmissing) { break } } if (foundmissing) { - break + //toast("This workflow contains a node that requires an execution argument. Please provide one.") + if (workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0) { + setExecutionRequestStarted(false) + setExecutionArgumentModalOpen(true) + return + } + + if (workflowExecutions.length > 0) { + setExecutionRequestStarted(false) + setExecutionArgumentModalOpen(true) + + return + } } } - if (foundmissing) { - //toast("This workflow contains a node that requires an execution argument. Please provide one.") - setExecutionRequestStarted(false) - setExecutionArgumentModalOpen(true) - return - } - } - var curelements = cy.elements(); for (let i = 0; i < curelements.length; i++) { curelements[i].addClass("not-executing-highlight"); @@ -10738,7 +10751,7 @@ const releaseToConnectLabel = "Release to Connect" padding: 30, pointerEvents: "auto", color: "white", - minWidth: isMobile ? "90%" : 800, + minWidth: isMobile ? "90%" : 650, border: theme.palette.defaultBorder, }, }} @@ -10754,77 +10767,120 @@ const releaseToConnectLabel = "Release to Connect" style={{ zIndex: 5000, position: "absolute", top: 34, right: 34 }} onClick={(e) => { e.preventDefault(); - setExecutionArgumentModalOpen(false); + setExecutionArgumentModalOpen(false) }} > - Provide a runtime argument + Provide an execution argument - - At least one node in this workflow requires a runtime argument ($exec). Please select one below, or provide a custom one in the text field next to the run button. - - {/* -
    - - { - setExecutionText(e.target.value); - }} - /> - -
    - */} - - {availableArguments.length > 0 ? -
    - - Previously used arguments: - - {availableArguments.map((data) => { - return ( - { - setExecutionText(data) - executeWorkflow(data, workflow.start, lastSaved); - setExecutionArgumentModalOpen(false) + {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ? +
    + {workflow.input_questions.map((question, index) => { + + return ( +
    + {question.name} + -
    - - {data} - - - ) - })} -
    - : null} + fullWidth={true} + placeholder="" + id="emailfield" + margin="normal" + variant="outlined" + onBlur={(e) => { + var newtext = {} + if (executionText.length > 0) { + try { + newtext = JSON.parse(executionText) + // Check if list or object, then make it object only + if (Array.isArray(newtext)) { + newtext = {} + } + } catch (e) { + console.log("Error parsing JSON: ", e) + } + } - + newtext[question.value] = e.target.value + setExecutionText(JSON.stringify(newtext)) + }} + /> +
    + ) + })} + + +
    + : +
    + + At least one node in this workflow requires an execution argument ($exec). Please select one below, or provide a custom one in the text field next to the run button. + + + + {availableArguments.length > 0 ? +
    + + Previously used arguments: + + {availableArguments.map((data) => { + return ( + { + setExecutionText(data) + executeWorkflow(data, workflow.start, lastSaved); + + setExecutionArgumentModalOpen(false) + }} + > +
    + + {data} + + + ) + })} +
    + : null} + + +
    + } @@ -14811,6 +14867,7 @@ const releaseToConnectLabel = "Release to Connect"
    ) : null} + {workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("email") ? ( @@ -14842,6 +14899,7 @@ const releaseToConnectLabel = "Release to Connect" }} /> ) : null} + {workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[ @@ -14874,6 +14932,39 @@ const releaseToConnectLabel = "Release to Connect" ) : null}
    + +
    + Enabled Input-Questions + {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ? +
    + {workflow.input_questions.map((question, index) => { + const selectionClick = () => { + console.log("Clicked input question: ", question) + console.log("PARAMS: ", workflow.triggers[selectedTriggerIndex].parameters) + } + + return ( +
    { + selectionClick() + }}> + + + {question.name} + +
    + ) + })} +
    + : +
    { + setEditWorkflowModalOpen(true) + }}> + No Input-Questions found. Click to add them! +
    + } +
    const PipelineSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined && selectedTrigger.trigger_type !== "SCHEDULE" ? null : @@ -16441,7 +16532,7 @@ const releaseToConnectLabel = "Release to Connect" - ); + ) return (
    diff --git a/frontend/src/views/LoginPage.jsx b/frontend/src/views/LoginPage.jsx index d3c529b3..52b9cd1d 100755 --- a/frontend/src/views/LoginPage.jsx +++ b/frontend/src/views/LoginPage.jsx @@ -166,7 +166,7 @@ const LoginDialog = (props) => { } else { if (responseJson["reason"] === "MFA_REDIRECT") { setLoginInfo( - "MFA required. Please the 6-digit code from your authenticator" + "MFA required. Please enter the 6-digit code from your authenticator" ); setMFAField(true); return; diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index ffdf0ec4..0b642d56 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -976,15 +976,16 @@ const Workflows = (props) => { }, 1000); } else if (selectedWorkflowIndexes.length > 0) { // Do backwards so it doesn't change + toast("Starting deletion of workflows. This might take a while.") for (var i = selectedWorkflowIndexes.length - 1; i >= 0; i--) { const workflow = filteredWorkflows[selectedWorkflowIndexes[i]-1] if (workflow !== undefined && workflow !== null && workflow.id !== undefined && workflow.id !== null) { - deleteWorkflow(workflow.id); + deleteWorkflow(workflow.id, true) } } setTimeout(() => { - getAvailableWorkflows(); + getAvailableWorkflows() }, 1000); setSelectedWorkflowIndexes([]); @@ -1115,7 +1116,7 @@ const Workflows = (props) => { }) } - const getAvailableWorkflows = () => { + const getAvailableWorkflows = (amount) => { var storageWorkflows = [] try { const storagewf = localStorage.getItem("workflows") @@ -1132,7 +1133,12 @@ const Workflows = (props) => { //console.log("Failed to get workflows from localstorage: ", e) } - fetch(globalUrl + "/api/v1/workflows", { + var url = `${globalUrl}/api/v1/workflows` + if (amount !== undefined && amount !== null) { + url += `?top=${amount}` + } + + fetch(url, { method: "GET", headers: { "Content-Type": "application/json", @@ -1338,8 +1344,21 @@ const Workflows = (props) => { setView(tmpView); } + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + const foundTab = params["top"]; + if (foundTab !== null && foundTab !== undefined) { + // Check if it's a number + if (isNaN(foundTab)) { + getAvailableWorkflows() + } else { + getAvailableWorkflows(foundTab) + } + } else { + getAvailableWorkflows() + } + getApps() - getAvailableWorkflows(); getFramework() } }, []) @@ -1836,7 +1855,7 @@ const Workflows = (props) => { }) } - const deleteWorkflow = (id) => { + const deleteWorkflow = (id, bulk) => { fetch(globalUrl + "/api/v1/workflows/" + id, { method: "DELETE", headers: { @@ -1850,15 +1869,19 @@ const Workflows = (props) => { console.log("Status not 200 for setting workflows :O!"); toast("Failed deleting workflow. Do you have access?"); } else { - toast("Deleted workflow " + id); + if (bulk !== true) { + toast("Deleted workflow " + id); + } } return response.json(); }) .then(() => { - setTimeout(() => { - getAvailableWorkflows(); - }, 1000); + if (bulk !== true) { + setTimeout(() => { + getAvailableWorkflows(); + }, 1000); + } }) .catch((error) => { toast(error.toString()); @@ -2962,7 +2985,7 @@ const Workflows = (props) => { className={classes.datagrid} rows={rows} columns={columns} - pageSize={25} + pageSize={100} checkboxSelection autoHeight density="standard" From 77e7a4a371a063761004bef10b6039b61a85d746 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 21 Jun 2024 17:31:43 +0530 Subject: [PATCH 36/46] Node Drag edge connection issue --- frontend/src/views/AngularWorkflow.jsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 0a17d93d..cca097d8 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -3817,9 +3817,15 @@ const releaseToConnectLabel = "Release to Connect" if (connected.length > 0 && connected !== undefined) { for (let connectkey in connected) { const edge = connected[connectkey] - //console.log("EDGE:", edge) - - //const edge = edgeBase.json() + if (edge.data.decorator && edge.data.label === releaseToConnectLabel) { + // Transform to normal edge + const currentedge = cy.getElementById(edge.data.id) + if (currentedge !== undefined && currentedge !== null) { + currentedge.data("decorator", false) + currentedge.data("label", "") + } + continue + } const sourcenode = cy.getElementById(edge.data.source) const destinationnode = cy.getElementById(edge.data.target) From ad9743af10927e63866504c72bd307d4fae98a8c Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 21 Jun 2024 15:12:40 +0200 Subject: [PATCH 37/46] Fixed workflow editing things --- frontend/src/components/EditWorkflow.jsx | 75 ++++++++++------- frontend/src/components/Oauth2Auth.jsx | 18 ++-- frontend/src/views/Admin.jsx | 2 +- frontend/src/views/AngularWorkflow.jsx | 102 +++++++++++++++++++++-- frontend/src/views/Workflows.jsx | 13 +-- 5 files changed, 153 insertions(+), 57 deletions(-) diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index 4b4aa3d5..cc4a33b4 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -338,26 +338,6 @@ const EditWorkflow = (props) => { autoFocus fullWidth /> -
    - { - setDescription(event.target.value) - }} - InputProps={{ - style: { - color: "white", - }, - }} - maxRows={4} - color="primary" - defaultValue={innerWorkflow.description} - placeholder="Description" - multiline - label="Description" - margin="dense" - fullWidth - /> -
    {usecases !== null && usecases !== undefined && usecases.length > 0 ? @@ -425,27 +405,56 @@ const EditWorkflow = (props) => { fullWidth value={newWorkflowTags} onChange={(chip) => { - console.log("Chip: ", chip) - //newWorkflowTags.push(chip); setNewWorkflowTags(chip); }} + onBlur={(event) => { + if (event.target.value.length === 0) { + return + } + + if (newWorkflowTags.includes(event.target.value)) { + return + } + + newWorkflowTags.push(event.target.value) + setNewWorkflowTags(newWorkflowTags) + + setUpdate(Math.random()) + }} onAdd={(chip) => { - newWorkflowTags.push(chip); - setNewWorkflowTags(newWorkflowTags); + newWorkflowTags.push(chip) + setNewWorkflowTags(newWorkflowTags) }} onDelete={(chip, index) => { console.log("Deleting: ", chip, index) - newWorkflowTags.splice(index, 1); - setNewWorkflowTags(newWorkflowTags); - setUpdate(Math.random()); + newWorkflowTags.splice(index, 1) + setNewWorkflowTags(newWorkflowTags) + setUpdate(Math.random()) }} />
    {showMoreClicked === true ? - - - +
    + { + setDescription(event.target.value) + }} + InputProps={{ + style: { + color: "white", + }, + }} + multiLine + rows={3} + color="primary" + defaultValue={innerWorkflow.description} + placeholder="Description" + multiline + label="Description" + margin="dense" + fullWidth + />
    @@ -564,6 +573,8 @@ const EditWorkflow = (props) => { fullWidth /> + + MSSP Suborg Distribution (beta - contact support@shuffler.io for more info) @@ -669,6 +680,7 @@ const EditWorkflow = (props) => { } + Input fields @@ -781,6 +793,7 @@ const EditWorkflow = (props) => { + Git Backup Repository @@ -931,7 +944,7 @@ const EditWorkflow = (props) => { - +
    : null} diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index f0c20a64..e9e794b2 100755 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -471,32 +471,26 @@ const AuthenticationOauth2 = (props) => { state += `%26refresh_uri%3d${authentication_url}`; } - // No prompt forcing - //var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=login&scope=${resources}&state=${state}&access_type=offline`; + // FIXME: Should this be =consent? var defaultPrompt = "login" if (prompt !== undefined && prompt !== null && prompt.length > 0) { - defaultPrompt = prompt + defaultPrompt = prompt } var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=${defaultPrompt}&scope=${resources}&state=${state}&access_type=offline`; - if (admin_consent === true) { console.log("Running Oauth2 WITH admin consent") //url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`; url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`; } - // Force new consent + console.log("OAUTH2 URL: ", url) + + // Force new consent //const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`; - // Admin consent + // Admin consent //const url = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${client_id}&scope=AaaServer.profile.Read&redirect_uri=${redirectUri}&prompt=consent` - - // &resource=https%3A%2F%2Fgraph.microsoft.com& - - // FIXME: Awful, but works for prototyping - // How can we get a callback properly realtime? - // How can we properly try-catch without breaks on error? try { var newwin = window.open(url, "", "width=582,height=700"); //console.log(newwin) diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 8546e887..f9f85761 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -3745,7 +3745,7 @@ If you're interested, please let me know a time that works for you, or set up a aria-label="disabled tabs example" > Edit Details /> - Org Limits & Cloud Sync /> + Limits & Cloud Sync /> Priorities /> Billing & Stats /> Branding (Beta) /> diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 0a17d93d..5df415e8 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -5294,7 +5294,59 @@ const releaseToConnectLabel = "Release to Connect" } setTimeout(() => { - setSelectedTriggerIndex(trigger_index); + if (trigger_index !== -1) { + const trigger = workflow.triggers[trigger_index] + if (trigger !== undefined && trigger !== null) { + + // Autofixer + if (trigger.trigger_type === "USERINPUT") { + const relevantparams = [ + "alertinfo", + "options", + "type", + "email", + "sms", + "subflow", + ] + var foundparams = 0 + for (var paramkey in trigger.parameters) { + if (relevantparams.includes(trigger.parameters[paramkey].name)) { + foundparams++ + } + } + + if (foundparams < 6) { + trigger.parameters = [{ + name: "alertinfo", + value: "Do you want to continue the workflow? Start parameters: $exec", + },{ + name: "options", + value: "boolean", + }, + { + name: "type", + value: "subflow", + }, + { + name: "email", + value: "test@test.com", + }, + { + name: "sms", + value: "0000000", + }, + { + name: "subflow", + value: "", + }] + + workflow.triggers[trigger_index].parameters = trigger.parameters + } + } + } + } + + setSelectedTriggerIndex(trigger_index) setSelectedTrigger(data) setSelectedActionEnvironment(data.env) }, 25) @@ -12382,7 +12434,7 @@ const releaseToConnectLabel = "Release to Connect" setSubworkflow(e.target.value); // Sets the startnode - if (e.target.value.id !== workflow.id) { + if (e.target.value.id !== workflow.id && e.target.value.id.length > 0 ) { console.log("WORKFLOW: ", e.target.value); const startnode = e.target.value.actions.find((action) => action.id === e.target.value.start); @@ -14808,6 +14860,10 @@ const releaseToConnectLabel = "Release to Connect" }) } + if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length > 0 && selectedTriggerIndex >= 0 && selectedTriggerIndex < workflow.triggers.length) { + console.log(workflow.triggers[selectedTriggerIndex]) + } + const UserinputSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "USERINPUT" ? null :

    @@ -14994,7 +15050,12 @@ const releaseToConnectLabel = "Release to Connect" const newname = (option.name.charAt(0).toUpperCase() + option.name.substring(1)).replaceAll("_", " "); return newname; }} - options={workflows} + options={ + [{ + "id": "", + "name": "No Workflow Selected", + }].concat(workflows) + } fullWidth style={{ backgroundColor: theme.palette.inputColor, @@ -15149,14 +15210,40 @@ const releaseToConnectLabel = "Release to Connect"

    -
    +
    Enabled Input-Questions {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
    {workflow.input_questions.map((question, index) => { + var foundParamIndex = workflow.triggers[selectedTriggerIndex].parameters.findIndex((param) => param.name === "input_questions") + const selectionClick = () => { - console.log("Clicked input question: ", question) - console.log("PARAMS: ", workflow.triggers[selectedTriggerIndex].parameters) + if (foundParamIndex === -1) { + workflow.triggers[selectedTriggerIndex].parameters.push({ + "name": "input_questions", + "value": [], + }) + + foundParamIndex = workflow.triggers[selectedTriggerIndex].parameters.length - 1 + } else { + try { + workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = JSON.parse(workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value) + } catch (e) { + console.log("Couldn't parse input questions: ", e) + workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = [] + } + } + + if (workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.includes(question.name)) { + workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.filter((item) => item !== question.name) + } else { + workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.push(question.name) + } + + // Make it back to a string + workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = JSON.stringify(workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value) + setWorkflow(workflow) + setUpdate(Math.random()) } return ( @@ -15164,7 +15251,7 @@ const releaseToConnectLabel = "Release to Connect" selectionClick() }}> {question.name} @@ -15176,6 +15263,7 @@ const releaseToConnectLabel = "Release to Connect" :
    { setEditWorkflowModalOpen(true) + toast.info("Expand and scroll down to add input-questions") }}> No Input-Questions found. Click to add them!
    diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 0b642d56..816f17c5 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -1162,10 +1162,11 @@ const Workflows = (props) => { }) .then((responseJson) => { if (responseJson !== undefined) { + var newarray = [] - for (var key in responseJson) { - const wf = responseJson[key] - if (wf.public === true) { + for (var wfkey in responseJson) { + const wf = responseJson[wfkey] + if (wf.public === true || wf.hidden === true) { continue } @@ -1178,9 +1179,9 @@ const Workflows = (props) => { var parsedactionlist = []; for (var key in newarray) { const workflow = newarray[key] - if (workflow.status === "production") { - setProdFilter = true - } + //if (workflow.status === "production") { + // setProdFilter = true + //} for (var actionkey in newarray[key].actions) { const action = newarray[key].actions[actionkey]; From 3db9a21a5abc324dc6de4be87008e4a7a37195a8 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 21 Jun 2024 19:27:51 +0530 Subject: [PATCH 38/46] Fixed that pointer issue on TopCytoscapBar --- frontend/src/views/AngularWorkflow.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index cca097d8..35dd0525 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -15712,7 +15712,6 @@ const releaseToConnectLabel = "Release to Connect" right: 0, left: isMobile ? 20 : leftBarSize + 20, top: isMobile ? 30 : appBarSize + 20, - pointerEvents: "none", } @@ -15733,7 +15732,6 @@ const releaseToConnectLabel = "Release to Connect"

    {workflow.name}

    From ef46b5ab9952a43fd730c928a42fde633b9c9d63 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 21 Jun 2024 20:30:42 +0530 Subject: [PATCH 39/46] Fixed that release to connect branch issue --- frontend/src/views/AngularWorkflow.jsx | 97 +++++++++++++++----------- 1 file changed, 56 insertions(+), 41 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 35dd0525..ed846eec 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -5650,16 +5650,15 @@ const releaseToConnectLabel = "Release to Connect" // Checks for errors in edges when they're added const onEdgeAdded = (event) => { - setLastSaved(false); - const edge = event.target.data(); - - //console.log("edge added: ", edge) + const edge = event.target.data() + //console.log("EDGE ADDED!: ", edge) if (edge.source === undefined && edge.target === undefined) { + // console.log("Edge source and target is undefined") return } if (edge.readded === true) { - console.log("Readded edge - stopping") + // console.log("Readded edge - stopping") event.target.data("readded", false) return @@ -5668,6 +5667,7 @@ const releaseToConnectLabel = "Release to Connect" const sourcenode = cy.getElementById(edge.source) const destinationnode = cy.getElementById(edge.target) if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) { + // console.log("Source or destination node is undefined") } else { //console.log("Edge added: Is it a trigger? If so, check if it already has a branch and remove it: ", sourcenode.data()) if (sourcenode.data("type") === "TRIGGER") { @@ -5680,17 +5680,17 @@ const releaseToConnectLabel = "Release to Connect" console.log("Node: ", targetedge) if (targetedge !== -1) { - event.target.remove() //console.log("Found branch already!") toast.error("Triggers can have exactly one target node") + event.target.remove() return // name: "Shuffle Workflow", // name: "User Input", } else { - console.log("Node doesn't already have one") + // console.log("Node doesn't already have one") } }, 50) } @@ -5704,6 +5704,10 @@ const releaseToConnectLabel = "Release to Connect" } } + if (edge.decorator === true) { + // console.log("Doing nothing to branch because decorator") + return + } var targetnode = workflow.triggers.findIndex( (data) => data.id === edge.target @@ -5721,7 +5725,7 @@ const releaseToConnectLabel = "Release to Connect" if (eventTarget.data("isButton") === true) { const parentNode = cy.getElementById(eventTarget.data("attachedTo")) event.target.remove() - console.log("Setting it to parentnode: ", parentNode.data()) + // console.log("Setting it to parentnode: ", parentNode.data()) if (parentNode !== undefined && parentNode !== null) { //event.target.data("target", eventTarget.data("attachedTo")) @@ -5743,15 +5747,14 @@ const releaseToConnectLabel = "Release to Connect" } } - if ( - eventTarget.data("isDescriptor") === true || - eventTarget.data("type") === "COMMENT" - ) { - console.log("Removing because of descriptor or comment") - event.target.remove(); - return; + if (eventTarget.data("isDescriptor") === true || eventTarget.data("type") === "COMMENT") { + // console.log("Removing because of descriptor or comment") + event.target.remove() + return } + + setLastSaved(false) targetnode = -1; // Check if: @@ -5759,38 +5762,51 @@ const releaseToConnectLabel = "Release to Connect" // dest == dest && source == source // backend: check all children? to stop recursion 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; - } else if ( - workflow.branches[branchkey].destination_id === edge.target && - workflow.branches[branchkey].source_id === edge.source - ) { - //toast("That branch already exists"); - event.target.remove(); + const branches = cy.edges().jsons() + + const startNode = cy.nodes().jsons().find((node) => node.data.isStartNode === true) + var startnodeId = workflow.start + if (startNode !== undefined && startNode !== null) { + startnodeId = startNode.data.id + } + + //for (let branchkey in workflow.branches) { + for (let branchkey in branches) { + const branch = branches[branchkey].data + + //if (workflow.branches[branchkey].destination_id === edge.source && workflow.branches[branchkey].source_id === edge.target) { + if (branch.target === edge.source && branch.source === edge.target) { + toast("A branch in the opposite direction already exists") + event.target.remove() + found = true + break + + //} else if (workflow.branches[branchkey].destination_id === edge.target && workflow.branches[branchkey].source_id === edge.source) { + } else if (branch.target === edge.target && branch.source === edge.source) { + + if (branch.conditions === undefined) { + // Edgehandles + } else { + // console.log("Removing because the same branch already exists") + event.target.remove() + + found = true + break + } + } else if (edge.target === startnodeId) { + targetnode = workflow.triggers.findIndex((data) => data.id === edge.source) - found = true; - break; - } else if (edge.target === workflow.start) { - targetnode = workflow.triggers.findIndex( - (data) => data.id === edge.source - ); if (targetnode === -1) { if (targetnode.type !== "TRIGGER") { - toast("Can't make arrow to starting node"); - event.target.remove(); - break; + toast("Can't make arrow to starting node") + event.target.remove() + break } found = true; } - } else if (edge.source === workflow.branches[branchkey].source_id) { + //} else if (edge.source === workflow.branches[branchkey].source_id) { + } else if (edge.source === branch.source) { // FIXME: Verify multi-target for triggers // 1. Check if destination exists // 2. Check if source is a trigger @@ -5834,7 +5850,6 @@ const releaseToConnectLabel = "Release to Connect" newdst !== null ) { const dstdata = RunAutocompleter(newdst.data()); - //console.log("DST Autocompleter: ", dstdata); } var newbranch = { From 4276717ff6e86f8c1e8f6b307f1a234de1ff8133 Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Fri, 21 Jun 2024 22:35:42 +0530 Subject: [PATCH 40/46] Other small fixes --- frontend/src/views/AngularWorkflow.jsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index ed846eec..81a76c41 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -12501,9 +12501,9 @@ const releaseToConnectLabel = "Release to Connect" let mappingWithName = {} let listWithValues = workflow.triggers[selectedTriggerIndex].parameters[5]?.value.split(";").filter(e => e).map(e => e.split("=")) console.log("LIST WITH VALUES: ", listWithValues) - if (listWithValues === undefined || listWithValues === null || listWithValues.length === 0) { - return "no-overrides"; - } + // if (listWithValues === undefined || listWithValues === null || listWithValues.length === 0) { + // return "no-overrides"; + // } for (let i = 0; i < listWithValues.length; i++) { mappingWithName[listWithValues[i][0]] = listWithValues[i][1] @@ -18389,7 +18389,7 @@ const releaseToConnectLabel = "Release to Connect"
    {foundnotifications > 0 ? - + { @@ -21401,7 +21401,11 @@ const releaseToConnectLabel = "Release to Connect" //cy.remove('*') setElements([]) } - + + // // Remove all edges + // cy.edges().remove() + // cy.nodes().remove() + // Remove all cy nodes setTimeout(() => { setupGraph(newrevision) From 52227397a9d0118b4a574b77dfbe1bfe9b4780c7 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 22 Jun 2024 23:44:01 +0200 Subject: [PATCH 41/46] Fixed user input to work better --- backend/go-app/walkoff.go | 2 +- frontend/src/components/EditWorkflow.jsx | 4 +- frontend/src/views/AngularWorkflow.jsx | 39 ++-- frontend/src/views/RunWorkflow.jsx | 245 ++++++++++++++++++----- 4 files changed, 211 insertions(+), 79 deletions(-) diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 8ea1550a..7ff135e3 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1076,7 +1076,7 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request // return workflowExecution, fmt.Sprintf("%s", err), nil } else { log.Printf("[ERROR] Failed in prepareExecution: '%s'", err) - return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed starting workflow: %s", err), err + return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed running: %s", err), err } } diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index cc4a33b4..e2443664 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -685,8 +685,8 @@ const EditWorkflow = (props) => { Input fields - - Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the workflow run page. + + Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the workflow run page. If chosen in the User Input node, these will be required fields. diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index fb28083d..baab3dab 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1073,22 +1073,20 @@ const releaseToConnectLabel = "Release to Connect" // User Input & Subflow nodes if (param.name === "workflow" || param.name === "subflow") { - const paramIndex = param.name === "workflow" ? 0 : 5 - console.log("Current vs new: ", workflow.triggers[trigger_index].parameters[paramIndex].value, subworkflow.id) - - if (workflow.triggers[trigger_index].parameters[paramIndex].value !== subworkflow.id) { - if (param.value === workflow.id) { - setSubworkflow(workflow); - baseSubflow = workflow - } else { - const sub = responseJson.find((data) => data.id === param.value); - if (sub !== undefined && subworkflow.id !== sub.id) { - baseSubflow = sub - setSubworkflow(sub); - } - } - } - } + const paramIndex = param.name === "workflow" ? 0 : 5 + if (workflow.triggers[trigger_index].parameters[paramIndex].value !== subworkflow.id) { + if (param.value === workflow.id) { + setSubworkflow(workflow); + baseSubflow = workflow + } else { + const sub = responseJson.find((data) => data.id === param.value); + if (sub !== undefined && subworkflow.id !== sub.id) { + baseSubflow = sub + setSubworkflow(sub); + } + } + } + } if (param.name === "startnode" && param.value !== undefined && param.value !== null) { @@ -3255,7 +3253,6 @@ const releaseToConnectLabel = "Release to Connect" const getChildWorkflows = (parentWorkflowId) => { if (originalWorkflow.suborg_distribution === undefined || originalWorkflow.suborg_distribution === null || originalWorkflow.suborg_distribution.length === 0) { - console.log("No suborg distribution") return } @@ -14866,10 +14863,6 @@ const releaseToConnectLabel = "Release to Connect" }) } - if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length > 0 && selectedTriggerIndex >= 0 && selectedTriggerIndex < workflow.triggers.length) { - console.log(workflow.triggers[selectedTriggerIndex]) - } - const UserinputSidebar = Object.getOwnPropertyNames(selectedTrigger).length === 0 || workflow.triggers[selectedTriggerIndex] === undefined || selectedTrigger.trigger_type !== "USERINPUT" ? null :

    @@ -15217,7 +15210,7 @@ const releaseToConnectLabel = "Release to Connect"

    - Enabled Input-Questions + Required Input-Questions {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
    {workflow.input_questions.map((question, index) => { @@ -17156,10 +17149,12 @@ const releaseToConnectLabel = "Release to Connect" //defaultReturn = return null; } else { + /* console.log( "Unable to handle invalid trigger type " + selectedTrigger.trigger_type ); + */ return null; } } else if (Object.getOwnPropertyNames(selectedEdge).length > 0) { diff --git a/frontend/src/views/RunWorkflow.jsx b/frontend/src/views/RunWorkflow.jsx index ee1802d7..4d8dc1bf 100644 --- a/frontend/src/views/RunWorkflow.jsx +++ b/frontend/src/views/RunWorkflow.jsx @@ -2,6 +2,7 @@ import React, {useState, useEffect} from 'react'; import ReactDOM from "react-dom" +import { ToastContainer, toast } from "react-toastify" import { useInterval } from "react-powerhooks"; import { makeStyles } from '@mui/material/styles'; import { useNavigate, Link, useParams } from "react-router-dom"; @@ -43,7 +44,8 @@ const bodyDivStyle = { const RunWorkflow = (defaultprops) => { const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, serverside } = defaultprops; - let navigate = useNavigate(); + let navigate = useNavigate(); + const [_, setUpdate] = useState(""); // Used to force rendring, don't remove const [message, setMessage] = useState(""); const [workflow, setWorkflow] = React.useState({}); const [executionRequest, setExecutionRequest] = React.useState({}); @@ -55,6 +57,7 @@ const RunWorkflow = (defaultprops) => { const [selectedOrganization, setSelectedOrganization] = React.useState(undefined); const [apps, setApps] = React.useState([]); const [buttonClicked, setButtonClicked] = React.useState(""); + const [foundSourcenode, setFoundSourcenode] = React.useState(undefined); const boxStyle = { color: "white", @@ -340,6 +343,8 @@ const RunWorkflow = (defaultprops) => { event.preventDefault() } + console.log("ONSUBMIT: ", event, execution_id, authorization, answer) + stop() setMessage("") setExecutionLoading(true) @@ -389,12 +394,27 @@ const RunWorkflow = (defaultprops) => { url += `?reference_execution=${execution_id}&authorization=${authorization}&answer=${answer}` data = {} fetchBody.method = "GET" + + if (executionArgument !== undefined && executionArgument !== null) { + try { + if (typeof executionArgument === "string") { + url += "¬e=" + executionArgument + } else { + url += "¬e=" + JSON.stringify(executionArgument) + } + } catch (e) { + url += "¬e=" + executionArgument + } + } + } else { fetchBody.method = "POST" fetchBody.body = JSON.stringify(data) } - console.log("Pre request: ", url, fetchBody) + // IF there is an execution argument, we should use it + console.log("FULL URL: ", url) + fetch(url, fetchBody) .then((response) => { if (response.status !== 200 && response.status !== 201) { @@ -408,16 +428,26 @@ const RunWorkflow = (defaultprops) => { }) start(); - return + return response.json() } } - return response.json(); + return response.json() }) .then(responseJson => { setExecutionLoading(false) - if (responseJson["success"] === false) { + if (responseJson.success === false) { console.log("Failed sending execution request") + if (responseJson.reason !== undefined && responseJson.reason !== null) { + toast.warn(responseJson.reason) + } + + stop() + setMessage("") + setExecutionData({}) + setExecutionInfo("") + setExecutionRunning(false) + setExecutionRequest({}) } else { console.log("Started execution") @@ -426,17 +456,24 @@ const RunWorkflow = (defaultprops) => { } else { setExecutionRunning(true); setExecutionRequest(responseJson) - start(); + start() } } }) .catch(error => { //setExecutionInfo("Error in workflow startup: " + error) + toast.warn("Error in workflow startup: " + error) + + stop() + setMessage("") + setExecutionData({}) + setExecutionInfo("") + setExecutionLoading(false) }) } - const getWorkflow = (workflow_id) => { + const getWorkflow = (workflow_id, selectedNode) => { fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { method: "GET", headers: { @@ -480,6 +517,48 @@ const RunWorkflow = (defaultprops) => { setExecutionArgument(newexec) } + if (selectedNode !== undefined && selectedNode !== null && selectedNode.length > 0) { + var found = false + for (var actionkey in responseJson.actions) { + if (responseJson.actions[actionkey].id === selectedNode) { + found = true + setFoundSourcenode(responseJson.actions[actionkey]) + break + } + } + + if (!found) { + for (var triggerkey in responseJson.triggers) { + if (responseJson.triggers[triggerkey].id !== selectedNode) { + continue + } + + setFoundSourcenode(responseJson.triggers[triggerkey]) + + if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0 && responseJson.triggers[triggerkey].trigger_type === "USERINPUT") { + + // Look for input questions param + for (var paramkey in responseJson.triggers[triggerkey].parameters) { + if (responseJson.triggers[triggerkey].parameters[paramkey].name === "input_questions") { + + var relevantquestions = [] + for (var questionkey in responseJson.input_questions) { + if (responseJson.triggers[triggerkey].parameters[paramkey].value.includes(responseJson.input_questions[questionkey].name)) { + relevantquestions.push(responseJson.input_questions[questionkey]) + } + } + + responseJson.input_questions = relevantquestions + } + } + } + + + break + } + } + } + handleGetOrg(responseJson.org_id) setWorkflow(responseJson); }) @@ -489,7 +568,7 @@ const RunWorkflow = (defaultprops) => { }; const { start, stop } = useInterval({ - duration: 3000, + duration: 1500, startImmediate: true, callback: () => { fetchUpdates(executionRequest.execution_id, executionRequest.authorization) @@ -526,15 +605,11 @@ const RunWorkflow = (defaultprops) => { for (var key in responseJson.results) { if (responseJson.results[key].status === "WAITING") { - console.log("Found: ", responseJson.results[key]) - const validate = validateJson(responseJson.results[key].result) - console.log("Validate: ", validate) if (validate.valid && typeof validate.result === "string") { validate.result = JSON.parse(validate.result) } - console.log("Newresult: ", validate.result) if (validate.result["information"] !== undefined && validate.result["information"] !== null) { setWorkflowQuestion(validate.result["information"]) } @@ -623,15 +698,15 @@ const RunWorkflow = (defaultprops) => { return } - fetch(globalUrl + "/api/v1/streams/results", { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(innerRequest), - credentials: "include", - }) + fetch(globalUrl + "/api/v1/streams/results", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(innerRequest), + credentials: "include", + }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for stream results :O!"); @@ -651,11 +726,14 @@ const RunWorkflow = (defaultprops) => { }); }; - const answer = new URLSearchParams(window.location.search).get("answer") - const execution_id = new URLSearchParams(window.location.search).get("reference_execution") - const authorization = new URLSearchParams(window.location.search).get("authorization") + const searchParams = new URLSearchParams(window.location.search) + const answer = searchParams.get("answer") + const execution_id = searchParams.get("reference_execution") + const authorization = searchParams.get("authorization") + const sourceNode = searchParams.get("source_node") + useEffect(() => { - getWorkflow(props.match.params.key) + getWorkflow(props.match.params.key, sourceNode) if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null) { console.log("Get execution: ", execution_id) fetchUpdates(execution_id, authorization, true) @@ -667,13 +745,60 @@ const RunWorkflow = (defaultprops) => { }, []) + useEffect(() => { + if (executionData === undefined || executionData === null || executionData === {}) { + return + } + + if (foundSourcenode === undefined || foundSourcenode === null || foundSourcenode === {}) { + return + } + + if (foundSourcenode.trigger_type !== "USERINPUT") { + return + } + + if (executionData.results === undefined || executionData.results === null || executionData.results.length === 0) { + return + } + + for (var resultkey in executionData.results) { + const result = executionData.results[resultkey] + if (result.action.id !== foundSourcenode.id) { + continue + } + + var parsedresult = result.result + try { + parsedresult = JSON.parse(parsedresult) + } catch (e) { + console.log("Error parsing result: ", e) + } + + if (result.status !== "WAITING") { + if (parsedresult.click_info !== undefined && parsedresult.click_info !== null) { + if (parsedresult.click_info.user !== undefined && parsedresult.click_info.user !== null && parsedresult.click_info.user.length > 0) { + setMessage("Already answered by " + parsedresult.click_info.user) + } + } else { + setMessage("Answered.") + } + + } + + break + } + + }, [executionData, foundSourcenode]) const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)" const buttonStyle = {borderRadius: 25, height: 50, fontSize: 18, backgroundImage: handleValidateForm(executionArgument) || executionLoading ? buttonBackground : "grey", color: "white"} - //console.log("execdata: ", executionData) const disabledButtons = message.length > 0 || executionData.status === "FINISHED" || executionData.status === "ABORTED" + //{disabledButtons ? null : + + const organization = selectedOrganization !== undefined && selectedOrganization !== null ? selectedOrganization.name : "Unknown" const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.org !== undefined && selectedOrganization.org !== null? selectedOrganization.org : "support@shuffler.io" //const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.contact !== undefined && selectedOrganization.contact !== null? selectedOrganization.contact : "support@shuffler.io" @@ -724,49 +849,54 @@ const RunWorkflow = (defaultprops) => { {organization} - - {contact} - - {message} + + {disabledButtons && message.length > 0 ? null : + + {message} + + } {answer !== undefined && answer !== null ? null : - {workflow.name} + {workflow.name} } {workflowQuestion.length > 0 ? - - {workflowQuestion} - +
    + + {workflowQuestion} + +
    : null} {workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
    {workflow.input_questions.map((question, index) => { - return (
    {question.name} { + onBlur={(e) => { //setExecutionArgument(e.target.value) executionArgument[question.value] = e.target.value + setUpdate(Math.random()) }} />
    @@ -813,20 +943,27 @@ const RunWorkflow = (defaultprops) => { : null} - : - answer !== undefined && answer !== null ? + ((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) ? - - {disabledButtons ? "Already answered. Nothing to do." : ""} - + + {disabledButtons && message.length > 0 ? + + {message}. You may close this window. + + : + + {disabledButtons ? "Answered. You may close this window." : ""} + + } + {disabledButtons ? null : What do you want to do? }
    -
    } - {buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ? + {/*buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ? finalize workflow animation { console.log("Img loaded.") @@ -868,7 +1005,7 @@ const RunWorkflow = (defaultprops) => { }} /> - : null} + : null*/}
    {executionInfo} From fcafc620ae9482450ce597e5f48384323aaf905c Mon Sep 17 00:00:00 2001 From: monilprajapati Date: Mon, 24 Jun 2024 14:23:44 +0530 Subject: [PATCH 42/46] Changed the UI for the Workflow revisions --- frontend/src/views/AngularWorkflow.jsx | 106 ++++++++++++++++--------- 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 81a76c41..2517c26c 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -2094,12 +2094,14 @@ const releaseToConnectLabel = "Release to Connect" } setWorkflow(workflow); + setSelectedRevision(workflow); } setSavingState(1); setTimeout(() => { setSavingState(0); }, 1500); + getRevisionHistory(useworkflow.id) } }) .catch((error) => { @@ -17027,6 +17029,10 @@ const releaseToConnectLabel = "Release to Connect" style={{ height: 50, marginLeft: 10 }} variant={"outlined"} onClick={() => { + if(!lastSaved){ + toast("Save the workflow first") + return + } setShowWorkflowRevisions(true) setSelectedRevision(workflow) //setOriginalWorkflow(workflow) @@ -21335,14 +21341,12 @@ const releaseToConnectLabel = "Release to Connect" }*/ const RevisionBox = (props) => { - const { revision, } = props - - if (revision === undefined || revision === null || revision === {}) { + const { revision, } = props + if (revision === undefined || revision === null) { return null } var newrevision = JSON.parse(JSON.stringify(revision)) - // Make unix timestamp into ISO timestamp in the format July 27th, 3:05 AM // Format: July 27th, 3:05 AM //console.log("Edited time: ", revision.edited) @@ -21402,9 +21406,9 @@ const releaseToConnectLabel = "Release to Connect" setElements([]) } - // // Remove all edges - // cy.edges().remove() - // cy.nodes().remove() + // Remove all edges + cy.edges().remove() + cy.nodes().remove() // Remove all cy nodes setTimeout(() => { @@ -21456,6 +21460,7 @@ const releaseToConnectLabel = "Release to Connect" {translatedDate} + {/* {newrevision.edited.toString().slice(6,10)} | {newrevision.revision_id.slice(0,5)} */} @@ -21538,39 +21543,65 @@ const releaseToConnectLabel = "Release to Connect" ) } - - const drawerData = originalWorkflow !== undefined && originalWorkflow !== null && originalWorkflow !== {} ? -
    - + //! Logs + console.log("Selected Revision", selectedRevision) + console.log("Workflow state", workflow) + console.log("All revision", allRevisions) + const drawerData = originalWorkflow !== undefined && originalWorkflow !== null ? +
    + Version History (Beta) +
    +
    +
    + + Current Version + + +
    - + +
    - {allRevisions.length > 0 ? - allRevisions.map((revision, index) => { - if (revision.edited === originalWorkflow.edited) { - return null - } - return ( - - ) - }) - : -
    - - No other revisions found. Save your workflow with changes to create a revision. - -
    - } -
    + {allRevisions.length > 0 ? +
    + { + allRevisions.map((revision, index) => { + if(revision.edited === selectedRevision.edited){ + return null + } + + return ( + + ) + }) + } +
    + + : +
    + + No other revisions found. Save your workflow with changes to create a revision. + +
    + } +
    +
    : null const workflowRevisions = !showWorkflowRevisions ? null : @@ -21581,7 +21612,7 @@ const releaseToConnectLabel = "Release to Connect" onClose={() => { //setShowWorkflowRevisions(false) }} - style={{ resize: "both", overflow: "auto", zIndex: 10005 }} + style={{ resize: "both", overflow: "hidden", zIndex: 10005 }} hideBackdrop={true} variant="persistent" BackdropProps={{ @@ -21592,7 +21623,7 @@ const releaseToConnectLabel = "Release to Connect" PaperProps={{ style: { resize: "both", - overflow: "auto", + overflow: "hidden", minWidth: isMobile ? "100%" : 360, maxWidth: isMobile ? "100%" : 360, backgroundColor: theme.palette.platformColor, @@ -21600,7 +21631,6 @@ const releaseToConnectLabel = "Release to Connect" fontSize: 18, zIndex: 15001, borderRight: theme.palette.defaultBorder, - paddingTop: 15, }, }} > From e1f7a1e9d35811da8de63b082a516eaf26a7a1ec Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 24 Jun 2024 15:28:21 +0200 Subject: [PATCH 43/46] Updated some workflow issues --- backend/go-app/go.mod | 2 +- frontend/src/components/Billing.jsx | 4 +- frontend/src/components/NewHeader.jsx | 3 +- frontend/src/components/ParsedAction.jsx | 30 ++++- frontend/src/views/Admin.jsx | 10 +- frontend/src/views/AngularWorkflow.jsx | 155 ++++++++++++----------- 6 files changed, 119 insertions(+), 85 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index ebe85b01..84fcf764 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -20,7 +20,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.6.47 + github.com/shuffle/shuffle-shared v0.6.50 golang.org/x/crypto v0.22.0 google.golang.org/api v0.176.1 google.golang.org/grpc v1.63.2 diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index e567a5c7..cb18d20d 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -1775,7 +1775,7 @@ const Billing = (props) => {