diff --git a/frontend/src/defaultCytoscapeStyle.jsx b/frontend/src/defaultCytoscapeStyle.jsx
new file mode 100644
index 00000000..f9e09f22
--- /dev/null
+++ b/frontend/src/defaultCytoscapeStyle.jsx
@@ -0,0 +1,433 @@
+const data = [
+ {
+ selector: "node",
+ css: {
+ label: "data(label)",
+ "text-valign": "center",
+ "font-family":
+ "Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif",
+ "font-weight": "lighter",
+ "margin-right": "10px",
+ "font-size": "18px",
+ width: "80px",
+ height: "80px",
+ color: "white",
+ padding: "10px",
+ margin: "5px",
+ "border-width": "1px",
+ "text-margin-x": "10px",
+ "z-index": 5001,
+ },
+ },
+ {
+ selector: "edge",
+ css: {
+ "target-arrow-shape": "triangle",
+ "target-arrow-color": "grey",
+ "curve-style": "unbundled-bezier",
+ label: "data(label)",
+ "text-margin-y": "-15px",
+ width: "5px",
+ color: "white",
+ "line-fill": "linear-gradient",
+ "line-gradient-stop-positions": ["0.0", "100"],
+ "line-gradient-stop-colors": ["grey", "grey"],
+ "z-index": 5001,
+ },
+ },
+ {
+ selector: `node[type="ACTION"]`,
+ css: {
+ shape: "roundrectangle",
+ "background-color": "#213243",
+ "border-color": "#81c784",
+ "background-width": "100%",
+ "background-height": "100%",
+ "border-radius": "5px",
+ "z-index": 5001,
+ },
+ },
+ {
+ selector: `node[type="COMMENT"]`,
+ css: {
+ shape: "roundrectangle",
+ color: "data(color)",
+ width: "data(width)",
+ height: "data(height)",
+ padding: "0px",
+ margin: "0px",
+ "background-color": "data(backgroundcolor)",
+ "background-image": "data(backgroundimage)",
+ "border-color": "#ffffff",
+ "text-margin-x": "0px",
+ "z-index": 4999,
+ "border-radius": "5px",
+ "background-opacity": "0.5",
+ "text-wrap": "wrap",
+ },
+ },
+ {
+ selector: `node[app_name="Shuffle Tools"]`,
+ css: {
+ width: "30px",
+ height: "30px",
+ "z-index": 5000,
+ "font-size": "0px",
+ "background-width": "75%",
+ "background-height": "75%",
+ "background-color": "data(iconBackground)",
+ "background-fill": "data(fillstyle)",
+ "background-gradient-direction": "to-right",
+ "background-gradient-stop-colors": "data(fillGradient)",
+ },
+ },
+ {
+ selector: `node[app_name="Testing"]`,
+ css: {
+ width: "30px",
+ height: "30px",
+ "z-index": 5000,
+ "font-size": "0px",
+ },
+ },
+ {
+ selector: `node[?small_image]`,
+ css: {
+ "background-image": "data(small_image)",
+ "text-halign": "right",
+ },
+ },
+ {
+ selector: `node[?large_image]`,
+ css: {
+ "background-image": "data(large_image)",
+ "text-halign": "right",
+ },
+ },
+ {
+ selector: `node[type="CONDITION"]`,
+ css: {
+ shape: "diamond",
+ "border-color": "##FFEB3B",
+ padding: "30px",
+ },
+ },
+ {
+ selector: `node[type="eventAction"]`,
+ css: {
+ "background-color": "#edbd21",
+ },
+ },
+ {
+ selector: `node[type="TRIGGER"]`,
+ css: {
+ shape: "octagon",
+ "border-radius": "5px",
+ "border-color": "orange",
+ "background-color": "#213243",
+ "background-width": "100%",
+ "background-height": "100%",
+ },
+ },
+ {
+ selector: `node[status="running"]`,
+ css: {
+ "border-color": "#81c784",
+ },
+ },
+ {
+ selector: `node[status="stopped"]`,
+ css: {
+ "border-color": "orange",
+ },
+ },
+ {
+ selector: 'node[type="mq"]',
+ css: {
+ "background-color": "#edbd21",
+ },
+ },
+ {
+ selector: "node[?isButton]",
+ css: {
+ shape: "ellipse",
+ width: "15px",
+ height: "15px",
+ "z-index": "5002",
+ "font-size": "0px",
+ border: "1px solid rgba(255,255,255,0.9)",
+ "background-image": "data(icon)",
+ "background-color": "data(iconBackground)",
+ },
+ },
+ {
+ selector: "node[?isSuggestion]",
+ css: {
+ shape: "ellipse",
+ width: "50px",
+ height: "50px",
+ "z-index": "5002",
+ "font-size": "0px",
+ border: "1px solid rgba(255,255,255,0.9)",
+ "background-image": "data(large_image)",
+ "background-color": "data(iconBackground)",
+ label: "data(label)",
+ },
+ },
+ {
+ selector: "node[?canConnect]",
+ css: {
+ "border-color": "#f86a3e",
+ "border-width": "10px",
+ "z-index": "5002",
+ "background-color": "#f86a3e",
+ },
+ },
+ {
+ selector: "node[?isDescriptor]",
+ css: {
+ shape: "ellipse",
+ "border-color": "#80deea",
+ width: "5px",
+ height: "5px",
+ "z-index": "5002",
+ "font-size": "10px",
+ "text-valign": "center",
+ "text-halign": "center",
+ border: "1px solid black",
+ "margin-right": "0px",
+ "text-margin-x": "0px",
+ "background-color": "data(imageColor)",
+ "background-image": "data(image)",
+ label: "data(label)",
+ },
+ },
+ {
+ selector: "node[?isStartNode]",
+ css: {
+ shape: "ellipse",
+ "border-color": "#80deea",
+ width: "80px",
+ height: "80px",
+ "font-size": "18px",
+ "background-width": "100%",
+ "background-height": "100%",
+ },
+ },
+ {
+ selector: "node[!is_valid]",
+ css: {
+ "border-color": "red",
+ "border-width": "10px",
+ },
+ },
+ {
+ selector: ":selected",
+ css: {
+ "background-color": "#77b0d0",
+ "border-color": "#77b0d0",
+ "border-width": "20px",
+ },
+ },
+ {
+ selector: ".skipped-highlight",
+ css: {
+ "background-color": "grey",
+ "border-color": "grey",
+ "border-width": "8px",
+ "transition-property": "background-color",
+ "transition-duration": "0.5s",
+ },
+ },
+ {
+ selector: ".success-highlight",
+ css: {
+ "background-color": "#41dcab",
+ "border-color": "#41dcab",
+ "border-width": "5px",
+ "transition-property": "background-color",
+ "transition-duration": "0.5s",
+ },
+ },
+ {
+ selector: ".hover-highlight",
+ css: {
+ "background-color": "#5f9265",
+ "border-color": "#5f9265",
+ "border-width": "5px",
+ "transition-property": "background-color",
+ "transition-duration": "0.5s",
+ },
+ },
+ {
+ selector: ".failure-highlight",
+ css: {
+ "background-color": "#8e3530",
+ "border-color": "#8e3530",
+ "border-width": "5px",
+ "transition-property": "background-color",
+ "transition-duration": "0.5s",
+ },
+ },
+ {
+ selector: ".not-executing-highlight",
+ css: {
+ "background-color": "grey",
+ "border-color": "grey",
+ "border-width": "5px",
+ "transition-property": "#ffef47",
+ "transition-duration": "0.25s",
+ },
+ },
+ {
+ selector: ".executing-highlight",
+ css: {
+ "background-color": "#ffef47",
+ "border-color": "#ffef47",
+ "border-width": "8px",
+ "transition-property": "border-width",
+ "transition-duration": "0.25s",
+ },
+ },
+ {
+ selector: ".awaiting-data-highlight",
+ css: {
+ "background-color": "#f4ad42",
+ "border-color": "#f4ad42",
+ "border-width": "5px",
+ "transition-property": "border-color",
+ "transition-duration": "0.5s",
+ },
+ },
+ {
+ selector: ".shuffle-hover-highlight",
+ css: {
+ "background-color": "#f85a3e",
+ "border-color": "#f85a3e",
+ "border-width": "12px",
+ "transition-property": "border-width",
+ "transition-duration": "0.25s",
+ label: "data(label)",
+ "font-size": "18px",
+ color: "white",
+ },
+ },
+ {
+ selector: "$node > node",
+ css: {
+ "padding-top": "10px",
+ "padding-left": "10px",
+ "padding-bottom": "10px",
+ "padding-right": "10px",
+ },
+ },
+ {
+ selector: "edge.executing-highlight",
+ css: {
+ width: "5px",
+ "target-arrow-color": "#ffef47",
+ "line-color": "#ffef47",
+ "transition-property": "line-color, width",
+ "transition-duration": "0.25s",
+ },
+ },
+ {
+ selector: `edge[?decorator]`,
+ css: {
+ width: "1px",
+ "line-style": "dashed",
+ "line-fill": "linear-gradient",
+ "target-arrow-color": "#f34079",
+ "line-gradient-stop-positions": ["0.0", "100"],
+ "line-gradient-stop-colors": ["#f86a3e", "#f34079"],
+ },
+ },
+ {
+ selector: "edge.success-highlight",
+ css: {
+ width: "5px",
+ "target-arrow-color": "#41dcab",
+ "line-color": "#41dcab",
+ "transition-property": "line-color, width",
+ "transition-duration": "0.5s",
+ "line-fill": "linear-gradient",
+ "line-gradient-stop-positions": ["0.0", "100"],
+ "line-gradient-stop-colors": ["#41dcab", "#41dcab"],
+ },
+ },
+ {
+ selector: ".eh-handle",
+ style: {
+ "background-color": "#337ab7",
+ width: "1px",
+ height: "1px",
+ shape: "circle",
+ "border-width": "1px",
+ "border-color": "black",
+ },
+ },
+ {
+ selector: ".eh-source",
+ style: {
+ "border-width": "3",
+ "border-color": "#337ab7",
+ },
+ },
+ {
+ selector: ".eh-target",
+ style: {
+ "border-width": "3",
+ "border-color": "#337ab7",
+ },
+ },
+ {
+ selector: ".eh-preview, .eh-ghost-edge",
+ style: {
+ "background-color": "#337ab7",
+ "line-color": "#337ab7",
+ "target-arrow-color": "#337ab7",
+ "source-arrow-color": "#337ab7",
+ },
+ },
+ {
+ selector: "edge:selected",
+ css: {
+ "target-arrow-color": "#f85a3e",
+ },
+ },
+ {
+ selector: `edge[?source_workflow]`,
+ css: {
+ "background-opacity": "1",
+ "font-size": "0px",
+ },
+ },
+ {
+ selector: `node[?source_workflow]`,
+ css: {
+ "background-opacity": "0",
+ "font-size": "0px",
+ },
+ },
+ {
+ selector: "node:selected",
+ css: {
+ "border-color": "#f86a3e",
+ "border-width": "7px",
+ },
+ },
+];
+
+//{
+// selector: 'edge[?hasErrors]',
+// css: {
+// 'target-arrow-color': '#991818',
+// 'line-color': '#991818',
+// 'line-style': 'dashed',
+// "line-fill": "linear-gradient",
+// "line-gradient-stop-positions": ["0.0", "100"],
+// "line-gradient-stop-colors": ["#991818", "#991818"],
+// },
+//},
+
+export default data;
diff --git a/frontend/src/theme.jsx b/frontend/src/theme.jsx
new file mode 100644
index 00000000..de75558c
--- /dev/null
+++ b/frontend/src/theme.jsx
@@ -0,0 +1,104 @@
+import React from "react";
+import { createMuiTheme } from "@material-ui/core/styles";
+
+const theme = createMuiTheme({
+ palette: {
+ primary: {
+ main: "#F86743",
+ contrastText: "#ffffff",
+ },
+ secondary: {
+ main: "#e8eaf6",
+ },
+ text: {
+ secondary: "rgba(255,255,255,0.7)",
+ },
+ type: "dark",
+ surfaceColor: "#27292d",
+ inputColor: "#383B40",
+ platformColor: "#1c1c1d",
+ backgroundColor: "#1a1a1a",
+ borderRadius: 5,
+ defaultBorder: "1px solid rgba(255,255,255,0.3)",
+ jsonTheme: "brewer",
+ reactJsonStyle: {
+ borderRadius: 5,
+ border: "1px solid rgba(255,255,255,0.7)",
+ padding: 5,
+ },
+ textFieldStyle: {
+ backgroundColor: "#383B40",
+ borderRadius: 5,
+ },
+ innerTextfieldStyle: {
+ color: "white",
+ minHeight: 50,
+ marginLeft: "5px",
+ maxWidth: "95%",
+ fontSize: "1em",
+ borderRadius: 5,
+ },
+ tooltip: {
+ backgroundColor: "white",
+ color: "rgba(0, 0, 0, 0.87)",
+ boxShadow: 1,
+ fontSize: 11,
+ },
+ defaultImage:
+ "data:image/png;base64,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",
+ },
+ typography: {
+ fontFamily: `"Roboto", "Helvetica", "Arial", sans-serif`,
+ useNextVariants: true,
+ h1: {
+ fontSize: 40,
+ },
+ h4: {
+ fontSize: 30,
+ fontWeight: 500,
+ },
+ h6: {
+ fontSize: 22,
+ },
+ body1: {
+ fontSize: 18,
+ },
+ },
+ overrides: {
+ MuiMenu: {
+ list: {
+ backgroundColor: "#27292d",
+ },
+ },
+ MuiCssBaseline: {
+ '@global': {
+ /*
+ scrollbarColor: "#6b6b6b #2b2b2b",
+ "&::-webkit-scrollbar, & *::-webkit-scrollbar": {
+ backgroundColor: "#2b2b2b",
+ },
+ "&::-webkit-scrollbar-thumb, & *::-webkit-scrollbar-thumb": {
+ borderRadius: 8,
+ backgroundColor: "#6b6b6b",
+ minHeight: 24,
+ border: "3px solid #2b2b2b",
+ },
+ "&::-webkit-scrollbar-thumb:focus, & *::-webkit-scrollbar-thumb:focus": {
+ backgroundColor: "#959595",
+ },
+ "&::-webkit-scrollbar-thumb:active, & *::-webkit-scrollbar-thumb:active": {
+ backgroundColor: "#959595",
+ },
+ "&::-webkit-scrollbar-thumb:hover, & *::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: "#959595",
+ },
+ "&::-webkit-scrollbar-corner, & *::-webkit-scrollbar-corner": {
+ backgroundColor: "#2b2b2b",
+ },
+ */
+ }
+ },
+ },
+});
+
+export default theme;
diff --git a/frontend/src/views/AngularWorkflows.jsx b/frontend/src/views/AngularWorkflows.jsx
new file mode 100644
index 00000000..7b80d767
--- /dev/null
+++ b/frontend/src/views/AngularWorkflows.jsx
@@ -0,0 +1,15429 @@
+import React, { useState, useEffect, useLayoutEffect } from "react";
+import ReactDOM from "react-dom"
+
+import { useInterval } from "react-powerhooks";
+import { makeStyles, useTheme } from "@material-ui/core/styles";
+
+import { v4 as uuidv4 } from "uuid";
+import { useNavigate, Link, useParams } from "react-router-dom";
+// import { Prompt } from "react-router"; // FIXME
+import { useBeforeunload } from "react-beforeunload";
+import ReactJson from "react-json-view";
+import NestedMenuItem from "material-ui-nested-menu-item";
+import ReactMarkdown from "react-markdown";
+import { useAlert } from "react-alert";
+import theme from '../theme';
+import { isMobile } from "react-device-detect"
+import aa from 'search-insights'
+import Drift from "react-driftjs";
+
+//import { Cron } from 'react-js-cron';
+//import 'react-js-cron/dist/styles.css'
+
+
+import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom';
+import algoliasearch from 'algoliasearch/lite';
+
+import {
+ Zoom,
+ Avatar,
+ Popover,
+ TextField,
+ Drawer,
+ Button,
+ Paper,
+ Grid,
+ Tabs,
+ InputAdornment,
+ Tab,
+ ButtonBase,
+ Tooltip,
+ Select,
+ MenuItem,
+ Divider,
+ Dialog,
+ DialogActions,
+ DialogTitle,
+ InputLabel,
+ DialogContent,
+ FormControl,
+ IconButton,
+ Menu,
+ Input,
+ Fade,
+ FormGroup,
+ FormControlLabel,
+ Typography,
+ Checkbox,
+ Breadcrumbs,
+ CircularProgress,
+ SwipeableDrawer,
+ Switch,
+ Chip,
+ Card,
+ List,
+ ListItem,
+ ListItemText,
+ ListItemAvatar,
+ Badge,
+} from "@material-ui/core";
+
+import {
+ AvatarGroup,
+} from "@mui/material"
+
+import {
+ Folder as FolderIcon,
+ LibraryBooks as LibraryBooksIcon,
+ OpenInNew as OpenInNewIcon,
+ Undo as UndoIcon,
+ GetApp as GetAppIcon,
+ Search as SearchIcon,
+ Visibility as VisibilityIcon,
+ Done as DoneIcon,
+ Close as CloseIcon,
+ Error as ErrorIcon,
+ ArrowLeft as ArrowLeftIcon,
+ Cached as CachedIcon,
+ DirectionsRun as DirectionsRunIcon,
+ Polymer as PolymerIcon,
+ FormatListNumbered as FormatListNumberedIcon,
+ PlayArrow as PlayArrowIcon,
+ AspectRatio as AspectRatioIcon,
+ MoreVert as MoreVertIcon,
+ Apps as AppsIcon,
+ Schedule as ScheduleIcon,
+ FavoriteBorder as FavoriteBorderIcon,
+ Pause as PauseIcon,
+ Delete as DeleteIcon,
+ AddCircleOutline as AddCircleOutlineIcon,
+ Save as SaveIcon,
+ KeyboardArrowRight as KeyboardArrowRightIcon,
+ ArrowBack as ArrowBackIcon,
+ Settings as SettingsIcon,
+ LockOpen as LockOpenIcon,
+ ExpandMore as ExpandMoreIcon,
+ VpnKey as VpnKeyIcon,
+ AddComment as AddCommentIcon,
+ Edit as EditIcon,
+} from "@material-ui/icons";
+
+import {
+ Preview as PreviewIcon,
+ ContentCopy as ContentCopyIcon,
+} from '@mui/icons-material';
+
+import Autocomplete from "@material-ui/lab/Autocomplete";
+
+import * as cytoscape from "cytoscape";
+import * as edgehandles from "cytoscape-edgehandles";
+import * as clipboard from "cytoscape-clipboard";
+import CytoscapeComponent from "react-cytoscapejs";
+import undoRedo from "cytoscape-undo-redo";
+
+import Draggable from "react-draggable";
+
+import cytoscapestyle from "../defaultCytoscapeStyle";
+import cxtmenu from "cytoscape-cxtmenu";
+
+import { validateJson, GetIconInfo } from "./Workflows.jsx";
+import { GetParsedPaths } from "./Apps.jsx";
+import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx";
+import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
+import ParsedAction from "../components/ParsedAction.jsx";
+import PaperComponent from "../components/PaperComponent.jsx"
+import ExtraApps from "../components/ExtraApps.jsx"
+import EditWorkflow from "../components/EditWorkflow.jsx"
+// import AppStats from "../components/AppStats.jsx";
+
+const surfaceColor = "#27292D";
+const inputColor = "#383B40";
+
+// http://apps.cytoscape.org/apps/yfileslayoutalgorithms
+cytoscape.use(edgehandles);
+cytoscape.use(clipboard);
+cytoscape.use(undoRedo);
+cytoscape.use(cxtmenu);
+
+// Adds specific text to items
+//import popper from 'cytoscape-popper';
+//cytoscape.use(popper);
+
+// https://stackoverflow.com/questions/19014250/rerender-view-on-browser-resize-with-react
+function useWindowSize() {
+ const [size, setSize] = useState([0, 0]);
+ useLayoutEffect(() => {
+ function updateSize() {
+ setSize([window.innerWidth, window.innerHeight]);
+ }
+ window.addEventListener("resize", updateSize);
+ updateSize();
+ return () => window.removeEventListener("resize", updateSize);
+ }, []);
+ return size;
+}
+
+export function sortByKey(array, key) {
+ if (array === undefined) {
+ return [];
+ }
+
+ if (key.startsWith("-") && key.length > 2) {
+ key = key.slice(1, key.length);
+ return array
+ .sort(function (a, b) {
+ var x = a[key];
+ var y = b[key];
+ return x < y ? -1 : x > y ? 1 : 0;
+ })
+ .reverse();
+ }
+
+ if (array === undefined || array === null) {
+ return [];
+ }
+
+ return array.sort(function (a, b) {
+ var x = a[key];
+ var y = b[key];
+ return x < y ? -1 : x > y ? 1 : 0;
+ });
+}
+
+export function removeParam(key, sourceURL) {
+ if (sourceURL === undefined) {
+ return;
+ }
+
+ var rtn = sourceURL.split("?")[0],
+ param,
+ params_arr = [],
+ queryString = sourceURL.indexOf("?") !== -1 ? sourceURL.split("?")[1] : "";
+
+ if (queryString !== "") {
+ params_arr = queryString.split("&");
+ for (let i = params_arr.length - 1; i >= 0; i -= 1) {
+ param = params_arr[i].split("=")[0];
+ if (param === key) {
+ params_arr.splice(i, 1);
+ }
+ }
+ rtn = rtn + "?" + params_arr.join("&");
+ }
+
+ if (rtn === "?") {
+ return "";
+ }
+
+ return rtn;
+}
+
+const useStyles = makeStyles({
+ notchedOutline: {
+ borderColor: "#f85a3e !important",
+ },
+ root: {
+ "& .MuiAutocomplete-listbox": {
+ border: "2px solid #f85a3e",
+ color: "white",
+ fontSize: 18,
+ "& li:nth-child(even)": {
+ backgroundColor: "#CCC",
+ },
+ "& li:nth-child(odd)": {
+ backgroundColor: "#FFF",
+ },
+ },
+ },
+ inputRoot: {
+ color: "white",
+ // This matches the specificity of the default styles at https://github.com/mui-org/material-ui/blob/v4.11.3/packages/material-ui-lab/src/Autocomplete/Autocomplete.js#L90
+ "&:hover .MuiOutlinedInput-notchedOutline": {
+ borderColor: "#f86a3e",
+ },
+ },
+});
+
+const splitter = "|~|";
+const svgSize = 24;
+//const referenceUrl = "https://shuffler.io/functions/webhooks/"
+//const referenceUrl = window.location.origin+"/api/v1/hooks/"
+
+const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
+const AngularWorkflow = (defaultprops) => {
+ const { globalUrl, isLoggedIn, isLoaded, userdata, data_id } = defaultprops;
+ const referenceUrl = globalUrl + "/api/v1/hooks/";
+ const alert = useAlert()
+ let navigate = useNavigate();
+ const params = useParams();
+ var props = JSON.parse(JSON.stringify(defaultprops))
+ props.match = {}
+ props.match.params = params
+
+ const green = "#86c142";
+ const yellow = "#FECC00";
+ //const theme = useTheme();
+
+
+ var to_be_copied = "";
+ const [firstrequest, setFirstrequest] = React.useState(true);
+ const [cystyle] = useState(cytoscapestyle);
+ const [cy, setCy] = React.useState();
+
+ const [currentView, setCurrentView] = React.useState(0);
+ const [triggerAuthentication, setTriggerAuthentication] = React.useState({});
+ const [triggerFolders, setTriggerFolders] = React.useState([]);
+ const [workflows, setWorkflows] = React.useState([]);
+ const [showEnvironment, setShowEnvironment] = React.useState(false);
+ const [editWorkflowDetails, setEditWorkflowDetails] = React.useState(false);
+
+ const [workflow, setWorkflow] = React.useState({});
+ const [userSettings, setUserSettings] = React.useState({});
+ const [subworkflow, setSubworkflow] = React.useState({});
+ const [subworkflowStartnode, setSubworkflowStartnode] = React.useState("");
+ const [leftViewOpen, setLeftViewOpen] = React.useState(isMobile ? false : true);
+ const [leftBarSize, setLeftBarSize] = React.useState(isMobile ? 0 : 350);
+ const [creatorProfile, setCreatorProfile] = React.useState({});
+ const [usecases, setUsecases] = React.useState([]);
+ const [files, setFiles] = React.useState({
+ "namespaces": [
+ "default",
+ ]
+ });
+ const [appGroup, setAppGroup] = React.useState([]);
+ const [triggerGroup, setTriggerGroup] = React.useState([]);
+ const [executionText, setExecutionText] = React.useState("");
+ const [executionRequestStarted, setExecutionRequestStarted] =
+ React.useState(false);
+ const [scrollConfig, setScrollConfig] = React.useState({
+ top: 0,
+ left: 0,
+ selected: "",
+ });
+
+ const [history, setHistory] = React.useState([]);
+ const [historyIndex, setHistoryIndex] = React.useState(history.length);
+ const [variableInfo, setVariableInfo] = React.useState({})
+
+ const [appAuthentication, setAppAuthentication] = React.useState(undefined);
+ const [variablesModalOpen, setVariablesModalOpen] = React.useState(false);
+ const [executionVariablesModalOpen, setExecutionVariablesModalOpen] =
+ React.useState(false);
+ const [authenticationModalOpen, setAuthenticationModalOpen] =
+ React.useState(false);
+ const [conditionsModalOpen, setConditionsModalOpen] = React.useState(false);
+ const [authenticationType, setAuthenticationType] = React.useState("");
+
+
+ const [workflowDone, setWorkflowDone] = React.useState(false);
+ const [localFirstrequest, setLocalFirstrequest] = React.useState(true);
+ const [requiresAuthentication, setRequiresAuthentication] =
+ React.useState(false);
+ const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false);
+ const [showSkippedActions, setShowSkippedActions] = React.useState(false);
+ const [lastExecution, setLastExecution] = React.useState("");
+ const [configureWorkflowModalOpen, setConfigureWorkflowModalOpen] =
+ React.useState(false);
+
+ const curpath =
+ typeof window === "undefined" || window.location === undefined
+ ? ""
+ : window.location.pathname;
+
+ // 0 = normal, 1 = just done, 2 = normal
+ const [savingState, setSavingState] = React.useState(0);
+
+ const [selectedResult, setSelectedResult] = React.useState({});
+ const [codeModalOpen, setCodeModalOpen] = React.useState(false);
+
+ const [variableAnchorEl, setVariableAnchorEl] = React.useState(null);
+
+ const [sourceValue, setSourceValue] = React.useState({});
+ const [destinationValue, setDestinationValue] = React.useState({});
+ const [conditionValue, setConditionValue] = React.useState({});
+ const [dragging, setDragging] = React.useState(false);
+ const [dragPosition, setDragPosition] = React.useState({
+ x: 0,
+ y: 0,
+ });
+
+ // Trigger stuff
+ const [selectedComment, setSelectedComment] = React.useState({});
+ const [selectedTrigger, setSelectedTrigger] = React.useState({});
+ const [selectedTriggerIndex, setSelectedTriggerIndex] = React.useState({});
+ const [selectedEdge, setSelectedEdge] = React.useState({});
+ const [selectedEdgeIndex, setSelectedEdgeIndex] = React.useState({});
+
+ const [visited, setVisited] = React.useState([]);
+
+ const [apps, setApps] = React.useState([]);
+ const [filteredApps, setFilteredApps] = React.useState([]);
+ const [prioritizedApps, setPrioritizedApps] = React.useState([]);
+
+ const [environments, setEnvironments] = React.useState([]);
+ const [established, setEstablished] = React.useState(false);
+ const [setupSent, setSetupSent] = React.useState(false);
+
+ const [graphSetup, setGraphSetup] = React.useState(false);
+
+ const [selectedApp, setSelectedApp] = React.useState({});
+ const [selectedAction, setSelectedAction] = React.useState({});
+ const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({});
+
+ const [executionRequest, setExecutionRequest] = React.useState({});
+
+ const [executionRunning, setExecutionRunning] = React.useState(false);
+ const [executionModalOpen, setExecutionModalOpen] = React.useState(false);
+ const [executionModalView, setExecutionModalView] = React.useState(0);
+ const [executionData, setExecutionData] = React.useState({});
+ const [appsLoaded, setAppsLoaded] = React.useState(false);
+ const [showVideo, setShowVideo] = React.useState("");
+ const [editWorkflowModalOpen, setEditWorkflowModalOpen] = React.useState(false);
+ const [userediting, setUserediting] = React.useState(false)
+
+ const [lastSaved, setLastSaved] = React.useState(true);
+ const [selectionOpen, setSelectionOpen] = React.useState(false);
+
+ // eslint-disable-next-line no-unused-vars
+ const [_, setUpdate] = useState(""); // Used for rendring, don't remove
+
+ const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
+ const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0);
+
+ // This should all be set once, not on every iteration
+ // Use states and don't update lol
+ const cloudSyncEnabled =
+ props.userdata !== undefined &&
+ props.userdata.active_org !== null &&
+ props.userdata.active_org !== undefined
+ ? props.userdata.active_org.cloud_sync === true
+ : false;
+ const isCloud =
+ window.location.host === "localhost:3002" ||
+ window.location.host === "shuffler.io";
+ const appBarSize = isCloud ? 75 : 60;
+ const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"];
+ const unloadText = "Are you sure you want to leave without saving (CTRL+S)?";
+ const classes = useStyles();
+
+ const [bodyWidth, bodyHeight] = useWindowSize()
+ //console.log("Mobile: ", isMobile, bodyWidth, bodyHeight)
+ const cytoscapeWidth = isMobile ? bodyWidth - leftBarSize : bodyWidth - leftBarSize - 25
+
+ const [elements, setElements] = useState([]);
+ // No point going as fast, as the nodes aren't realtime anymore, but bulk updated.
+ // Set it from 2500 to 6000 to reduce overall load
+ const { start, stop } = useInterval({
+ duration: 3000,
+ startImmediate: false,
+ callback: () => {
+ fetchUpdates();
+ },
+ });
+
+ const getAppDocs = (appname, location, version) => {
+ fetch(`${globalUrl}/api/v1/docs/${appname}?location=${location}&version=${version}`, {
+ headers: {
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status === 200) {
+ //alert.success("Successfully GOT app "+appId)
+ } else {
+ //alert.error("Failed getting app");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success === true) {
+ if (responseJson.reason !== undefined && responseJson.reason !== undefined && responseJson.reason.length > 0) {
+ if (!responseJson.reason.includes("404: Not Found") && responseJson.reason.length > 25) {
+ selectedApp.documentation = responseJson.reason
+ setSelectedApp(selectedApp)
+ setUpdate(Math.random())
+ }
+ }
+ }
+
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ useEffect(() => {
+ if (authenticationModalOpen === true && selectedAction.app_name !== undefined) {
+ console.log(`Should get app docs for: ${selectedAction.app_name}`)
+ //console.log(selectedAction)
+ //console.log("APP: ", selectedApp)
+
+ if (selectedAction.documentation === undefined || selectedAction.documentation === null || selectedAction.documentation.length === 0) {
+ // SelectedApp.documentation = Markdown? If so, it works
+ //
+ const apptype = selectedApp.generated === false ? "python" : "openapi"
+ getAppDocs(selectedAction.app_name, apptype, selectedAction.app_version)
+ }
+ }
+ }, [authenticationModalOpen])
+
+ const getAvailableWorkflows = (trigger_index) => {
+ fetch(globalUrl + "/api/v1/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;
+ }
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson !== undefined) {
+ setWorkflows(responseJson);
+
+ // Sets up subflow trigger with the right info
+ if (trigger_index > -1) {
+ var baseSubflow = {}
+ const trigger = workflow.triggers[trigger_index];
+ if (trigger.parameters.length >= 3) {
+ for (let paramkey in trigger.parameters) {
+ const param = trigger.parameters[paramkey];
+
+ if (param.name === "workflow") {
+ 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
+ ) {
+ if (Object.getOwnPropertyNames(baseSubflow).length > 0) {
+ const foundAction = baseSubflow.actions.find(action => action.id === param.value)
+ if (foundAction !== null && foundAction !== undefined) {
+ setSubworkflowStartnode(foundAction);
+ }
+ } else {
+ setSubworkflowStartnode(param.value);
+ }
+ }
+ }
+ }
+ }
+ }
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("Workflow error: ", error.toString())
+ });
+ };
+
+ function OuterLink(props) {
+ if (props.href.includes("http") || props.href.includes("mailto")) {
+ return (
+
+ {props.children}
+
+ );
+ }
+ return (
+
+ {props.children}
+
+ );
+ }
+
+ function Img(props) {
+ return ;
+ }
+
+ function CodeHandler(props) {
+ return (
+
+ {props.value}
+
+ );
+ }
+
+ function Heading(props) {
+ const element = React.createElement(
+ `h${props.level}`,
+ { style: { marginTop: 40 } },
+ props.children
+ );
+ return (
+
+ {props.level !== 1 ? (
+
+ ) : null}
+ {element}
+
+ );
+ }
+
+ const generateApikey = () => {
+ fetch(globalUrl + "/api/v1/generateapikey", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for APIKEY gen :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ setUserSettings(responseJson);
+ })
+ .catch((error) => {
+ console.log("Apikey error: ", error);
+ });
+ };
+
+ const getSettings = () => {
+ fetch(globalUrl + "/api/v1/getsettings", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for get settings :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (
+ responseJson.success === true &&
+ (responseJson.apikey === undefined ||
+ responseJson.apikey.length === 0 ||
+ responseJson.apikey === null)
+ ) {
+ generateApikey();
+ }
+
+ if (responseJson.success === true) {
+ setUserSettings(responseJson)
+ }
+ })
+ .catch((error) => {
+ console.log("Settings error: ", error);
+ });
+ };
+
+ const setNewAppAuth = (appAuthData) => {
+ fetch(globalUrl + "/api/v1/apps/authentication", {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(appAuthData),
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for setting app auth :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (!responseJson.success) {
+ alert.error("Failed to set app auth: " + responseJson.reason);
+ } else {
+ getAppAuthentication(true, false);
+ setAuthenticationModalOpen(false);
+
+ // Needs a refresh with the new authentication..
+ //alert.success("Successfully saved new app auth")
+ }
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("New auth error: ", error.toString());
+ });
+ };
+
+ const getWorkflowExecution = (id, execution_id) => {
+ fetch(globalUrl + "/api/v1/workflows/" + id + "/executions", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for WORKFLOW EXECUTION :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.length > 0) {
+ // FIXME: Sort this by time
+
+ // - means it's opposite
+ const newkeys = sortByKey(responseJson, "-started_at");
+ setWorkflowExecutions(newkeys);
+
+ const cursearch =
+ typeof window === "undefined" || window.location === undefined
+ ? ""
+ : window.location.search;
+
+ var tmpView = new URLSearchParams(cursearch).get("execution_id");
+ if (
+ execution_id !== undefined && execution_id !== null &&
+ execution_id.length > 0 && (tmpView === undefined || tmpView === null || tmpView.length === 0)
+ ) {
+ tmpView = execution_id;
+ }
+
+ if (tmpView !== undefined && tmpView !== null && tmpView.length > 0) {
+ const execution = responseJson.find((data) => data.execution_id === tmpView);
+
+ if (execution !== null && execution !== undefined) {
+
+ if (execution.execution_argument.includes("too large")) {
+ setExecutionData({});
+ setExecutionRunning(true);
+ setExecutionRequestStarted(false);
+ } else {
+ setExecutionData(execution);
+ }
+
+ setExecutionModalView(1);
+ start();
+
+ setExecutionRequest({
+ execution_id: execution.execution_id,
+ authorization: execution.authorization,
+ });
+
+ const newitem = removeParam("execution_id", cursearch);
+ navigate(curpath + newitem)
+ //props.history.push(curpath + newitem);
+ } else {
+ console.log("Couldn't find execution for execution ID. Retrying as user to get ", tmpView)
+
+ //setExecutionRequestStarted(true);
+ const cur_execution = {
+ execution_id: tmpView,
+ //authorization: data.authorization,
+ }
+ setExecutionModalView(1);
+ setExecutionRequest(cur_execution);
+ start();
+
+ const newitem = removeParam("execution_id", cursearch);
+ navigate(curpath + newitem)
+
+ setTimeout(() => {
+ stop()
+ }, 5000);
+ }
+ }
+ }
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("Get execution error: ", error.toString());
+ });
+ };
+
+ const fetchUpdates = () => {
+ fetch(globalUrl + "/api/v1/streams/results", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(executionRequest),
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for stream results :O!");
+ stop();
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ //console.log("RESPONSE: ", responseJson)
+ handleUpdateResults(responseJson, executionRequest);
+ })
+ .catch((error) => {
+ console.log("Execution result Error: ", error);
+ stop();
+ });
+ };
+
+ const abortExecution = () => {
+ setExecutionRunning(false);
+
+ fetch(
+ globalUrl +
+ "/api/v1/workflows/" +
+ props.match.params.key +
+ "/executions/" +
+ executionRequest.execution_id +
+ "/abort",
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for ABORT EXECUTION :O!");
+ }
+
+ return response.json();
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("Abort error: ", error.toString());
+ });
+ };
+
+ const handleColoring = (actionId, status, label) => {
+ if (cy === undefined) {
+ return
+ }
+
+ var currentnode = cy.getElementById(actionId);
+ if (currentnode.length === 0) {
+ return
+ //continue;
+ }
+
+ currentnode = currentnode[0];
+ const outgoingEdges = currentnode.outgoers("edge");
+ const incomingEdges = currentnode.incomers("edge");
+
+ switch (status) {
+ case "EXECUTING":
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("success-highlight");
+ currentnode.removeClass("failure-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.removeClass("awaiting-data-highlight");
+ incomingEdges.addClass("success-highlight");
+ currentnode.addClass("executing-highlight");
+ break;
+ case "SKIPPED":
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("success-highlight");
+ currentnode.removeClass("failure-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.removeClass("awaiting-data-highlight");
+ currentnode.removeClass("executing-highlight");
+ currentnode.addClass("skipped-highlight");
+ break;
+ case "WAITING":
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("success-highlight");
+ currentnode.removeClass("failure-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.removeClass("awaiting-data-highlight");
+ currentnode.addClass("executing-highlight");
+
+ if (!visited.includes(label)) {
+ if (executionRunning) {
+ visited.push(label);
+ setVisited(visited);
+ }
+ }
+
+ // FIXME - add outgoing nodes to executing
+ //const outgoingNodes = outgoingEdges.find().data().target
+ if (outgoingEdges.length > 0) {
+ outgoingEdges.addClass("success-highlight");
+ }
+ break;
+ case "SUCCESS":
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("executing-highlight");
+ currentnode.removeClass("failure-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.removeClass("awaiting-data-highlight");
+ currentnode.addClass("success-highlight");
+ incomingEdges.addClass("success-highlight");
+ outgoingEdges.addClass("success-highlight");
+
+ if (visited !== undefined && visited !== null && !visited.includes(label)) {
+ if (executionRunning) {
+ visited.push(label);
+ setVisited(visited);
+ }
+ }
+
+ // FIXME - add outgoing nodes to executing
+ //const outgoingNodes = outgoingEdges.find().data().target
+ if (outgoingEdges.length > 0) {
+ for (let i = 0; i < outgoingEdges.length; i++) {
+ const edge = outgoingEdges[i];
+ const targetnode = cy.getElementById(edge.data().target);
+ if (
+ targetnode !== undefined &&
+ !targetnode.classes().includes("success-highlight") &&
+ !targetnode.classes().includes("failure-highlight")
+ ) {
+ targetnode.removeClass("not-executing-highlight");
+ targetnode.removeClass("success-highlight");
+ targetnode.removeClass("shuffle-hover-highlight");
+ targetnode.removeClass("failure-highlight");
+ targetnode.removeClass("awaiting-data-highlight");
+ targetnode.addClass("executing-highlight");
+ }
+ }
+ }
+ break;
+ case "FAILURE":
+ //When status comes as failure, allow user to start workflow execution
+ if (executionRunning) {
+ setExecutionRunning(false);
+ }
+
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("executing-highlight");
+ currentnode.removeClass("success-highlight");
+ currentnode.removeClass("awaiting-data-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.addClass("failure-highlight");
+
+ if (!visited.includes(label)) {
+ //if (item.action.result !== undefined && item.action.result !== null && !item.action.result.includes("failed condition")) {
+ // alert.error("Error for " + item.action.label + " with result " + item.result);
+ //}
+ visited.push(label);
+ setVisited(visited);
+ }
+ break;
+ case "AWAITING_DATA":
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("executing-highlight");
+ currentnode.removeClass("success-highlight");
+ currentnode.removeClass("failure-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.addClass("awaiting-data-highlight");
+ break;
+ default:
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("executing-highlight");
+ currentnode.removeClass("success-highlight");
+ currentnode.removeClass("failure-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.removeClass("awaiting-data-highlight");
+ currentnode.addClass("not-executing-highlight");
+ //console.log("DEFAULT -> Clearing!");
+ break;
+ }
+ }
+
+ // Controls the colors and direction of execution results.
+ // Style is in defaultCytoscapeStyle.js
+ const handleUpdateResults = (responseJson, executionRequest) => {
+ if (responseJson === undefined || responseJson === null || responseJson.success === false) {
+ return
+ }
+ //console.log(responseJson)
+ // Loop nodes and find results
+ // Update on every interval? idk
+
+ ReactDOM.unstable_batchedUpdates(() => {
+ if (JSON.stringify(responseJson) !== JSON.stringify(executionData)) {
+ // FIXME: If another is selected, don't edit..
+ // Doesn't work because this is some async garbage
+ if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) {
+ if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || (executionData.results !== undefined && responseJson.results !== null && executionData.results.length !== responseJson.results.length)) {
+ console.log("Updating data!")
+ setExecutionData(responseJson)
+ } else {
+ console.log("NOT updating executiondata state.");
+ }
+ }
+ }
+
+ if (responseJson.execution_id !== executionRequest.execution_id) {
+ cy.elements().removeClass("success-highlight failure-highlight executing-highlight");
+ return;
+ }
+
+ if (responseJson.results !== null && responseJson.results.length > 0) {
+ // First clear current nodes
+ if (responseJson.workflow.actions !== undefined && responseJson.workflow.actions !== null) {
+ // In clearing of actions
+ for (let actionKey in responseJson.workflow.actions) {
+ var item = responseJson.workflow.actions[actionKey];
+
+ handleColoring(item.id, "", item.label)
+ }
+ }
+
+ for (let resultKey in responseJson.results) {
+ var item = responseJson.results[resultKey];
+
+ handleColoring(item.action.id, item.status, item.action.label)
+ }
+ }
+
+ if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING") {
+ stop();
+
+ if (executionRunning) {
+ setExecutionRunning(false);
+ }
+
+ var curelements = cy.elements();
+ for (let i = 0; i < curelements.length; i++) {
+ if (curelements[i].classes().includes("executing-highlight")) {
+ curelements[i].removeClass("executing-highlight");
+ curelements[i].addClass("failure-highlight");
+ }
+ }
+
+ getWorkflowExecution(props.match.params.key, "");
+ } else if (responseJson.status === "FINISHED") {
+ setExecutionRunning(false);
+ stop();
+ getWorkflowExecution(props.match.params.key, "");
+ setUpdate(Math.random());
+ }
+ })
+ };
+
+ const sendStreamRequest = (body) => {
+ console.log("Stream not activated yet.")
+ return
+
+ // Session may be important here huh
+ body.user_id = userdata.id
+
+ fetch(globalUrl + "/api/v1/workflows/" + props.match.params.key + "/stream", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(body),
+ credentials: "include",
+ })
+ .then((response) => {
+ setSavingState(0);
+ if (response.status !== 200) {
+ console.log("Status not 200 for setting workflows :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ console.log("RESP: ", responseJson)
+ })
+ .catch((error) => {
+ console.log("Stream send error: ", error.toString())
+ //alert.error(error.toString());
+ })
+ }
+
+ const saveWorkflow = (curworkflow, executionArgument, startNode) => {
+ 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`;
+ return;
+ }
+
+ setSavingState(2);
+
+ // This might not be the right course of action, but seems logical, as items could be running already
+ // Makes it possible to update with a version in current render
+ stop();
+ var useworkflow = workflow;
+ if (curworkflow !== undefined) {
+ useworkflow = curworkflow;
+ }
+
+ var cyelements = cy.elements();
+ var newActions = [];
+ var newTriggers = [];
+ var newBranches = [];
+ var newVBranches = [];
+ var newComments = [];
+ for (let cyelementsKey in cyelements) {
+ if (cyelements[cyelementsKey].data === undefined) {
+ continue;
+ }
+
+ var type = cyelements[cyelementsKey].data()["type"];
+ if (type === undefined) {
+ if (
+ cyelements[cyelementsKey].data().source === undefined ||
+ cyelements[cyelementsKey].data().target === undefined
+ ) {
+ continue;
+ }
+
+ var parsedElement = {
+ id: cyelements[cyelementsKey].data().id,
+ source_id: cyelements[cyelementsKey].data().source,
+ destination_id: cyelements[cyelementsKey].data().target,
+ conditions: cyelements[cyelementsKey].data().conditions,
+ decorator: cyelements[cyelementsKey].data().decorator,
+ };
+
+ if (parsedElement.decorator) {
+ newVBranches.push(parsedElement);
+ } else {
+ newBranches.push(parsedElement);
+ }
+ } else {
+ if (type === "ACTION") {
+ const cyelement = cyelements[cyelementsKey].data();
+ const elementid =
+ cyelement.id === undefined || cyelement.id === null
+ ? cyelement["_id"]
+ : cyelement.id;
+
+ var curworkflowAction = useworkflow.actions.find(
+ (a) =>
+ a !== undefined &&
+ (a["id"] === elementid || a["_id"] === elementid)
+ );
+ if (curworkflowAction === undefined) {
+ curworkflowAction = cyelements[cyelementsKey].data();
+ }
+
+ curworkflowAction.position = cyelements[cyelementsKey].position();
+
+ // workaround to fix some edgecases
+ if (
+ curworkflowAction.parameters === "" ||
+ curworkflowAction.parameters === null
+ ) {
+ curworkflowAction.parameters = [];
+ }
+
+ if (
+ curworkflowAction.example === undefined ||
+ curworkflowAction.example === "" ||
+ curworkflowAction.example === null
+ ) {
+ if (cyelements[cyelementsKey].data().example !== undefined) {
+ curworkflowAction.example = cyelements[cyelementsKey].data().example;
+ }
+ }
+
+ // Override just in this place
+ curworkflowAction.errors = [];
+ curworkflowAction.isValid = true;
+
+ // Cleans up OpenAPI items
+ var newparams = [];
+ for (let parametersKey in curworkflowAction.parameters) {
+ const thisitem = curworkflowAction.parameters[parametersKey];
+ if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) {
+ continue;
+ }
+
+ newparams.push(thisitem);
+ }
+
+ curworkflowAction.parameters = newparams;
+ newActions.push(curworkflowAction);
+ } else if (type === "TRIGGER") {
+ if (useworkflow.triggers === undefined || useworkflow.triggers === null) {
+ useworkflow.triggers = [];
+ }
+
+ var curworkflowTrigger = useworkflow.triggers.find(
+ (a) => a.id === cyelements[cyelementsKey].data()["id"]
+ );
+ if (curworkflowTrigger === undefined) {
+ curworkflowTrigger = cyelements[cyelementsKey].data();
+ }
+
+ curworkflowTrigger.position = cyelements[cyelementsKey].position();
+ if (curworkflowTrigger.canConnect === false) {
+ continue
+ }
+
+ newTriggers.push(curworkflowTrigger);
+ } else if (type === "COMMENT") {
+ if (useworkflow.comments === undefined || useworkflow.comments === null) {
+ useworkflow.comments = [];
+ }
+
+ var curworkflowComment = useworkflow.comments.find(
+ (a) => a.id === cyelements[cyelementsKey].data()["id"]
+ )
+
+ if (curworkflowComment === undefined) {
+ curworkflowComment = cyelements[cyelementsKey].data();
+ try {
+ curworkflowComment.position.x = parseInt(curworkflowComment.position.x)
+ } catch (e) {
+ console.log("Failed to parse position Y of comment: ", curworkflowComment.position.x)
+ }
+
+ try {
+ curworkflowComment.position.y = parseInt(curworkflowComment.position.y)
+ } catch (e) {
+ console.log("Failed to parse position Y of comment: ", curworkflowComment.position.y)
+ }
+ }
+
+ const parsedHeight = parseInt(curworkflowComment["height"])
+ if (!isNaN(parsedHeight)) {
+ curworkflowComment.height = parsedHeight
+ } else {
+ curworkflowComment.width = 150
+ }
+
+ const parsedWidth = parseInt(curworkflowComment["width"])
+ if (!isNaN(parsedWidth)) {
+ curworkflowComment.width = parsedWidth
+ } else {
+ curworkflowComment.width = 200
+ }
+
+ curworkflowComment.position = cyelements[cyelementsKey].position();
+ //console.log(curworkflowComment)
+
+ newComments.push(curworkflowComment);
+ } else {
+ alert.info("No handler for type: " + type);
+ }
+ }
+ }
+
+ if (userediting === true) {
+ useworkflow.user_editing = true
+ }
+
+ useworkflow.actions = newActions;
+ useworkflow.triggers = newTriggers;
+ useworkflow.branches = newBranches;
+ useworkflow.comments = newComments;
+ useworkflow.visual_branches = newVBranches;
+
+ // Errors are backend defined
+ useworkflow.errors = [];
+ useworkflow.previously_saved = true;
+
+ if (cy !== undefined) {
+ // scale: 0.3,
+ // bg: "#27292d",
+ const cyImageData = cy.png({
+ output: "base64uri",
+ maxWidth: 480,
+ maxHeight: 270,
+ })
+
+ if (cyImageData !== undefined && cyImageData !== null && cyImageData.length > 0) {
+ useworkflow.image = cyImageData
+ }
+ }
+
+ setLastSaved(true);
+ fetch(globalUrl + "/api/v1/workflows/" + props.match.params.key, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(useworkflow),
+ credentials: "include",
+ })
+ .then((response) => {
+ setSavingState(0);
+ if (response.status !== 200) {
+ console.log("Status not 200 for setting workflows :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (executionArgument !== undefined && startNode !== undefined) {
+ //console.log("Running execution AFTER saving");
+ executeWorkflow(executionArgument, startNode, true);
+ return;
+ }
+
+ if (!responseJson.success) {
+ console.log(responseJson);
+ if (responseJson.reason !== undefined && responseJson.reason !== null) {
+ alert.error("Failed to save: " + responseJson.reason);
+ } else {
+ alert.error("Failed to save. Please contact your support@shuffler.io or your local admin if this is unexpected.")
+ }
+ } else {
+
+ sendStreamRequest({
+ "item": "workflow",
+ "type": "save",
+ "id": workflow.id,
+ })
+
+ if (
+ responseJson.new_id !== undefined &&
+ responseJson.new_id !== null
+ ) {
+ window.location.pathname = "/workflows/" + responseJson.new_id;
+ }
+
+ success = true;
+ if (responseJson.errors !== undefined) {
+ workflow.errors = responseJson.errors;
+ if (responseJson.errors.length === 0) {
+ workflow.isValid = true;
+ workflow.is_valid = true;
+
+ const cyelements = cy.elements();
+
+ for (let i = 0; i < cyelements.length; i++) {
+ //cyelements[i].removeStyle();
+ cyelements[i].data().is_valid = true;
+ cyelements[i].data().errors = [];
+ }
+
+ for (let actionkey in workflow.actions) {
+ workflow.actions[actionkey].is_valid = true;
+ workflow.actions[actionkey].errors = [];
+ }
+ }
+
+ setWorkflow(workflow);
+ }
+
+ setSavingState(1);
+ setTimeout(() => {
+ setSavingState(0);
+ }, 1500);
+ }
+ })
+ .catch((error) => {
+ setSavingState(0);
+ //alert.error(error.toString());
+ console.log("Save workflow error: ", error.toString());
+ });
+
+ return success;
+ };
+
+ const monitorUpdates = () => {
+ var firstnode = cy.getElementById(workflow.start);
+ if (firstnode.length === 0) {
+ var found = false;
+ for (let actionkey in workflow.actions) {
+ if (workflow.actions[actionkey].isStartNode) {
+ console.log("Updating startnode");
+ workflow.start = workflow.actions[actionkey].id;
+ firstnode = cy.getElementById(workflow.actions[actionkey].id);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return false;
+ }
+ }
+
+ cy.elements().removeClass(
+ "success-highlight failure-highlight executing-highlight"
+ );
+ firstnode[0].addClass("executing-highlight");
+
+ return true;
+ };
+
+ const executeWorkflow = (executionArgument, startNode, hasSaved) => {
+ 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) {
+ alert.info("Save it to get a new version");
+ }
+
+ var returncheck = monitorUpdates();
+ if (!returncheck) {
+ alert.error("No startnode set.");
+ return;
+ }
+
+ setVisited([])
+ setExecutionRequest({})
+ stop()
+
+ var curelements = cy.elements();
+ for (let i = 0; i < curelements.length; i++) {
+ curelements[i].addClass("not-executing-highlight");
+ }
+
+ 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",
+ },
+ credentials: "include",
+ body: JSON.stringify(data),
+ }
+ )
+ .then((response) => {
+ setExecutionRequestStarted(false)
+ if (response.status !== 200) {
+ console.log("Status not 200 for WORKFLOW EXECUTION :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (!responseJson.success) {
+ alert.error("Failed to start: " + responseJson.reason);
+ setExecutionRunning(false);
+ setExecutionRequestStarted(false);
+ stop();
+
+ for (let i = 0; i < curelements.length; i++) {
+ curelements[i].removeClass("not-executing-highlight");
+ }
+ return;
+ } else {
+ setExecutionRunning(true);
+ setExecutionRequestStarted(false);
+ }
+
+ if (
+ responseJson.execution_id === "" ||
+ responseJson.execution_id === undefined ||
+ responseJson.authorization === "" ||
+ responseJson.authorization === undefined
+ ) {
+ alert.error("Something went wrong during execution startup");
+ console.log("BAD RESPONSE FOR EXECUTION: ", responseJson);
+ setExecutionRunning(false);
+ setExecutionRequestStarted(false);
+ stop();
+
+ for (let i = 0; i < curelements.length; i++) {
+ curelements[i].removeClass("not-executing-highlight");
+ }
+ return;
+ }
+
+ setExecutionRequest({
+ execution_id: responseJson.execution_id,
+ authorization: responseJson.authorization,
+ });
+ setExecutionData({});
+ setExecutionModalOpen(true);
+ setExecutionModalView(1);
+ start();
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ setExecutionRequestStarted(false)
+ console.log("Execute workflow err: ", error.toString());
+ });
+ })
+ };
+
+ // This can be used to only show prioritzed ones later
+ // Right now, it can prioritize authenticated ones
+ //"Testing",
+ const internalIds = ["Shuffle Tools", "http", "email"];
+
+ const getAppAuthentication = (reset, updateAction) => {
+ fetch(globalUrl + "/api/v1/apps/authentication", {
+ 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) {
+ 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];
+
+ const newfields = {};
+ for (let filterkey in item.fields) {
+ newfields[item.fields[filterkey].key] = item.fields[filterkey].value;
+ }
+
+ item.fields = newfields;
+ if (item.app.name === selectedApp.name) {
+ authenticationOptions.push(item);
+
+ // Always becoming the last one
+ if (item.edited > latest) {
+ latest = item.edited;
+ selectedAction.selectedAuthentication = item;
+
+ for (let actionkey in workflow.actions) {
+ if (workflow.actions[actionkey].app_name === selectedApp.name) {
+ workflow.actions[actionkey].selectedAuthentication = item;
+ workflow.actions[actionkey].authentication_id = item.id;
+ appUpdates = true;
+ }
+ }
+ }
+ }
+ }
+
+ 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);
+ alert.info("Added and updated authentication!");
+ } else {
+ console.log("Closing auth modal? FAIL")
+
+ alert.error("Failed to find new authentication. See details in Oauth2 popup window where auth was attempted.");
+ }
+ } else {
+ alert.info("No authentication to update");
+ }
+ }
+ } else {
+ setAppAuthentication([]);
+ }
+ })
+ .catch((error) => {
+ setAppAuthentication([]);
+ //alert.error("Auth loading error: " + error.toString());
+ console.log("AppAuth error: " + error.toString());
+ });
+ };
+
+ const getApps = () => {
+ fetch(globalUrl + "/api/v1/apps", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ setAppsLoaded(true)
+ if (response.status !== 200) {
+ console.log("Status not 200 for apps :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson === null) {
+ console.log("No response")
+ const pretend_apps = [{
+ "name": "TBD",
+ "app_name": "TBD",
+ "app_version": "TBD",
+ "description": "TBD",
+ "version": "TBD",
+ "large_image": "",
+ }]
+ setApps(pretend_apps)
+ setFilteredApps(pretend_apps)
+ setPrioritizedApps(pretend_apps);
+ return
+ }
+
+ if (responseJson.success === false) {
+ return
+ }
+
+ // FIXME - handle versions on left bar
+ //handleAppVersioning(responseJson)
+ //var tmpapps = []
+ //tmpapps = tmpapps.concat(getExtraApps())
+ //tmpapps = tmpapps.concat(responseJson)
+ setApps(responseJson);
+
+ if (isCloud) {
+ setFilteredApps(
+ responseJson.filter((app) => !internalIds.includes(app.name))
+ );
+ setPrioritizedApps(
+ responseJson.filter((app) => internalIds.includes(app.name))
+ );
+ } else {
+ //setFilteredApps(
+ // responseJson.filter(
+ // (app) =>
+ // !internalIds.includes(app.name) &&
+ // !(!app.activated && app.generated)
+ // )
+ //);
+
+ var tmpFiltered = responseJson.filter((app) => !internalIds.includes(app.name))
+ //tmpFiltered = sortByKey(tmpFiltered, "activated")
+ setFilteredApps(tmpFiltered)
+
+ //!(!app.activated && app.generated)
+ setPrioritizedApps(
+ responseJson.filter((app) => internalIds.includes(app.name))
+ );
+ }
+ })
+ .catch((error) => {
+ setAppsLoaded(true)
+ //alert.error("App loading error: "+error.toString());
+ console.log("App loading error: " + error.toString());
+ });
+ };
+
+ // Searhc by username, userId, workflow, appId should all work
+ const getUserProfile = (username, rerun) => {
+ fetch(`${globalUrl}/api/v1/users/creators/${username}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for WORKFLOW EXECUTION :O!");
+ }
+
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success !== false) {
+ console.log("Found creator: ", responseJson)
+ setCreatorProfile(responseJson)
+ } else {
+ console.log("Couldn't find the creator profile (rerun?): ", responseJson, rerun)
+ // If the current user is any of the Shuffle Creators
+ // AND the workflow doesn't have an owner: allow editing.
+ // else: Allow suggestions?
+ //console.log("User: ", userdata)
+ //if (rerun !== true) {
+ // getUserProfile(userdata.id, true)
+ //}
+ }
+ })
+ .catch((error) => {
+ console.log("Get userprofile error: ", error);
+ })
+ }
+
+ const getFiles = () => {
+ fetch(globalUrl + "/api/v1/files", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ //console.log("Status not 200 for apps :O!");
+ return;
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.files !== undefined && responseJson.files !== null) {
+ setFiles(responseJson);
+ } else {
+ setFiles({
+ "namespaces": [
+ "default"
+ ]
+ });
+ }
+
+ if (
+ responseJson.namespaces !== undefined &&
+ responseJson.namespaces !== null
+ ) {
+ //setFileNamespaces(responseJson.namespaces);
+ }
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("Error loading files: ", error)
+ });
+ };
+
+ const getWorkflow = (workflow_id, sourcenode) => {
+ fetch(globalUrl + "/api/v1/workflows/" + workflow_id, {
+ 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!");
+ window.location.pathname = "/workflows";
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ // Not sure why this is necessary.
+ if (responseJson.isValid === undefined) {
+ responseJson.isValid = true;
+ }
+
+ if (responseJson.errors === undefined) {
+ responseJson.errors = [];
+ }
+
+ if (responseJson.actions === undefined || responseJson.actions === null) {
+ responseJson.actions = [];
+ }
+
+ if (responseJson.triggers === undefined || responseJson.triggers === null) {
+ responseJson.triggers = [];
+ }
+
+ if (responseJson.public) {
+ //alert.info("This workflow is public. Save the workflow to use it in your organization.");
+
+ setAppAuthentication([])
+ console.log("RESP: ", responseJson)
+ if (Object.getOwnPropertyNames(creatorProfile).length === 0) {
+ //getUserProfile("frikky")
+ getUserProfile(responseJson.id, false)
+ }
+
+ //{appGroup.map((data, index) => {
+ //const [appGroup, setAppGroup] = React.useState([]);
+ var appsFound = []
+ for (let actionkey in responseJson.actions) {
+ const parsedAction = responseJson.actions[actionkey]
+ if (parsedAction.large_image === undefined || parsedAction.large_image === null || parsedAction.large_image === "") {
+ continue
+ }
+ if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0){
+ appsFound.push(parsedAction)
+ }
+ }
+
+ setAppGroup(appsFound)
+
+ appsFound = []
+ for (let triggerkey in responseJson.triggers) {
+ const parsedAction = responseJson.triggers[triggerkey]
+ if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0){
+ appsFound.push(parsedAction)
+ }
+ }
+
+ setTriggerGroup(appsFound)
+ } else {
+ getAppAuthentication();
+ getEnvironments();
+ getWorkflowExecution(props.match.params.key, "");
+ getAvailableWorkflows(-1);
+ getSettings();
+ getFiles()
+ }
+
+ // Appends SUBFLOWS. Does NOT run during normal grabbing of workflows.
+ if (sourcenode.id !== undefined) {
+ console.log("WORKFLOW: ", responseJson);
+
+ var nodefound = false;
+ const target = sourcenode.parameters.find(
+ (item) => item.name === "startnode"
+ );
+ console.log(sourcenode.parameters);
+ console.log(target);
+ const target_id = target === undefined ? "" : target.value;
+ const actions = responseJson.actions.map((action) => {
+ const node = {
+ group: "nodes",
+ };
+
+ // Set it dynamically?
+ node.position = {
+ x: sourcenode.position.x + action.position.x,
+ y: sourcenode.position.y + action.position.y,
+ };
+
+ node.data = action;
+
+ node.data.canConnect = false
+ node.data.is_valid = true
+ node.data.isValid = true
+ node.data._id = action["id"];
+ node.data.type = "ACTION";
+ node.data.source_workflow = responseJson.id;
+ if (action.id === target_id) {
+ nodefound = true;
+ }
+
+ if (responseJson.public) {
+ node.data.is_valid = true
+ node.is_valid = true
+ }
+
+ var example = "";
+ if (
+ action.example !== undefined &&
+ action.example !== null &&
+ action.example.length > 0
+ ) {
+ example = action.example;
+ }
+
+ node.data.example = example;
+ return node;
+ });
+
+ var triggers = responseJson.triggers.map((trigger) => {
+ const node = {};
+
+ console.log("Only add workflow: ", trigger.app_name)
+ if (trigger.app_name !== "Shuffle Workflow" && trigger.app_name !== "User Input") {
+ return null
+ }
+
+ node.position = trigger.position;
+ node.data = trigger;
+
+ node.data.canConnect = false
+ node.data.id = trigger["id"];
+ node.data._id = trigger["id"];
+ node.data.type = "TRIGGER";
+
+ return node;
+ });
+
+ triggers = triggers.filter((trigger) => trigger !== null);
+ const insertedNodes = [].concat(actions, triggers);
+ var edges = responseJson.branches.map((branch, index) => {
+ const edge = {};
+ var conditions = responseJson.branches[index].conditions;
+ if (conditions === undefined || conditions === null) {
+ conditions = [];
+ }
+
+ var label = "";
+ if (conditions.length === 1) {
+ label = conditions.length + " condition";
+ } else if (conditions.length > 1) {
+ label = conditions.length + " conditions";
+ }
+
+ const sourceFound = insertedNodes.findIndex(
+ (action) => action.data.id === branch.source_id
+ );
+ if (sourceFound < 0) {
+ return null;
+ }
+
+ const destinationFound = insertedNodes.findIndex(
+ (action) => action.data.id === branch.destination_id
+ );
+ if (destinationFound < 0) {
+ return null;
+ }
+
+
+ edge.data = {
+ id: branch.id,
+ _id: branch.id,
+ source: branch.source_id,
+ target: branch.destination_id,
+ label: label,
+ conditions: conditions,
+ hasErrors: branch.has_errors,
+ decorator: false,
+ source_workflow: responseJson.id,
+ };
+
+ if (responseJson.public) {
+ edge.data.is_valid = true
+ edge.is_valid = true
+ }
+
+ return edge;
+ });
+
+ edges = edges.filter((edge) => edge !== null);
+ cy.removeListener("add");
+ cy.add(insertedNodes)
+ cy.add(edges);
+
+ if (nodefound === true) {
+ const newId = uuidv4();
+ cy.add({
+ group: "edges",
+ data: {
+ id: newId,
+ _id: newId,
+ source: sourcenode.id,
+ target: target_id,
+ label: "Subflow",
+ decorator: true,
+ source_workflow: responseJson.id,
+ },
+ });
+ }
+
+ cy.fit(null, 100);
+ cy.on("add", "node", (e) => onNodeAdded(e));
+ cy.on("add", "edge", (e) => onEdgeAdded(e));
+ } else {
+ setWorkflow(responseJson);
+ setWorkflowDone(true);
+
+ // Add error checks
+ console.log("Workflow: ", responseJson);
+ if (!responseJson.public) {
+ if (
+ !responseJson.previously_saved ||
+ !responseJson.is_valid ||
+ responseJson.errors !== undefined ||
+ responseJson.errors !== null ||
+ responseJson.errors !== // what
+ responseJson.errors.length > 0
+ ) {
+ console.log("Setting configure Modal to open")
+ setConfigureWorkflowModalOpen(true);
+ }
+ }
+ }
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("Get workflows error: ", error.toString());
+ });
+ };
+
+ const onUnselect = (event) => {
+ const nodedata = event.target.data();
+ console.log("UNSELECT: ", nodedata);
+ //if (nodedata.type === "ACTION") {
+ // setLastSelected(nodedata)
+ //}
+ //
+
+ // Wait for new node to possibly be selected
+ //setTimeout(() => {
+ const typeIds = cy.elements('node:selected').jsons();
+ for (var idkey in typeIds) {
+ const item = typeIds[idkey]
+ if (item.data.isButton === true) {
+ //console.log("Reselect old node & return - or just return?")
+
+ if (item.data.buttonType === "delete" && item.data.attachedTo === nodedata.id) {
+ //console.log("delete of same node!")
+ }
+ return
+ }
+ }
+
+ // Unselecting all
+ //cy.elements().unselect()
+
+ //if (nodedata.app_name === undefined && nodedata.source === undefined) {
+ // return;
+ //}
+ //event.target.removeClass("selected");
+ //
+
+
+
+
+
+ //// If button is clicked, select current node
+
+ // Attempt at rewrite of name in other actions in following nodes.
+ // Should probably be done in the onBlur for the textfield instead
+ /*
+ if (event.target.data().type === "ACTION") {
+ const nodeaction = event.target.data()
+ const curaction = workflow.actions.find(a => a.id === nodeaction.id)
+ console.log("workflowaction: ", curaction)
+ console.log("nodeaction: ", nodeaction)
+ if (nodeaction.label !== curaction.label) {
+ console.log("BEACH!")
+
+ var params = []
+ const fixedName = "$"+curaction.label.toLowerCase().replace(" ", "_")
+ for (var actionkey in workflow.actions) {
+ if (workflow.actions[actionkey].id === curaction.id) {
+ continue
+ }
+
+ for (var paramkey in workflow.actions[actionkey].parameters) {
+ const param = workflow.actions[actionkey].parameters[paramkey]
+ if (param.value === null || param.value === undefined || !param.value.includes("$")) {
+ continue
+ }
+
+ const innername = param.value.toLowerCase().replace(" ", "_")
+ if (innername.includes(fixedName)) {
+ //workflow.actions[actionkey].parameters[paramkey].replace(
+ //console.log("FOUND!: ", innername)
+ }
+ }
+ }
+ }
+ }
+ */
+
+
+ // FIXME - check if they have value before overriding like this for no reason.
+ // Would save a lot of time (400~ ms -> 30ms)
+ //console.log("ACTION: ", selectedAction)
+ //console.log("APP: ", selectedApp)
+
+ ReactDOM.unstable_batchedUpdates(() => {
+ setSelectedAction({});
+ setSelectedApp({});
+ setSelectedTrigger({});
+ setSelectedComment({})
+ setSelectedEdge({});
+
+ setSelectedEdge({})
+ setSelectedActionEnvironment({})
+ setTriggerAuthentication({})
+ setSelectedTriggerIndex(-1)
+ setTriggerFolders([])
+ setSubworkflow({})
+ setLocalFirstrequest(true)
+
+ // Can be used for right side view
+ setRightSideBarOpen(false);
+ setScrollConfig({
+ top: 0,
+ left: 0,
+ selected: "",
+ });
+ //console.timeEnd("UNSELECT");
+ })
+
+ sendStreamRequest({
+ "item": "node",
+ "type": "unselect",
+ "userid": userdata.id,
+ })
+ //}, 150)
+ };
+
+ const onEdgeSelect = (event) => {
+ ReactDOM.unstable_batchedUpdates(() => {
+ setRightSideBarOpen(true);
+ setLastSaved(false);
+
+ /*
+ // Used to not be able to edit trigger-based branches.
+ const triggercheck = workflow.triggers.find(trigger => trigger.id === event.target.data()["source"])
+ if (triggercheck === undefined) {
+ */
+ if (
+ event.target.data("type") !== "COMMENT" &&
+ event.target.data().decorator
+ ) {
+ alert.info("This edge can't be edited.");
+ } else {
+ //console.log("DATA: ", event.target.data())
+ const destinationId = event.target.data("target");
+ //console.log("DATA: ", event.target.data())
+ const curaction = workflow.actions.find((a) => a.id === destinationId);
+ //console.log("ACTION: ", curaction)
+ if (curaction !== undefined && curaction !== null) {
+ if (
+ curaction.app_name === "Shuffle Tools" &&
+ curaction.name === "router"
+ ) {
+ alert.info("Router action can't have incoming conditions");
+ event.target.unselect();
+ return;
+ }
+ }
+
+ setSelectedEdgeIndex(
+ workflow.branches.findIndex(
+ (data) => data.id === event.target.data()["id"]
+ )
+ );
+ setSelectedEdge(event.target.data());
+ }
+
+ setSelectedAction({});
+ setSelectedTrigger({});
+ })
+ };
+
+ // Comparing locations between nodes and setting views
+ var styledElements = [];
+ var originalLocation = {
+ x: 0,
+ y: 0,
+ };
+
+ const onCtxTap = (event) => {
+ const nodedata = event.target.data();
+ console.log(nodedata);
+ if (
+ nodedata.type === "TRIGGER" &&
+ nodedata.app_name === "Shuffle Workflow"
+ ) {
+ if (nodedata.parameters === null) {
+ alert.error("Set a workflow first");
+ return;
+ }
+
+ if (nodedata.parameters === undefined) {
+ return
+ }
+
+ const workflow_id = nodedata.parameters.find(
+ (param) => param.name === "workflow"
+ );
+
+ if (workflow.id === workflow_id.valu) {
+ return;
+ }
+
+ cy.animation({
+ zoom: 0,
+ center: {
+ eles: event.target,
+ },
+ })
+ .play()
+ .promise()
+ .then(() => {
+ console.log("DONE: ", workflow_id);
+ getWorkflow(workflow_id.value, nodedata);
+ cy.fit(null, 50);
+ });
+ }
+ };
+
+ const onNodeDragStop = (event, selectedAction) => {
+ const nodedata = event.target.data();
+ if (nodedata.id === selectedAction.id) {
+ return;
+ }
+
+ if (nodedata.finished === false) {
+ return;
+ }
+
+ const connected = event.target.connectedEdges().jsons()
+ if (connected.length > 0 && connected !== undefined) {
+ for (let connectkey in connected) {
+ const edge = connected[connectkey]
+ //console.log("EDGE:", edge)
+
+ //const edge = edgeBase.json()
+
+ const sourcenode = cy.getElementById(edge.data.source)
+ const destinationnode = cy.getElementById(edge.data.target)
+ if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) {
+ continue
+ }
+
+ const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position())
+ const currentedge = cy.getElementById(edge.data.id)
+ if (currentedge !== undefined && currentedge !== null) {
+ currentedge.style('control-point-distance', edgeCurve.distance)
+ currentedge.style('control-point-weight', edgeCurve.weight)
+ }
+ }
+ }
+
+ if (styledElements.length === 1) {
+ console.log(
+ "Should reset location and autofill: ",
+ styledElements,
+ selectedAction
+ );
+ if (originalLocation.x !== 0 || originalLocation.y !== 0) {
+ const currentnode = cy.getElementById(nodedata.id);
+ if (currentnode !== null && currentnode !== undefined) {
+ currentnode.position("x", originalLocation.x);
+ currentnode.position("y", originalLocation.y);
+ }
+
+ originalLocation = { x: 0, y: 0 };
+ }
+
+ const curElement = document.getElementById(styledElements[0]);
+ if (curElement !== null && curElement !== undefined) {
+ curElement.style.border = curElement.style.original_border;
+ var newValue = "$" + nodedata.label.toLowerCase().replaceAll(" ", "_");
+ if (nodedata.type === "TRIGGER") {
+ if (nodedata.trigger_type === "WEBHOOK" || nodedata.trigger_type === "SCHEDULE" || nodedata.trigger_type === "EMAIL") {
+ var newValue = "$exec"
+ }
+ }
+ var paramname = "";
+ var idnumber = -1;
+ if (curElement.id.startsWith("rightside_field_")) {
+ console.log("FOUND FIELD WITH NUMBER: ", curElement.id);
+
+
+ // Find exact position to put the text
+
+ const idsplit = curElement.id.split("_");
+ console.log(idsplit);
+ if (idsplit.length === 3 && !isNaN(idsplit[2])) {
+ console.log("ADDING TO PARAM ", idsplit[2]);
+
+ selectedAction.parameters[idsplit[2]].value += newValue;
+ paramname = selectedAction.parameters[idsplit[2]].name;
+ idnumber = idsplit[2];
+ }
+ }
+
+ if (idnumber >= 0 && paramname.length > 0) {
+ const exampledata = GetExampleResult(nodedata);
+ const parsedname = paramname
+ .toLowerCase()
+ .trim()
+ .replaceAll("_", " ");
+
+ const foundresult = GetParamMatch(parsedname, exampledata, "");
+ if (foundresult.length > 0) {
+ console.log("FOUND RESULT: ", paramname, foundresult);
+ newValue = `${newValue}${foundresult}`;
+ }
+
+ selectedAction.parameters[idnumber].value = newValue;
+ }
+
+ curElement.value = newValue;
+ }
+ }
+
+ if (
+ nodedata.app_name !== undefined &&
+ ((nodedata.app_name !== "Shuffle Tools" &&
+ nodedata.app_name !== "Testing" &&
+ nodedata.app_name !== "Shuffle Workflow" &&
+ nodedata.app_name !== "User Input" &&
+ nodedata.app_name !== "Webhook" &&
+ nodedata.app_name !== "Schedule" &&
+ nodedata.app_name !== "Email") ||
+ nodedata.isStartNode)
+ ) {
+ const allNodes = cy.nodes().jsons();
+ var found = false;
+ for (let nodekey in allNodes) {
+ const currentNode = allNodes[nodekey];
+ if (
+ currentNode.data.attachedTo === nodedata.id &&
+ currentNode.data.isDescriptor
+ ) {
+ found = true;
+ console.log("FOUND THE NODE!");
+ break;
+ }
+ }
+
+ // Readding the icon after moving the node
+ if (!found) {
+ const iconInfo = GetIconInfo(nodedata);
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ const offset = nodedata.isStartNode ? 36 : 44;
+ const decoratorNode = {
+ position: {
+ x: event.target.position().x + offset,
+ y: event.target.position().y + offset,
+ },
+ locked: true,
+ data: {
+ isDescriptor: true,
+ isValid: true,
+ is_valid: true,
+ label: "",
+ image: svgpin_Url,
+ imageColor: iconInfo.iconBackgroundColor,
+ attachedTo: nodedata.id,
+ },
+ };
+
+ cy.add(decoratorNode).unselectify();
+ } else {
+ console.log("Node already exists - don't add descriptor node");
+ }
+ }
+
+ originalLocation = {
+ x: 0,
+ y: 0,
+ };
+
+ sendStreamRequest({
+ "item": "node",
+ "type": "move",
+ "id": nodedata.id,
+ "location": {
+ "x": event.target.position("x"),
+ "y": event.target.position("y"),
+ }
+ })
+ };
+
+ const onNodeDrag = (event, selectedAction) => {
+ const nodedata = event.target.data();
+ if (nodedata.finished === false) {
+ console.log("NOT FINISHED - ADD EXAMPLE BRANCHES TO CLOSEST!!")
+ return
+ }
+
+ if (nodedata.app_name !== undefined) {
+ const allNodes = cy.nodes().jsons();
+ for (var nodekey in allNodes) {
+ const currentNode = allNodes[nodekey];
+ if (currentNode.data.attachedTo === nodedata.id) {
+ cy.getElementById(currentNode.data.id).remove();
+ }
+
+ // Calculate location
+ //currentNode.position.x >
+ //if (nodedata.position.x > 0 && nodedata.position.y > 0) {
+ // console.log("Positive both")
+ //}
+
+ //console.log(currentNode.position)
+ //console.log(nodedata.position)
+ }
+ } else {
+ //console.log("No appid? ", nodedata)
+ }
+
+ if (nodedata.buttonType === "edgehandler") {
+ console.log("Enable edgehandler!")
+ console.log("Find parent: ", nodedata.attachedTo)
+ const parentNode = cy.getElementById(nodedata.attachedTo);
+ if (parentNode !== null && parentNode !== undefined) {
+ console.log("Start parentnode tracking!")
+ //cy.edgehandles().start(parentNode)
+ }
+ }
+
+ if (nodedata.id === selectedAction.id) {
+ return;
+ }
+
+
+
+ /*
+ // 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.found) {
+ console.log("No closest node found for: ", nodedata.id)
+ } else {
+ console.log("Closest: ", closestNode)
+ }
+ */
+
+ if (
+ originalLocation.x === 0 &&
+ originalLocation.y === 0 &&
+ nodedata.position !== undefined
+ ) {
+ originalLocation.x = nodedata.position.x;
+ originalLocation.y = nodedata.position.y;
+ }
+
+ // Part of autocomplete. Styles elements in frontend to indicate
+ // what and where we may input data for the user.
+ const onMouseUpdate = (e) => {
+ const x = e.pageX;
+ const y = e.pageY;
+
+ const elementMouseIsOver = document.elementFromPoint(x, y);
+ if (elementMouseIsOver !== undefined && elementMouseIsOver !== null) {
+ // Color for #f85a3e translated to rgb
+ const newBorder = "3px solid rgb(248, 90, 62)";
+ if (
+ elementMouseIsOver.style.border !== newBorder &&
+ elementMouseIsOver.id.includes("rightside")
+ ) {
+ if (elementMouseIsOver.style.border !== undefined) {
+ elementMouseIsOver.style.original_border =
+ elementMouseIsOver.style.border;
+ } else {
+ elementMouseIsOver.style.original_border = "";
+ }
+
+ elementMouseIsOver.style.border = newBorder;
+ console.log("STYLED: ", styledElements);
+ for (var styledElementsKey in styledElements) {
+ const curElement = document.getElementById(styledElements[styledElementsKey]);
+ if (curElement !== null && curElement !== undefined) {
+ curElement.style.border = curElement.style.original_border;
+ }
+ }
+
+ styledElements = [];
+ styledElements.push(elementMouseIsOver.id);
+ } else if (
+ elementMouseIsOver.id === "cytoscape_view" ||
+ elementMouseIsOver.id === ""
+ ) {
+ for (var index in styledElements) {
+ const curElement = document.getElementById(styledElements[index]);
+ if (curElement !== null && curElement !== undefined) {
+ curElement.style.border = curElement.style.original_border;
+ }
+ }
+
+ styledElements = [];
+ }
+ }
+
+ // Ensure it only happens once
+ document.removeEventListener("mousemove", onMouseUpdate, false);
+ };
+
+ document.addEventListener("mousemove", onMouseUpdate, false);
+ };
+
+
+ useBeforeunload(() => {
+ if (!lastSaved) {
+ return unloadText;
+ } else {
+ if (workflow.public === false) {
+ //document.removeEventListener("mousemove", onMouseUpdate, true);
+ document.removeEventListener("keydown", handleKeyDown, true);
+ document.removeEventListener("paste", handlePaste, true);
+ }
+ }
+ });
+
+ // Nodeselectbatching:
+ // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once
+ // onNodeClick
+ const onNodeSelect = (event, newAppAuth) => {
+ // Forces all states to update at the same time,
+ // Otherwise everything is SUPER slow
+ ReactDOM.unstable_batchedUpdates(() => {
+ const data = event.target.data();
+ if (data.isButton) {
+ if (data.buttonType === "delete") {
+ const parentNode = cy.getElementById(data.attachedTo);
+ if (parentNode !== null && parentNode !== undefined) {
+ removeNode(data.attachedTo)
+ //parentNode.remove()
+ }
+
+ return
+ } else if (data.buttonType === "set_startnode" && data.type !== "TRIGGER") {
+ const parentNode = cy.getElementById(data.attachedTo);
+ if (parentNode !== null && parentNode !== undefined) {
+ var oldstartnode = cy.getElementById(workflow.start);
+ if (
+ oldstartnode !== null &&
+ oldstartnode !== undefined &&
+ oldstartnode.length > 0
+ ) {
+ try {
+ oldstartnode[0].data("isStartNode", false);
+ } catch (e) {
+ console.log("Startnode error: ", e);
+ }
+ }
+
+ workflow.start = parentNode.data("id");
+ setLastSaved(false);
+ parentNode.data("isStartNode", true);
+ }
+
+ //event.target.unselect();
+ setRightSideBarOpen(true);
+ return
+
+ } else if (data.buttonType === "copy") {
+ console.log("COPY!");
+
+ // 1. Find parent
+ // 2. Find branches for parent
+ // 3. Make a new node that's moved a little bit
+ const parentNode = cy.getElementById(data.attachedTo);
+ if (parentNode !== null && parentNode !== undefined) {
+ var newNodeData = JSON.parse(JSON.stringify(parentNode.data()));
+ newNodeData.id = uuidv4();
+ if (newNodeData.position !== undefined) {
+ newNodeData.position = {
+ x: newNodeData.position.x + 100,
+ y: newNodeData.position.y + 100,
+ };
+ }
+
+ newNodeData.isStartNode = false;
+ newNodeData.errors = [];
+ newNodeData.is_valid = true;
+ newNodeData.isValid = true;
+ newNodeData.label = parentNode.data("label") + "_copy";
+
+ cy.add({
+ group: "nodes",
+ data: newNodeData,
+ position: newNodeData.position,
+ });
+
+ // Readding the icon after moving the node
+ if (
+ newNodeData.app_name !== "Testing" ||
+ newNodeData.app_name !== "Shuffle Workflow"
+ ) {
+ } else {
+ const iconInfo = GetIconInfo(newNodeData);
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ const offset = newNodeData.isStartNode ? 36 : 44;
+ const decoratorNode = {
+ position: {
+ x: newNodeData.position.x + offset,
+ y: newNodeData.position.y + offset,
+ },
+ locked: true,
+ data: {
+ isDescriptor: true,
+ isValid: true,
+ is_valid: true,
+ label: "",
+ image: svgpin_Url,
+ imageColor: iconInfo.iconBackgroundColor,
+ attachedTo: newNodeData.id,
+ },
+ };
+
+ cy.add(decoratorNode).unselectify();
+ }
+
+ workflow.actions.push(newNodeData);
+
+ const sourcebranches = workflow.branches.filter(
+ (foundbranch) => foundbranch.source_id === parentNode.data("id")
+ );
+ const destinationbranches = workflow.branches.filter(
+ (foundbranch) =>
+ foundbranch.destination_id === parentNode.data("id")
+ );
+
+ for (var sourceBranchesKey in sourcebranches) {
+ var newbranch = JSON.parse(JSON.stringify(sourcebranches[sourceBranchesKey]));
+ newbranch.id = uuidv4();
+ newbranch.source_id = newNodeData.id;
+
+ newbranch._id = newbranch.id;
+ newbranch.source = newbranch.source_id;
+ newbranch.target = newbranch.destination_id;
+ cy.add({
+ group: "edges",
+ data: newbranch,
+ });
+ }
+
+ for (var destinationBranchesKey in destinationbranches) {
+ var newbranch = JSON.parse(
+ JSON.stringify(destinationbranches[destinationBranchesKey])
+ );
+ newbranch.id = uuidv4();
+ newbranch.destination_id = newNodeData.id;
+
+ newbranch._id = newbranch.id;
+ newbranch.source = newbranch.source_id;
+ newbranch.target = newbranch.destination_id;
+ cy.add({
+ group: "edges",
+ data: newbranch,
+ });
+ }
+
+ //event.target.unselect();
+ return
+ }
+ }
+
+ return;
+ } else if (data.isDescriptor) {
+ console.log("Can't select descriptor");
+ event.target.unselect();
+ return;
+ }
+
+ if (data.type === "ACTION") {
+ setSelectedComment({})
+ //var curaction = JSON.parse(JSON.stringify(data))
+ // FIXME: Trust it to just work?
+ //event.target.data()
+ var curaction = workflow.actions.find((a) => a.id === data.id)
+ if (!curaction || curaction === undefined) {
+ if (data.id !== undefined && data.app_name !== undefined) {
+ workflow.actions.push(data)
+ setWorkflow(workflow)
+ curaction = data
+ } else {
+ if (workflow.public !== true) {
+ alert.error("Action not found. Please remake it.");
+ }
+
+ event.target.remove();
+ return;
+ }
+ }
+
+ //var newapps = JSON.parse(JSON.stringify(apps))
+ var newapps = apps
+ if (apps === null || apps === undefined || apps.length === 0) {
+ newapps = filteredApps
+ }
+
+ // Check ID first, then names etc
+ // That way it always selects the right IF it exists
+ var curapp = newapps.find((a) =>
+ a.id === curaction.app_id
+ )
+
+ if (curapp === undefined || curapp === null) {
+ console.log("Couldn't find ID - checking with name & version")
+
+ curapp = newapps.find((a) =>
+ a.name === curaction.app_name &&
+ (a.app_version === curaction.app_version ||
+ (a.loop_versions !== null &&
+ a.loop_versions.includes(curaction.app_version)))
+ )
+ }
+
+ if (curaction.template === true) {
+ //newapps.
+ const parsedname = curaction.name.replaceAll(" ", "_").toLowerCase()
+ console.log("FIND AN ACTION AMONG THE APPS THAT MATCHES NAME: ", parsedname)
+
+ curaction.matching_actions = []
+ for (var newAppskey in newapps) {
+ for (let actionsSubkey in newapps[newAppskey].actions) {
+ const tmpaction = newapps[newAppskey].actions[actionsSubkey]
+ if (tmpaction.name.replaceAll(" ", "_").toLowerCase() === parsedname) {
+ console.log("MATCH!: ", newapps[newAppskey])
+ curaction.matching_actions.push({
+ "app_name": newapps[newAppskey].name,
+ "app_version": newapps[newAppskey].app_version,
+ "app_id": newapps[newAppskey].id,
+ "action": tmpaction,
+ "large_image": newapps[newAppskey].large_image,
+ "app_index": newAppskey,
+ "action_index": actionsSubkey,
+ })
+ }
+ }
+ }
+ }
+
+ if (!curapp || curapp === undefined) {
+ console.log("APPS - couldn't find it: ", newapps)
+ //alert.error(`App ${curaction.app_name}:${curaction.app_version} not found. Is it activated?`);
+
+ const tmpapp = {
+ name: curaction.app_name,
+ app_name: curaction.app_name,
+ app_version: curaction.app_version,
+ id: curaction.app_id,
+ actions: [curaction],
+ };
+
+ setSelectedApp(tmpapp);
+ setSelectedAction(curaction);
+ } else {
+ //if (curapp.id !== curaction.id) {
+ // curaction.app_id = curapp.id
+ // //.valueOf()
+ //}
+ curaction.app_id = curapp.id
+
+ setAuthenticationType(
+ curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null ? {
+ type: "oauth2",
+ redirect_uri: curapp.authentication.redirect_uri,
+ refresh_uri: curapp.authentication.refresh_uri,
+ token_uri: curapp.authentication.token_uri,
+ scope: curapp.authentication.scope,
+ client_id: curapp.authentication.client_id,
+ client_secret: curapp.authentication.client_secret,
+ } : {
+ type: "",
+ }
+ )
+
+ const requiresAuth = curapp.authentication.required; //&& ((curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) || (curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null))
+ setRequiresAuthentication(requiresAuth);
+ if (curapp.authentication.required) {
+ //console.log("App requires auth.")
+ // Setup auth here :)
+ const authenticationOptions = [];
+ var findAuthId = "";
+ if (
+ curaction.authentication_id !== null &&
+ curaction.authentication_id !== undefined &&
+ curaction.authentication_id.length > 0
+ ) {
+ findAuthId = curaction.authentication_id;
+ }
+
+ const tmpAuth = JSON.parse(JSON.stringify(newAppAuth));
+ //console.log("FOUND AUTH OPTIONS: ", tmpAuth)
+
+ const curappName = curapp.name.toLowerCase()
+ for (let tmpAuthKey in tmpAuth) {
+ var item = tmpAuth[tmpAuthKey];
+
+ const newfields = {};
+ if (item.app.name.toLowerCase() !== curappName) {
+ continue
+ }
+
+ // Makes list into key:value object
+ 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.name.toLowerCase() === curappName) {
+ authenticationOptions.push(item);
+ if (item.id === findAuthId) {
+ curaction.selectedAuthentication = item;
+ }
+ }
+ }
+
+ curaction.authentication = authenticationOptions;
+ if (
+ curaction.selectedAuthentication === null ||
+ curaction.selectedAuthentication === undefined ||
+ curaction.selectedAuthentication.length === ""
+ ) {
+ curaction.selectedAuthentication = {};
+ }
+ } else {
+ curaction.authentication = [];
+ curaction.authentication_id = "";
+ curaction.selectedAuthentication = {};
+ }
+
+ if (
+ curaction.parameters !== undefined &&
+ curaction.parameters !== null &&
+ curaction.parameters.length > 0
+ ) {
+ for (var curActionParamKey in curaction.parameters) {
+ if (
+ curaction.parameters[curActionParamKey].options !== undefined &&
+ curaction.parameters[curActionParamKey].options !== null &&
+ curaction.parameters[curActionParamKey].options.length > 0 &&
+ curaction.parameters[curActionParamKey].value === ""
+ ) {
+ curaction.parameters[curActionParamKey].value = curaction.parameters[curActionParamKey].options[0];
+ }
+ }
+ } else {
+ console.log("Should check APP if it has the same params as ACTION")
+ for (let actionKey in curapp.actions) {
+ const tmpaction = curapp.actions[actionKey]
+ if (tmpaction.name === curaction.name) {
+ console.log("Found action - needs change?", tmpaction)
+ if (tmpaction.parameters !== undefined && tmpaction.parameters !== null && tmpaction.parameters.length > 0) {
+ curaction.parameters = JSON.parse(JSON.stringify(tmpaction.parameters))
+ }
+ break
+ }
+ }
+ }
+
+ //curaction["authentication"] = []
+ //curaction["authentication_id"] = ""
+ // Fix parameters that are... Not ideal
+ //var paramnames = []
+ //var newparams = []
+ //for (let paramKey in curaction.parameters) {
+ // console.log("Name: ", curaction.parameters[paramKey].name)
+ // if (paramnames.includes(curaction.parameters[paramKey].name)) {
+ // continue
+ // }
+
+ // paramnames.push(curaction.parameters[paramKey].name)
+ // newparams.push(curaction.parameters[paramKey])
+ //}
+
+ //curaction.parameters = newparams
+ console.log("ACTION CLICK: ", curaction)
+
+ setSelectedApp(curapp);
+ setSelectedAction(curaction);
+
+ cy.removeListener("drag");
+ cy.removeListener("free");
+ cy.on("drag", "node", (e) => onNodeDrag(e, curaction));
+ cy.on("free", "node", (e) => onNodeDragStop(e, curaction));
+ }
+
+ if (environments !== undefined && environments !== null && (typeof environments === "array" || typeof environments === "object")) {
+ var parsedenv = environments
+ //if (typeof environments === "object") {
+ // parsedenv = [environments]
+ //}
+
+ const envs = parsedenv.find((a) => a.Name === curaction.environment);
+ var env = environments[defaultEnvironmentIndex]
+ if (envs !== undefined && envs !== null) {
+ env = envs
+ }
+
+ setSelectedActionEnvironment(env);
+ }
+ } else if (data.type === "TRIGGER") {
+ setSelectedComment({})
+ if (workflow.triggers === null) {
+ workflow.triggers = []
+ }
+
+ var trigger_index = workflow.triggers.findIndex(
+ (a) => a.id === data.id
+ );
+
+ //console.log("Trigger: ", data, trigger_index)
+ if (trigger_index === -1) {
+ workflow.triggers.push(data)
+ trigger_index = workflow.triggers.length - 1
+ setWorkflow(workflow)
+ }
+
+ //console.log("Trigger2: ", data, trigger_index)
+ //if (data.id !== undefined && data.app_name !== undefined) {
+ // //newapps.push(data)
+ // workflow.actions.push(data)
+ // curaction = data
+ //} else {
+ // alert.error("Action not found. Please remake it.");
+ // event.target.remove();
+ // return;
+ //}
+
+ if (data.app_name === "Shuffle Workflow") {
+ getAvailableWorkflows(trigger_index);
+ getSettings();
+ } else if (data.app_name === "Webhook") {
+ if (workflow.triggers[trigger_index].parameters !== undefined && workflow.triggers[trigger_index].parameters !== null && workflow.triggers[trigger_index].parameters.length > 0) {
+ console.log("Can set params here!")
+ workflow.triggers[trigger_index].parameters[0] = {
+ name: "url",
+ value: referenceUrl + "webhook_" + workflow.triggers[trigger_index].id,
+ };
+
+ if (workflow.triggers[trigger_index].parameters.length < 5) {
+ console.log("Adding to webhook params!")
+ workflow.triggers[trigger_index].parameters.push({
+ name: "await_response",
+ value: "v1,"
+ })
+ }
+ }
+ }
+
+ setSelectedTriggerIndex(trigger_index);
+ setSelectedTrigger(data);
+ setSelectedActionEnvironment(data.env);
+ } else if (data.type === "COMMENT") {
+ setSelectedComment(data);
+ } else {
+ alert.error("Can't handle " + data.type);
+ return;
+ }
+
+ setRightSideBarOpen(true);
+ setLastSaved(false);
+ setScrollConfig({
+ top: 0,
+ left: 0,
+ selected: "",
+ });
+
+ console.log("DOne in the node update")
+
+ sendStreamRequest({
+ "item": "node",
+ "type": "select",
+ "id": data.id,
+ "userid": userdata.id,
+ "location": {
+ "x": event.target.position("x"),
+ "y": event.target.position("y"),
+ }
+ })
+
+ })
+ }
+
+ const activateApp = (appid, refresh) => {
+ fetch(globalUrl + "/api/v1/apps/" + appid + "/activate", {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Failed to activate")
+ }
+
+ return response.json()
+ })
+ .then((responseJson) => {
+ if (responseJson.success === false) {
+ alert.error("Failed to activate the app")
+ } else {
+ alert.success("App activated for your organization! Refresh the page to use the app.")
+
+ if (refresh === true) {
+ getApps()
+ }
+ }
+ })
+ .catch(error => {
+ //alert.error(error.toString())
+ console.log("Activate app error: ", error.toString())
+ });
+ }
+
+ const GetExampleResult = (item) => {
+ var exampledata = item.example === undefined ? "" : item.example;
+ if (workflowExecutions.length > 0) {
+ // Look for the ID
+ for (let execkey in workflowExecutions) {
+ if (
+ workflowExecutions[execkey].results === undefined ||
+ workflowExecutions[execkey].results === null
+ ) {
+ continue;
+ }
+
+ var foundResult = { result: "" };
+ if (item.id === "exec") {
+ if (
+ workflowExecutions[execkey].execution_argument !== undefined &&
+ workflowExecutions[execkey].execution_argument !== null &&
+ workflowExecutions[execkey].execution_argument.length > 0
+ ) {
+ foundResult.result = workflowExecutions[execkey].execution_argument;
+ } else {
+ continue;
+ }
+ } else {
+ foundResult = workflowExecutions[execkey].results.find(
+ (result) => result.action.id === item.id
+ );
+ if (foundResult === undefined) {
+ continue;
+ }
+ }
+
+ foundResult.result = foundResult.result.trim();
+ foundResult.result = foundResult.result.split(" None").join(' "None"');
+ foundResult.result = foundResult.result.split(" False").join(" false");
+ foundResult.result = foundResult.result.split(" True").join(" true");
+
+ var jsonvalid = true;
+ try {
+ if (
+ !foundResult.result.includes("{") &&
+ !foundResult.result.includes("[")
+ ) {
+ jsonvalid = false;
+ }
+ } catch (e) {
+ try {
+ foundResult.result = foundResult.result.split("'").join('"');
+ if (
+ !foundResult.result.includes("{") &&
+ !foundResult.result.includes("[")
+ ) {
+ jsonvalid = false;
+ }
+ } catch (e) {
+ jsonvalid = false;
+ }
+ }
+
+ // Finds the FIRST json only
+ if (jsonvalid) {
+ try {
+ exampledata = JSON.parse(foundResult.result)
+ } catch (e) {
+ console.log("Result: ", exampledata)
+
+ }
+
+ break;
+ }
+ }
+ }
+
+ return exampledata;
+ };
+
+ const GetParamMatch = (paramname, exampledata, basekey) => {
+ if (typeof exampledata !== "object") {
+ return "";
+ }
+
+ if (exampledata === null) {
+ return "";
+ }
+
+ //console.log("NOT REPLACING ON PURPOSE!!")
+ //return ""
+
+ // Basically just a stupid if-else :)
+ const synonyms = {
+ id: [
+ "id",
+ "ref",
+ "sourceref",
+ "reference",
+ "sourcereference",
+ "alert id",
+ "case id",
+ "incident id",
+ "service id",
+ "sid",
+ "uid",
+ "uuid",
+ "team id",
+ "message id",
+ "message_id",
+ ],
+ title: ["title", "name", "message"],
+ description: ["description", "explanation", "story", "details"],
+ email: ["mail", "email", "sender", "receiver", "recipient"],
+ data: [
+ "data",
+ "ip",
+ "domain",
+ "url",
+ "hash",
+ "md5",
+ "sha2",
+ "sha256",
+ "value",
+ "item",
+ ],
+ tags: ["tags", "taxonomies"],
+ };
+
+ // 1. Find the right synonym
+ // 2. Replace with an autocomplete if it exists
+ var selectedsynonyms = [paramname];
+ for (const [key, value] of Object.entries(synonyms)) {
+ if (key === paramname || value.includes(paramname)) {
+ if (!value.includes(key)) {
+ value.push(key.toLowerCase());
+ }
+
+ selectedsynonyms = value;
+ break;
+ }
+ }
+
+ var toreturn = "";
+
+ for (const [key, value] of Object.entries(exampledata)) {
+ // Check if loop or JSON
+
+ if (typeof value === "object") {
+ if (Array.isArray(value)) {
+ var selectedkey = "";
+ if (isNaN(key)) {
+ selectedkey = `.${key}`;
+ }
+
+ for (let [subitem,subitemval] in Object.entries(value)) {
+ toreturn = GetParamMatch(
+ paramname,
+ value[subitem],
+ `${basekey}${selectedkey}.#`
+ );
+ if (toreturn.length > 0) {
+ break;
+ }
+ }
+
+ if (toreturn.length > 0) {
+ break;
+ }
+
+ } else {
+ var selectedkey = "";
+ if (isNaN(key)) {
+ selectedkey = `.${key}`;
+ }
+
+ toreturn = GetParamMatch(
+ paramname,
+ value,
+ `${basekey}${selectedkey}`
+ );
+ if (toreturn.length > 0) {
+ break;
+ }
+ }
+ } else {
+ if (selectedsynonyms.includes(key.toLowerCase())) {
+ toreturn = `${basekey}.${key}`
+ break
+ }
+ }
+ }
+
+ return toreturn;
+ };
+
+ // Takes an action as input, then runs through and updates the relevant fields
+ // based on previous actions'
+ // Uses lots of synonyms
+ // autocomplete
+ const RunAutocompleter = (dstdata) => {
+ // **PS: The right action should already be set here**
+ // 1. Check execution argument
+ // 2. Check parents in order
+ var exampledata = GetExampleResult({ id: "exec", name: "exec" });
+ var parentlabel = "exec";
+ for (var dstdataParamKey in dstdata.parameters) {
+ const param = dstdata.parameters[dstdataParamKey];
+ // Skip authentication params
+ if (param.configuration) {
+ continue
+ }
+
+ if (param.options !== undefined && param.options !== null && param.options.length > 0) {
+ continue
+ }
+
+ const paramname = param.name.toLowerCase().trim().replaceAll("_", " ");
+
+ const foundresult = GetParamMatch(paramname, exampledata, "");
+ if (foundresult.length > 0) {
+ console.log("FOUND ReS for field: ", dstdata.parameters[dstdataParamKey].name, foundresult)
+ if (dstdata.parameters[dstdataParamKey].value.length === 0) {
+ dstdata.parameters[dstdataParamKey].value = `$${parentlabel}${foundresult}`;
+ dstdata.parameters[dstdataParamKey].autocompleted = true
+ }
+ }
+ }
+
+ var parents = getParents(dstdata);
+ if (parents.length > 1) {
+ for (let parentkey in parents) {
+ const item = parents[parentkey];
+ if (item.label === "Execution Argument") {
+ continue;
+ }
+
+ parentlabel =
+ item.label === undefined
+ ? ""
+ : item.label.toLowerCase().trim().replaceAll(" ", "_");
+
+ exampledata = GetExampleResult(item);
+ if (dstdata.parameters !== undefined && dstdata.parameters !== null) {
+ for (let [paramkey,paramkeyval] in Object.entries(dstdata.parameters)) {
+ const param = dstdata.parameters[paramkey];
+ // Skip authentication params
+ if (param.configuration) {
+ continue
+ }
+
+ if (param.options !== undefined && param.options !== null && param.options.length > 0) {
+ continue
+ }
+
+ const paramname = param.name
+ .toLowerCase()
+ .trim()
+ .replaceAll("_", " ");
+
+ const foundresult = GetParamMatch(paramname, exampledata, "");
+ if (foundresult.length > 0) {
+ if (dstdata.parameters[paramkey].value.length === 0) {
+ dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`;
+ dstdata.parameters[paramkey].autocompleted = true
+ } else {
+ //dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`;
+ }
+ }
+ }
+ }
+ // Check agains every param
+ }
+ }
+
+ return dstdata;
+ };
+
+ // Checks for errors in edges when they're added
+ const onEdgeAdded = (event) => {
+ setLastSaved(false);
+ const edge = event.target.data();
+
+ console.log("edge added: ", edge)
+ if (edge.source === undefined && edge.target === undefined) {
+ return
+ }
+
+ if (edge.readded === true) {
+ console.log("Readded edge - stopping")
+
+ event.target.data("readded", false)
+ return
+ }
+
+ const sourcenode = cy.getElementById(edge.source)
+ const destinationnode = cy.getElementById(edge.target)
+ if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) {
+ } 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") {
+ if (sourcenode.data("app_name") !== "Shuffle Workflow" && sourcenode.data("app_name") !== "User Input") {
+ setTimeout(() => {
+ const alledges = cy.edges().jsons()
+ console.log("edges: ", alledges, edge)
+ var targetedge = alledges.findIndex(
+ (data) => data.data.source === edge.source && data.data.id !== edge.id
+ )
+
+ console.log("Node: ", targetedge)
+ if (targetedge !== -1) {
+ event.target.remove()
+
+ //console.log("Found branch already!")
+ alert.info("Triggers can have exactly one target node")
+ return
+
+
+ // name: "Shuffle Workflow",
+ // name: "User Input",
+ } else {
+ console.log("Node doesn't already have one")
+ }
+ }, 50)
+ }
+ }
+
+ const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position())
+ const currentedge = cy.getElementById(edge.id)
+ if (currentedge !== undefined && currentedge !== null) {
+ currentedge.style('control-point-distance', edgeCurve.distance)
+ currentedge.style('control-point-weight', edgeCurve.weight)
+ }
+
+ }
+
+
+ var targetnode = workflow.triggers.findIndex(
+ (data) => data.id === edge.target
+ );
+ if (targetnode !== -1) {
+ console.log("TARGETNODE: ", targetnode);
+ if (
+ workflow.triggers[targetnode].app_name === "User Input" ||
+ workflow.triggers[targetnode].app_name === "Shuffle Workflow"
+ ) {
+ } else {
+ alert.error("Can't have triggers as target of branch");
+ event.target.remove();
+ }
+ }
+
+ const eventTarget = event.target.target()
+ console.log("BUTTON ADDED! Find parent from: ", eventTarget)
+ if (eventTarget.data("isButton") === true) {
+ const parentNode = cy.getElementById(eventTarget.data("attachedTo"))
+ event.target.remove()
+ console.log("Setting it to parentnode: ", parentNode.data())
+ if (parentNode !== undefined && parentNode !== null) {
+ //event.target.data("target", eventTarget.data("attachedTo"))
+
+ const newEdgeUuid = uuidv4()
+ const newcybranch = {
+ source: event.target.data("source"),
+ target: eventTarget.data("attachedTo"),
+ _id: newEdgeUuid,
+ id: newEdgeUuid,
+ hasErrors: event.target.data("hasErrors"),
+ };
+
+ const edgeToBeAdded = {
+ group: "edges",
+ data: newcybranch,
+ }
+
+ cy.add(edgeToBeAdded);
+ }
+ }
+
+ if (
+ eventTarget.data("isDescriptor") === true ||
+ eventTarget.data("type") === "COMMENT"
+ ) {
+ console.log("Removing because of descriptor or comment")
+ event.target.remove();
+ return;
+ }
+
+ targetnode = -1;
+
+ // Check if:
+ // dest == source && source == dest
+ // 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
+ ) {
+ alert.error("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
+ ) {
+ console.log(edge.source);
+ alert.error("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") {
+ alert.error("Can't make arrow to starting node");
+ event.target.remove();
+ break;
+ }
+
+ found = true;
+ }
+ } 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
+ // targetnode = workflow.triggers.findIndex(data => data.id === edge.source)
+ // console.log("Destination: ", edge.target)
+ // console.log("CHECK SOURCE IF ITS A TRIGGER: ", targetnode)
+ // if (targetnode !== -1) {
+ // alert.error("Triggers can only target one target (startnode)")
+ // event.target.remove()
+ // found = true
+ // break
+ // }
+ } else {
+ //console.log("INSIDE LAST CHECK: ", edge)
+ // Find the targetnode and check if its a trigger
+ // FIXME - do this for both actions and other types?
+ /*
+ targetnode = workflow.triggers.findIndex(data => data.id === edge.target)
+ if (targetnode !== -1) {
+ console.log("TARGETNODE: ", targetnode)
+ if (workflow.triggers[targetnode].app_name === "User Input" || workflow.triggers[targetnode].app_name === "Shuffle Workflow") {
+ } else {
+ alert.error("Can't have triggers as target of branch")
+ event.target.remove()
+ found = true
+ break
+ }
+ }
+ */
+ }
+ }
+
+ // 1. Guess what the next node's action should be
+ // 2. Get result from previous nodes (if any)
+ // 3. TRY to automatically map them in based on synonyms
+ const newsource = cy.getElementById(edge.source);
+ const newdst = cy.getElementById(edge.target);
+ if (
+ newsource !== undefined &&
+ newsource !== null &&
+ newdst !== undefined &&
+ newdst !== null
+ ) {
+ const dstdata = RunAutocompleter(newdst.data());
+ console.log("AUTO DST: ", dstdata);
+ }
+
+ var newbranch = {
+ source_id: edge.source,
+ destination_id: edge.target,
+ id_: edge.id,
+ id: edge.id,
+ hasErrors: false,
+ decorator: false,
+ };
+
+ if (!found) {
+ newbranch["hasErrors"] = false;
+
+ workflow.branches.push(newbranch);
+ setWorkflow(workflow);
+ }
+
+ history.push({
+ type: "edge",
+ action: "added",
+ data: edge,
+ });
+ setHistory(history);
+ setHistoryIndex(history.length);
+ };
+
+ const onNodeAdded = (event) => {
+ const node = event.target;
+ const nodedata = event.target.data();
+
+ //if (Object.keys(nodedata).length === 1) {
+ // console.log("Check if another node actually exists before adding")
+ //}
+
+ if (nodedata.finished === false || (nodedata.id !== undefined && nodedata.is_valid === undefined)
+ ) {
+ //if (nodedata.app_id === undefined) {
+ //console.log("Returning because node is not valid: ", nodedata)
+ return;
+ }
+
+
+ // DONT MOVE THIS LINE RIGHT HERE v
+ setLastSaved(false)
+ // Dont move the line above. May break stuff.
+
+
+ if (node.isNode() && cy.nodes().size() === 1) {
+ workflow.start = node.data("id");
+ nodedata.isStartNode = true;
+ } else {
+ if (workflow.actions === null) {
+ console.log("Returning because node has no value")
+ return;
+ }
+
+ // Remove bad startnode
+ for (let actionkey in workflow.actions) {
+ const action = workflow.actions[actionkey];
+ if (action.isStartNode && workflow.start !== action.id) {
+ action.isStartNode = false;
+ }
+ }
+ }
+
+ if (nodedata.type === "ACTION") {
+ /*
+ var curaction = workflow.actions.find((a) => a.id === nodedata.id);
+ if (curaction === null || curaction === undefined) {
+ alert.error("Node not found. Please remake it.")
+ event.target.remove();
+ }
+ */
+ if (
+ workflow.actions.length === 1 &&
+ workflow.actions[0].id === workflow.start
+ ) {
+ const newEdgeUuid = uuidv4();
+ const newcybranch = {
+ source: workflow.start,
+ target: nodedata.id,
+ _id: newEdgeUuid,
+ id: newEdgeUuid,
+ hasErrors: false,
+ };
+
+ const edgeToBeAdded = {
+ group: "edges",
+ data: newcybranch,
+ };
+
+ console.log("SHOULD STITCH WITH STARTNODE");
+ cy.add(edgeToBeAdded);
+ }
+
+ if (nodedata.app_name === "Shuffle Tools") {
+ const iconInfo = GetIconInfo(nodedata);
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+ nodedata.large_image = svgpin_Url;
+ nodedata.fillGradient = iconInfo.fillGradient;
+ nodedata.fillstyle = "solid";
+ if (
+ nodedata.fillGradient !== undefined &&
+ nodedata.fillGradient !== null &&
+ nodedata.fillGradient.length > 0
+ ) {
+ nodedata.fillstyle = "linear-gradient";
+ } else {
+ nodedata.iconBackground = iconInfo.iconBackgroundColor;
+ }
+ }
+
+
+ if (
+ nodedata.parameters !== undefined &&
+ nodedata.parameters !== null &&
+ !nodedata.label.endsWith("_copy")
+ ) {
+ var newparameters = [];
+
+ for (let [subkey,subkeyval] in Object.entries(nodedata.parameters)) {
+ var newparam = JSON.parse(
+ JSON.stringify(nodedata.parameters[subkey])
+ );
+ newparam.id = uuidv4();
+
+ if (newparam.value === undefined || newparam.value === null) {
+ newparam.value = "";
+ } else {
+ newparam.value = newparam.value;
+ }
+
+ newparameters.push(newparam);
+ }
+
+ nodedata.parameters = newparameters;
+ }
+
+ if (workflow.actions === undefined || workflow.actions === null) {
+ workflow.actions = [nodedata];
+ } else {
+ workflow.actions.push(nodedata);
+ }
+
+ setWorkflow(workflow);
+ } else if (nodedata.type === "TRIGGER") {
+ if (nodedata.is_valid === false) {
+ alert.info("This trigger is not available to you");
+ node.remove();
+ return;
+ }
+
+ if (workflow.triggers === undefined) {
+ workflow.triggers = [nodedata];
+ } else {
+ workflow.triggers.push(nodedata);
+ }
+
+ const newEdgeUuid = uuidv4();
+ const newcybranch = {
+ source: nodedata.id,
+ target: workflow.start,
+ source_id: nodedata.id,
+ destination_id: workflow.start,
+ _id: newEdgeUuid,
+ id: newEdgeUuid,
+ hasErrors: false,
+ decorator: false,
+ };
+
+ const edgeToBeAdded = {
+ group: "edges",
+ data: newcybranch,
+ };
+
+ if (
+ nodedata.name !== "User Input" &&
+ nodedata.name !== "Shuffle Workflow"
+ ) {
+ if (
+ workflow.actions !== undefined &&
+ workflow.actions !== null &&
+ workflow.actions.length > 0
+ ) {
+ cy.add(edgeToBeAdded);
+ }
+ }
+
+ setWorkflow(workflow);
+ }
+
+ if (nodedata.app_name !== undefined) {
+ history.push({
+ type: "node",
+ action: "added",
+ data: nodedata,
+ });
+ setHistory(history);
+ setHistoryIndex(history.length);
+ }
+ };
+
+ const onEdgeRemoved = (event) => {
+ setLastSaved(false);
+
+ const edge = event.target;
+ if (edge.data("decorator") === true) {
+ return;
+ }
+
+ // Check if the source is trigger and can start
+ console.log("Removed: ", edge.data())
+ const allNodes = cy.nodes().jsons()
+ for (let nodekey in allNodes) {
+ const curnode = allNodes[nodekey]
+ if (curnode.data.type !== "TRIGGER") {
+ continue
+ }
+
+ if (curnode.data.id === edge.data("source")) {
+ console.log("Found matching trigger source: ", curnode)
+ if (curnode.data.app_name !== "Shuffle Workflow" && curnode.data.app_name !== "User Input") {
+ // If it's started, READD the edge
+ if (curnode.data.status === "running") {
+ console.log("Edge is running - readd it: ", edge.data())
+
+ // Just making sure it's not running infinitely
+ var newdata = edge.data()
+ newdata.readded = true
+
+ try {
+ cy.add({
+ group: "edges",
+ data: newdata,
+ })
+
+ alert.error("You must STOP the trigger before deleting its branches")
+ } catch (e) {
+ console.log("Failed re-adding edge: ", e)
+ }
+ }
+
+ //status: "uninitialized",
+ }
+ }
+ }
+
+ workflow.branches = workflow.branches.filter(
+ (a) => a.id !== edge.data().id
+ );
+ setWorkflow(workflow);
+ event.target.remove();
+
+ // trigger as source check
+ const indexcheck = workflow.triggers.findIndex(
+ (data) => edge.data("source") === data.id
+ );
+ if (indexcheck !== -1) {
+ console.log("Shouldnt remove edge from trigger? ");
+ }
+
+ if (edge.data().source !== undefined) {
+ history.push({
+ type: "edge",
+ action: "removed",
+ data: edge.data(),
+ });
+
+ setHistory(history);
+ setHistoryIndex(history.length);
+ }
+ };
+
+ const onNodeRemoved = (event) => {
+ const node = event.target;
+ const data = node.data();
+
+ if (data.finished === false) {
+ return
+ }
+
+ workflow.actions = workflow.actions.filter((a) => a.id !== data.id);
+ workflow.triggers = workflow.triggers.filter((a) => a.id !== data.id);
+ if (workflow.start === data.id && workflow.actions.length > 0) {
+ // FIXME - should check branches connected to startnode, as picking random
+ // is just confusing
+ if (workflow.actions[0].id !== data.id) {
+ const ele = cy.getElementById(workflow.actions[0].id);
+ if (ele !== undefined && ele !== null) {
+ ele.data("isStartNode", true);
+ workflow.start = ele.id();
+ }
+ } else {
+ if (workflow.actions.length > 1) {
+ const ele = cy.getElementById(workflow.actions[1].id);
+ if (ele !== undefined && ele !== null) {
+ ele.data("isStartNode", true);
+ workflow.start = ele.id();
+ }
+ }
+ }
+ }
+
+ if (data.app_name !== undefined) {
+ const allNodes = cy.nodes().jsons();
+ for (let allNodesKey in allNodes) {
+ const currentNode = allNodes[allNodesKey];
+ if (currentNode.data.attachedTo === data.id) {
+ cy.getElementById(currentNode.data.id).remove();
+ }
+ }
+
+ history.push({
+ type: "node",
+ action: "removed",
+ data: data,
+ })
+
+ //console.log("REMOVED: ", data)
+ setHistory(history);
+ setHistoryIndex(history.length);
+ }
+
+ setWorkflow(workflow);
+ if (data.type === "TRIGGER") {
+ saveWorkflow(workflow);
+ }
+ };
+
+ //var previouskey = 0
+ const handleKeyDown = (event) => {
+ switch (event.keyCode) {
+ case 27:
+ if (configureWorkflowModalOpen === true) {
+ setConfigureWorkflowModalOpen(false);
+ }
+ break;
+ case 46:
+ console.log("DELETE");
+ break;
+ case 38:
+ //console.log("UP");
+ break;
+ case 37:
+ //console.log("LEFT");
+ break;
+ case 40:
+ //console.log("DOWN");
+ break;
+ case 39:
+ //console.log("RIGHT");
+ break;
+ case 90:
+ if (event.ctrlKey) {
+ console.log("CTRL+Z");
+ }
+
+ break;
+ case 67:
+ if (event.ctrlKey && !event.shiftKey) {
+ if (
+ event.path !== undefined &&
+ event.path !== null &&
+ event.path.length > 0
+ ) {
+ if (event.path[0].localName !== "body") {
+ console.log("Skipping because body is not targeted");
+ return;
+ }
+ }
+
+ if (
+ event.target !== undefined &&
+ event.target !== null
+ ) {
+ if (event.target.localName !== "body") {
+ console.log("Skipping because body is not targeted")
+ return;
+ }
+ }
+
+ console.log("CTRL+C");
+ if (cy !== undefined) {
+ var cydata = cy.$(":selected").jsons();
+ if (cydata !== undefined && cydata !== null && cydata.length > 0) {
+ console.log(cydata);
+
+ const elementName = "copy_element_shuffle";
+ var copyText = document.getElementById(elementName);
+ if (copyText !== null && copyText !== undefined) {
+ const clipboard = navigator.clipboard;
+ if (clipboard === undefined) {
+ alert.error("Can only copy over HTTPS (port 3443)");
+ return;
+ }
+
+ navigator.clipboard.writeText(JSON.stringify(cydata));
+ copyText.select();
+ copyText.setSelectionRange(0, 99999); /* For mobile devices */
+ document.execCommand("copy");
+ alert.success(`Copied ${cydata.length} element(s)`);
+ }
+ }
+ }
+ }
+ break;
+ case 86:
+ if (event.ctrlKey) {
+ //console.log("CTRL+V")
+ // The below parts are handled in the function handlePaste()
+ /*
+ const clipboard = navigator.clipboard
+ if (clipboard === undefined || window === undefined || window === null) {
+ alert.error("Can only use cliboard over HTTPS (port 3443)")
+ return
+ }
+
+ console.log("CLIPBOARD: ", window.clipboardData)
+ const pastedData = window.clipboardData.getData('Text');
+ console.log("PASTED: ", pastedData)
+
+
+ //var tmpAuth = JSON.parse(JSON.stringify(appAuthentication))
+ var jsonvalid = true
+ var parsedjson = []
+ */
+ }
+ break;
+ case 88:
+ if (event.ctrlKey) {
+ console.log("CTRL+X");
+ }
+ break;
+ case 83:
+ break;
+ case 70:
+ break;
+ case 65:
+ // As a poweruser myself, I found myself hitting this a few
+ // too many times to just edit text. Need a better bind, which does NOT work while inside a field
+ break;
+ default:
+ break;
+ }
+
+ //previouskey = event.keyCode
+ };
+
+ const handlePaste = (event) => {
+ //console.log("EV: ", event)
+ if (
+ event.path !== undefined &&
+ event.path !== null &&
+ event.path.length > 0
+ ) {
+ //console.log("PATH: ", event.path[0])
+ if (event.path[0].localName !== "body") {
+ //console.log("Skipping because body is not targeted")
+ return;
+ }
+ }
+
+ //console.log("PATH2: ", event.target)
+ if (
+ event.target !== undefined &&
+ event.target !== null
+ ) {
+ if (event.target.localName !== "body") {
+ //console.log("Skipping because body is not targeted")
+ return;
+ }
+ }
+
+
+ event.preventDefault();
+ const clipboard = (event.originalEvent || event).clipboardData.getData(
+ "text/plain"
+ );
+ //console.log("Text: ", clipboard)
+ //window.document.execCommand('insertText', false, text);
+ //
+ try {
+ const parsedjson = JSON.parse(clipboard);
+ //console.log("Parsed: ", parsedjson)
+
+ for (let jsonkey in parsedjson) {
+ const item = parsedjson[jsonkey];
+ console.log("Adding: ", item);
+ item.data.id = uuidv4()
+
+ cy.add({
+ group: item.group,
+ data: item.data,
+ position: item.position,
+ });
+ }
+ } catch (e) {
+ console.log("Error pasting: ", e);
+ //alert.info("Failed parsing clipboard: ", e)
+ }
+ };
+
+ const registerKeys = () => {
+ document.addEventListener("keydown", handleKeyDown);
+ document.addEventListener("paste", handlePaste);
+ };
+
+ const getEnvironments = () => {
+ fetch(globalUrl + "/api/v1/getenvironments", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for envs :O!");
+ if (isCloud) {
+ setEnvironments([{ Name: "Cloud", Type: "cloud" }]);
+ } else {
+ setEnvironments([{ Name: "Onprem", Type: "onprem" }]);
+ }
+
+ return;
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ var found = false;
+ var showEnvCnt = 0;
+ for (let jsonkey in responseJson) {
+ if (responseJson[jsonkey].default && !found) {
+ setDefaultEnvironmentIndex(jsonkey);
+ found = true;
+ }
+
+ if (responseJson[jsonkey].archived === false) {
+ showEnvCnt += 1;
+ }
+ }
+
+ if (showEnvCnt > 1) {
+ setShowEnvironment(true);
+ }
+
+ if (!found) {
+ for (let jsonkey in responseJson) {
+ if (!responseJson[jsonkey].archived) {
+ setDefaultEnvironmentIndex(jsonkey);
+ break;
+ }
+ }
+ }
+
+ // FIXME: Don't allow multiple in cloud yet. Cloud -> Onprem isn't stable.
+ if (isCloud) {
+ if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) {
+ setEnvironments(responseJson);
+ } else {
+ setEnvironments([{ Name: "Cloud", Type: "cloud" }]);
+ }
+ } else {
+ setEnvironments(responseJson);
+ }
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("Get environments error: ", error.toString());
+ });
+ };
+
+ if (
+ !firstrequest &&
+ graphSetup &&
+ established &&
+ props.match.params.key !== workflow.id &&
+ workflow.id !== undefined &&
+ workflow.id !== null &&
+ workflow.id.length > 0
+ ) {
+ window.location.pathname = "/workflows/" + props.match.params.key;
+ }
+
+ const animationDuration = 150;
+ const onNodeHoverOut = (event) => {
+ // console.log("Hover out: ", event.target.data());
+
+ const nodedata = event.target.data();
+ // console.log("nodedata", nodedata);
+ // console.log("nodedata.app_name: ", nodedata.app_name);
+ if (nodedata.app_name !== undefined) {
+
+ const allNodes = cy.nodes().jsons();
+ // console.log("allNodes: ", allNodes)
+ for (var nodekey in allNodes) {
+ const currentNode = allNodes[nodekey];
+ // console.log("Current node: ", currentNode);
+ if (
+ currentNode.data.isButton &&
+ currentNode.data.attachedTo !== nodedata.id
+ ) {
+ cy.getElementById(currentNode.data.id).remove();
+ }
+
+ }
+ }
+
+ const cytoscapeElement = document.getElementById("cytoscape_view")
+ if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
+ cytoscapeElement.style.cursor = "default"
+ }
+
+ // Skipping node editing if it's the selected one
+ if (cy !== undefined) {
+ const typeIds = cy.elements('node:selected').jsons();
+ for (var idkey in typeIds) {
+ const item = typeIds[idkey]
+ if (item.data.id === nodedata.id) {
+ return
+ }
+ }
+ }
+ //if (nodedata.id === selectedAction.id || nodedata.id === selectedTrigger.id) {
+ // return
+ //}
+
+ var parsedStyle = {
+ "border-width": "1px",
+ "font-size": "18px",
+ //"cursor": "default",
+ }
+
+ if ((nodedata.app_name === "Testing" || nodedata.app_name === "Shuffle Tools") && !nodedata.isStartNode) {
+ parsedStyle = {
+ "border-width": "1px",
+ "font-size": "0px",
+ };
+ }
+
+ event.target.animate(
+ {
+ style: parsedStyle,
+ },
+ {
+ duration: animationDuration,
+ }
+ );
+
+ const outgoingEdges = event.target.outgoers("edge");
+ const incomingEdges = event.target.incomers("edge");
+ if (outgoingEdges.length > 0) {
+ outgoingEdges.removeClass("hover-highlight");
+ }
+
+ if (incomingEdges.length > 0) {
+ outgoingEdges.removeClass("hover-highlight");
+ }
+ };
+
+ const buttonColor = "rgba(255,255,255,0.9)";
+ const buttonBackgroundColor = "#1f2023";
+ const addStartnodeButton = (event) => {
+ var parentNode = cy.$("#" + event.target.data("id"));
+ if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
+
+ if (parentNode.data("isStartNode")) {
+ return;
+ }
+
+ const px = parentNode.position("x") - 65;
+ const py = parentNode.position("y") - 45;
+ const circleId = (newNodeId = uuidv4());
+
+ parentNode.data("circleId", circleId);
+
+ const iconInfo = {
+ icon: "M9.4 2H15V12H8L7.6 10H2V17H0V0H9L9.4 2ZM9 10H11V8H13V6H11V4H9V6L8 4V2H6V4H4V2H2V4H4V6H2V8H4V6H6V8H8V6L9 8V10ZM6 6V4H8V6H6ZM9 6H11V8H9V6Z",
+ iconColor: buttonColor,
+ iconBackgroundColor: buttonBackgroundColor,
+ };
+
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ cy.add({
+ group: "nodes",
+ data: {
+ weight: 30,
+ id: circleId,
+ name: "TEEEXT",
+ isButton: true,
+ buttonType: "set_startnode",
+ attachedTo: event.target.data("id"),
+ icon: svgpin_Url,
+ iconBackground: iconInfo.iconBackgroundColor,
+ is_valid: true,
+ },
+ position: { x: px, y: py },
+ locked: true,
+ });
+ };
+
+ const addCopyButton = (event) => {
+ var parentNode = cy.$("#" + event.target.data("id"));
+ if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
+
+ const px = parentNode.position("x") - 65;
+ const py = parentNode.position("y") - 5;
+ const circleId = (newNodeId = uuidv4());
+
+ parentNode.data("circleId", circleId);
+
+ 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,
+ iconBackgroundColor: buttonBackgroundColor,
+ };
+
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ cy.add({
+ group: "nodes",
+ data: {
+ weight: 30,
+ id: circleId,
+ name: "TEEEXT",
+ isButton: true,
+ buttonType: "copy",
+ attachedTo: event.target.data("id"),
+ icon: svgpin_Url,
+ iconBackground: iconInfo.iconBackgroundColor,
+ is_valid: true,
+ },
+ position: { x: px, y: py },
+ locked: true,
+ });
+ };
+
+ const addSuggestionButtons = (event) => {
+ var parentNode = cy.$("#" + event.target.data("id"));
+ if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
+
+ const px = parentNode.position("x") + 300;
+ const py = parentNode.position("y") + 100;
+ const circleId = (newNodeId = uuidv4());
+
+ parentNode.data("circleId", circleId);
+
+ var appid = "1234"
+ var suggestions = [{
+ app_name: "TheHive",
+ app_version: "1.1.0",
+ app_id: appid,
+ sharing: false,
+ private_id: false,
+ isStartNode: false,
+ label: "Suggestion 1",
+ large_image: "data:image/png;base64,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",
+ finished: false,
+ is_valid: true,
+ isSuggestion: true,
+ attachedTo: event.target.data("id"),
+ }]
+ //isButton: true,
+
+ cy.add({
+ group: "nodes",
+ data: suggestions[0],
+ position: { x: px, y: py },
+ locked: true,
+ });
+
+ //suggestions[0].id = uuidv4()
+ //cy.add({
+ // group: "nodes",
+ // data: suggestions[0],
+ // position: { x: parentNode.position("x") + 300, y: parentNode.position("y") - 100},
+ // locked: true,
+ //});
+ }
+
+ const addDeleteButton2 = (event) => {
+ var parentNode = cy.$("#" + event.target.data("id"));
+ if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
+
+ const px = parentNode.position("x") + 100;
+ const py = parentNode.position("y") + 35;
+ const circleId = (newNodeId = uuidv4());
+
+ parentNode.data("circleId", circleId);
+
+ const iconInfo = {
+ icon: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z",
+ iconColor: buttonColor,
+ iconBackgroundColor: buttonBackgroundColor,
+ };
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ cy.add({
+ group: "nodes",
+ data: {
+ weight: 30,
+ id: circleId,
+ name: "This is autocomplete",
+ buttonType: "delete",
+ attachedTo: event.target.data("id"),
+ icon: svgpin_Url,
+ iconBackground: iconInfo.iconBackgroundColor,
+ is_valid: true,
+ },
+ position: { x: px, y: py },
+ locked: true,
+ });
+ };
+
+ const addDeleteButton = (event) => {
+ var parentNode = cy.$("#" + event.target.data("id"));
+ if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
+
+ const px = parentNode.position("x") - 65;
+ const py = parentNode.position("y") + 35;
+ const circleId = (newNodeId = uuidv4());
+
+ parentNode.data("circleId", circleId);
+
+ const iconInfo = {
+ icon: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z",
+ iconColor: buttonColor,
+ iconBackgroundColor: buttonBackgroundColor,
+ };
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ cy.add({
+ group: "nodes",
+ data: {
+ weight: 30,
+ id: circleId,
+ name: "TEEEXT",
+ isButton: true,
+ buttonType: "delete",
+ attachedTo: event.target.data("id"),
+ icon: svgpin_Url,
+ iconBackground: iconInfo.iconBackgroundColor,
+ is_valid: true,
+ },
+ position: { x: px, y: py },
+ locked: true,
+ });
+ };
+
+ const onNodeHover = (event) => {
+ const nodedata = event.target.data();
+
+ if (nodedata.finished === false) {
+ console.log("NODE: ", nodedata)
+ return;
+ }
+
+ const cytoscapeElement = document.getElementById("cytoscape_view")
+ if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
+ cytoscapeElement.style.cursor = "pointer"
+ }
+
+ //var parentNode = cy.$("#" + event.target.data("id"));
+ //if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
+
+ if (nodedata.app_name !== undefined && !workflow.public === true) {
+ const allNodes = cy.nodes().jsons();
+
+ var found = false;
+ for (var _key in allNodes) {
+ const currentNode = allNodes[_key];
+ // console.log("CURRENT NODE: ", currentNode)
+ if (
+ (currentNode.data.isButton || currentNode.data.isSuggestion) &&
+ currentNode.data.attachedTo !== nodedata.id
+ ) {
+ cy.getElementById(currentNode.data.id).remove();
+ }
+
+ /*if (
+ currentNode.data.isSuggestion &&
+ currentNode.data.attachedTo !== nodedata.id
+ ) {
+ cy.getElementById(currentNode.data.id).remove();
+ }*/
+
+ if (
+ currentNode.data.isButton &&
+ currentNode.data.attachedTo === nodedata.id
+ ) {
+ found = true;
+ }
+ }
+
+ if (!found) {
+ addDeleteButton(event);
+
+ if (nodedata.type === "TRIGGER") {
+ if (nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT") {
+ addCopyButton(event);
+ }
+ } else {
+ addCopyButton(event);
+ addStartnodeButton(event);
+ }
+
+ // autocomplete
+ // right click
+ // suggestions
+ //addSuggestionButtons(event);
+ }
+ }
+
+ var parsedStyle = {
+ "border-width": "7px",
+ "border-opacity": ".7",
+ "font-size": "25px",
+ //"cursor": "pointer",
+ }
+
+ const typeIds = cy.elements('node:selected').jsons();
+ for (var idkey in typeIds) {
+ const item = typeIds[idkey]
+ if (item.data.id === nodedata.id) {
+ //console.log("items: ", item.data.id, nodedata.id)
+ parsedStyle["border-width"] = "12px"
+ break
+ }
+ }
+
+ if (nodedata.type !== "COMMENT") {
+ parsedStyle.color = "white";
+
+ //if (!event.target.data("isButton") && !event.target.data("buttonId")) {
+ // const px = event.target.position("x") - 0;
+ // const py = event.target.position("y") - 50;
+ // const circleId = (newNodeId = uuidv4());
+
+ // console.log("Got px, py: ", px, py)
+ //
+ // cy.add({
+ // group: "nodes",
+ // data: {
+ // weight: 30,
+ // id: circleId,
+ // isButton: true,
+ // attachedTo: event.target.data("id"),
+ // buttonType: "edgehandler",
+ // is_valid: true,
+ // },
+ // position: { x: px, y: py },
+ // locked: true,
+ // })
+ //}
+ }
+
+ if (event.target !== undefined && event.target !== null) {
+ event.target.animate(
+ {
+ style: parsedStyle,
+ },
+ {
+ duration: animationDuration,
+ }
+ );
+ }
+
+ const outgoingEdges = event.target.outgoers("edge");
+ const incomingEdges = event.target.incomers("edge");
+ if (outgoingEdges.length > 0) {
+ outgoingEdges.addClass("hover-highlight");
+ }
+
+ if (incomingEdges.length > 0) {
+ outgoingEdges.addClass("hover-highlight");
+ }
+ };
+
+ const onEdgeHoverOut = (event) => {
+ if (event === null || event === undefined) {
+ event.target.removeStyle();
+ return;
+ }
+
+ const edgeData = event.target.data();
+ if (edgeData.decorator === true) {
+ return;
+ }
+
+ const cytoscapeElement = document.getElementById("cytoscape_view")
+ if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
+ cytoscapeElement.style.cursor = "default"
+ }
+
+ //event.target.removeStyle();
+ };
+
+ // This is here to have a proper transition for lines
+ const onEdgeHover = (event) => {
+ if (event === null || event === undefined) {
+ return;
+ }
+
+ const edgeData = event.target.data();
+ if (edgeData.decorator === true) {
+ return;
+ }
+
+ const cytoscapeElement = document.getElementById("cytoscape_view")
+ if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
+ cytoscapeElement.style.cursor = "pointer"
+ }
+
+ //const sourcecolor = cy
+ // .getElementById(event.target.data("source"))
+ // .style("border-color");
+ //const targetcolor = cy
+ // .getElementById(event.target.data("target"))
+ // .style("border-color");
+
+ ////console.log(sourcecolor, targetcolor)
+ //if (
+ // sourcecolor !== null &&
+ // sourcecolor !== undefined &&
+ // targetcolor !== null &&
+ // targetcolor !== undefined &&
+ // !sourcecolor.includes("rgb") &&
+ // !targetcolor.includes("rgb")
+ //) {
+ // console.log(sourcecolor)
+ // console.log(targetcolor)
+
+ // if (event.target !== null && event.target.value !== null) {
+ // event.target.animate({
+ // style: {
+ // "target-arrow-color": targetcolor,
+ // "line-fill": "linear-gradient",
+ // "line-gradient-stop-colors": [sourcecolor, targetcolor],
+ // "line-gradient-stop-positions": [0, 1],
+ // },
+ // duration: animationDuration,
+ // })
+ // } else {
+ // event.target.animate({
+ // style: {
+ // "target-arrow-color": targetcolor,
+ // "line-fill": "linear-gradient",
+ // "line-gradient-stop-colors": ["#41dcab", "#41dcab"],
+ // "line-gradient-stop-positions": [0, 1],
+ // },
+ // duration: animationDuration,
+ // })
+
+ // }
+ //}
+
+ if (event.target !== undefined && event.target !== null) {
+ //const targetcolor = "#66a8b1"
+ //const parsedStyle = {
+ // "width": "10px",
+ // "font-size": "18px",
+ // "target-arrow-color": "#66a8b1",
+ // "color": "#66a8b1",
+ //}
+
+ //event.target.addClass("shuffle-hover-highlight");
+
+ //console.log("Style1: ", event.target)
+ //console.log("Style: ", event.target.style())
+
+ //event.target.animate(
+ // {
+ // style: parsedStyle,
+ // },
+ // {
+ // duration: animationDuration,
+ // }
+ //)
+ }
+ }
+
+ // Calculates how a trigger should curve
+ // Thanks to:
+ // https://codepen.io/guillaumethomas/pen/xxbbBKO
+ const calculateEdgeCurve = (sourcenodePosition, destinationnodePosition) => {
+ const xParsed = destinationnodePosition.x - sourcenodePosition.x
+ const yParsed = destinationnodePosition.y - sourcenodePosition.y
+
+ const z = Math.sqrt(xParsed * xParsed + yParsed * yParsed);
+ const costheta = xParsed / z;
+ const alpha = 0.25;
+ var controlPointDistance = [-alpha * yParsed * costheta, alpha * yParsed * costheta];
+ var controlPointWeight = [alpha, 1 - alpha]
+
+ //'control-point-weight': ['0.33', '0.66'],
+ //var controlPointWeight = ["0.33", "0.66"]
+ //var controlPointDistance = ["33%", "-66%"]
+ //var controlPointWeight = ["0.00", "1.00"]
+ /*
+ if (yParsed !== 0) {
+ //const degreeFound = Math.atan2(xParsed / yParsed)
+ const degreeFound = Math.atan2(xParsed, yParsed) * 180 / Math.PI
+
+ if (degreeFound > 90 && degreeFound < 180) {
+ console.log("TOPRIGHT")
+ } else if (degreeFound < 90 && degreeFound > 0) {
+ console.log("BOTTOMRIGHT")
+ } else if (degreeFound < 0 && degreeFound > -90) {
+ console.log("BOTTOMLEFT")
+ //controlPointWeight = ["0.20", "0.80"]
+ //controlPointWeight = "0.7"
+ //controlPointDistance = "50%"
+
+ } else if (degreeFound < -90 && degreeFound > -180) {
+ console.log("TOPLEFT")
+ } else {
+ console.log("STRAIGHT!")
+ }
+ }
+ */
+
+ return {
+ "distance": controlPointDistance,
+ "weight": controlPointWeight,
+ }
+ }
+
+ const setupGraph = () => {
+ const actions = workflow.actions.map((action) => {
+ const node = {};
+
+ if (!action.isStartNode && action.app_name === "Shuffle Tools") {
+ const iconInfo = GetIconInfo(action);
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+ action.large_image = svgpin_Url;
+ action.fillGradient = iconInfo.fillGradient;
+ action.fillstyle = "solid";
+ if (
+ action.fillGradient !== undefined &&
+ action.fillGradient !== null &&
+ action.fillGradient.length > 0
+ ) {
+ action.fillstyle = "linear-gradient";
+ } else {
+ action.iconBackground = iconInfo.iconBackgroundColor;
+ }
+ }
+
+ node.position = action.position;
+ node.data = action;
+
+ node.data.id = action["id"];
+ node.data._id = action["id"];
+ node.data.type = "ACTION";
+ node.isStartNode = action["id"] === workflow.start;
+
+ if (workflow.public === true) {
+ node.data.is_valid = true
+ node.is_valid = true
+ }
+
+ var example = "";
+ if (
+ action.example !== undefined &&
+ action.example !== null &&
+ action.example.length > 0
+ ) {
+ example = action.example;
+ }
+
+ node.data.example = example;
+
+ return node;
+ });
+
+ const decoratorNodes = workflow.actions.map((action) => {
+ if (!action.isStartNode) {
+ if (action.app_name === "Testing") {
+ return null;
+ } else if (action.app_name === "Shuffle Tools") {
+ return null;
+ }
+ }
+
+ const iconInfo = GetIconInfo(action);
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ const offset = action.isStartNode ? 36 : 44;
+ const decoratorNode = {
+ position: {
+ x: action.position.x + offset,
+ y: action.position.y + offset,
+ },
+ locked: true,
+ data: {
+ isDescriptor: true,
+ isValid: true,
+ is_valid: true,
+ label: "",
+ image: svgpin_Url,
+ imageColor: iconInfo.iconBackgroundColor,
+ attachedTo: action.id,
+ },
+ };
+ return decoratorNode;
+ });
+
+ const triggers = workflow.triggers.map((trigger) => {
+ const node = {};
+ node.position = trigger.position;
+ node.data = trigger;
+
+ node.data._id = trigger["id"];
+ node.data.id = trigger["id"];
+ node.data.type = "TRIGGER";
+
+ return node;
+ });
+
+ var comments = [];
+ if (
+ workflow.comments !== undefined &&
+ workflow.comments !== null &&
+ workflow.comments.length > 0
+ ) {
+ comments = workflow.comments.map((comment) => {
+ const node = {};
+ node.position = comment.position;
+ node.data = comment;
+
+ node.data._id = comment["id"];
+ node.data.type = "COMMENT";
+
+ return node;
+ });
+ }
+
+ // FIXME - tmp branch update
+ var insertedNodes = [].concat(actions, triggers, decoratorNodes, comments);
+ insertedNodes = insertedNodes.filter((node) => node !== null);
+
+ var edges = workflow.branches.map((branch, index) => {
+ const edge = {};
+ var conditions = workflow.branches[index].conditions;
+ if (conditions === undefined || conditions === null) {
+ conditions = [];
+ }
+
+ var label = "";
+ if (conditions.length === 1) {
+ label = conditions.length + " condition";
+ } else if (conditions.length > 1) {
+ label = conditions.length + " conditions";
+ }
+
+ edge.data = {
+ id: branch.id,
+ _id: branch.id,
+ source: branch.source_id,
+ target: branch.destination_id,
+ label: label,
+ conditions: conditions,
+ hasErrors: branch.has_errors,
+ decorator: false,
+ };
+
+ // This is an attempt at prettier edges. The numbers are weird to work with.
+ // Bezier curves
+ //http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html
+ var sourcenode = actions.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id)
+ var destinationnode = actions.find(node => node.data._id === branch.destination_id || node.data.id === branch.destination_id)
+
+ if (sourcenode === undefined) {
+ sourcenode = triggers.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id)
+ }
+
+ if (destinationnode === undefined) {
+ destinationnode = triggers.find(node => node.data._id === branch.destination_id || node.data.id === branch.destination_id)
+ }
+
+ if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) {
+ const edgeCurve = calculateEdgeCurve(sourcenode.position, destinationnode.position)
+ edge.style = {
+ 'control-point-distance': edgeCurve.distance,
+ 'control-point-weight': edgeCurve.weight,
+ }
+ } else {
+ console.log("FAILED node curve handling")
+ }
+
+ return edge;
+ });
+
+ if (
+ workflow.visual_branches !== undefined &&
+ workflow.visual_branches !== null &&
+ workflow.visual_branches.length > 0
+ ) {
+ const visualedges = workflow.visual_branches.map((branch, index) => {
+ const edge = {};
+
+ if (workflow.branches[index] === undefined) {
+ return {};
+ }
+
+ var conditions = workflow.branches[index].conditions;
+ if (conditions === undefined || conditions === null) {
+ conditions = [];
+ }
+
+ const label = "Subflow";
+ edge.data = {
+ id: branch.id,
+ _id: branch.id,
+ source: branch.source_id,
+ target: branch.destination_id,
+ label: label,
+ decorator: true,
+ };
+
+ return edge;
+ });
+
+ edges = edges.concat(visualedges);
+ }
+
+ setWorkflow(workflow);
+
+ // Verifies if a branch is valid and skips others
+ var newedges = [];
+ for (var edgeKey in edges) {
+ var item = edges[edgeKey];
+ if (item.data === undefined) {
+ continue;
+ }
+
+ const sourcecheck = insertedNodes.find(
+ (data) => data.data.id === item.data.source
+ );
+ const destcheck = insertedNodes.find(
+ (data) => data.data.id === item.data.target
+ );
+ if (sourcecheck === undefined || destcheck === undefined) {
+ continue;
+ }
+
+ newedges.push(item);
+ }
+
+ insertedNodes = insertedNodes.concat(newedges);
+ setElements(insertedNodes);
+ };
+
+ const removeNode = (nodeId) => {
+ const selectedNode = cy.getElementById(nodeId);
+ // console.log("selected node in removenode", selectedNode);
+ if (selectedNode.data() === undefined) {
+ console.log("No node to remove")
+ return;
+ }
+
+ //console.log("Removing node: ", selectedNode.data("id"), "Action: ", selectedAction.id)
+
+ // Get selected node
+
+ if (selectedNode.data().type === "TRIGGER") {
+ console.log("Should remove trigger!");
+ console.log(selectedNode.data());
+ const triggerindex = workflow.triggers.findIndex(
+ (data) => data.id === selectedNode.data().id
+ );
+
+ setSelectedTriggerIndex(triggerindex);
+ if (selectedNode.data().trigger_type === "SCHEDULE") {
+ setSelectedTrigger(selectedNode.data());
+ stopSchedule(selectedNode.data(), triggerindex);
+ } else if (selectedNode.data().trigger_type === "WEBHOOK") {
+ setSelectedTrigger(selectedNode.data());
+ deleteWebhook(selectedNode.data(), triggerindex);
+ } else if (selectedNode.data().trigger_type === "EMAIL") {
+ setSelectedTrigger(selectedNode.data());
+ stopMailSub(selectedTrigger, triggerindex);
+ }
+ }
+
+ //if (selectedNode.data("id") === selectedAction.id) {
+ // setSelectedApp({});
+ // setSelectedAction({});
+ //setSelectedTrigger({});
+ //setSelectedTriggerIndex({});
+ //}
+ const parsedSelection = cy.$(":selected");
+ if (selectedNode.data().decorator === true && selectedNode.data("type") !== "COMMENT") {
+ alert.info("This node can't be deleted.");
+ } else {
+ console.log("Deleted.")
+ selectedNode.remove();
+
+ setSelectedTrigger({})
+ setSelectedEdge({})
+ setSelectedAction({})
+ }
+
+ // An attempt at NOT unselecting when removing
+ /*
+ setTimeout(() => {
+ if (parsedSelection.data() !== undefined) {
+ if (parsedSelection.data("id") !== selectedNode.data("id")) {
+ console.log("SHOULD SELECT SINCE ID IS DIFFERENT")
+
+ parsedSelection.select()
+ }
+ }
+
+ console.log("Parsed: ", parsedSelection.data("id"), selectedNode.data("id"))
+ }, 2500)
+ */
+ };
+
+
+ if (isLoaded && setupSent === false) {
+ setSetupSent(true)
+
+ sendStreamRequest({
+ "item": "workflow",
+ "type": "enter",
+ "userid": userdata.id,
+ })
+ }
+
+ const fetchUsecases = () => {
+ fetch(globalUrl + "/api/v1/workflows/usecases", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for usecases");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success !== false) {
+ console.log("Usecases: ", usecases)
+ setUsecases(responseJson)
+ } else {
+ }
+ })
+ .catch((error) => {
+ //alert.error("ERROR: " + error.toString());
+ console.log("ERROR getting usecases: " + error.toString());
+ })
+ }
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ //useEffect(() => {
+ if (firstrequest) {
+ setFirstrequest(false);
+ getWorkflow(props.match.params.key, {});
+ getApps();
+ fetchUsecases()
+
+ const cursearch =
+ typeof window === "undefined" || window.location === undefined
+ ? ""
+ : window.location.search;
+
+ // FIXME: Don't check specific one here
+ const tmpExec = new URLSearchParams(cursearch).get("execution_highlight");
+ if (
+ tmpExec !== undefined &&
+ tmpExec !== null &&
+ tmpExec === "executions"
+ ) {
+ setExecutionModalOpen(true)
+ const newitem = removeParam("execution_highlight", cursearch);
+ navigate(curpath + newitem)
+ //props.history.push(curpath + newitem);
+ }
+
+ const tmpView = new URLSearchParams(cursearch).get("view");
+ if (
+ tmpView !== undefined &&
+ tmpView !== null &&
+ tmpView === "executions"
+ ) {
+ setExecutionModalOpen(true);
+
+ const newitem = removeParam("view", cursearch);
+ navigate(curpath + newitem)
+ //navigate(`?execution_highlight=${parsed_url}`)
+ //props.history.push(curpath + newitem);
+ }
+ return;
+ }
+
+ // App length necessary cus of cy initialization
+ if (
+ // First load - gets the workflow
+ elements.length === 0 &&
+ workflow.actions !== undefined &&
+ !graphSetup &&
+ Object.getOwnPropertyNames(workflow).length > 0
+ ) {
+
+ setGraphSetup(true);
+ setupGraph();
+ console.log("In graph setup")
+
+ // 2nd load - configures cytoscape
+ } else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && appAuthentication !== undefined) {
+ console.log("In POST graph setup!")
+
+
+ //This part has to load LAST, as it's kind of not async.
+ //This means we need everything else to happen first.
+
+ setEstablished(true);
+ // Validate if the node is just a node lol
+
+ console.log("CY grid: ", cy.gridGuide)
+
+ // https://www.npmjs.com/package/cytoscape-grid-guide
+ //
+ if (cy.gridGuide !== undefined) {
+ cy.gridGuide({
+ gridSpacing: 30,
+ guidelinesStyle: {
+ strokeStyle: "#8b7d6b", // color of geometric guidelines
+ geometricGuidelineRange: 400, // range of geometric guidelines
+ range: 100, // max range of distribution guidelines
+ minDistRange: 10, // min range for distribution guidelines
+ distGuidelineOffset: 10, // shift amount of distribution guidelines
+ horizontalDistColor: "#ff0000", // color of horizontal distribution alignment
+ verticalDistColor: "#00ff00", // color of vertical distribution alignment
+ initPosAlignmentColor: "#0000ff", // color of alignment to initial mouse location
+ lineDash: [0, 0], // line style of geometric guidelines
+ horizontalDistLine: [0, 0], // line style of horizontal distribution guidelines
+ verticalDistLine: [0, 0], // line style of vertical distribution guidelines
+ initPosAlignmentLine: [0, 0], // line style of alignment to initial mouse position
+ }
+ })
+ } else {
+ console.log("ERROR: Failed to render grid as it's unitialized")
+ }
+
+ if (cy.edgehandles !== undefined) {
+ cy.edgehandles({
+ handleNodes: (el) => {
+ if (el.isNode() &&
+ !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;
+ },
+ });
+
+ //cy.edgehandles({
+ // preview: false,
+ //})
+
+ //cy.edgehandles().enable()
+ } else {
+ console.log("ERROR: Failed to initialize edgehandler")
+ }
+ // preview: true,
+
+ cy.fit(null, 200);
+
+ cy.on("boxselect", "node", (e) => {
+ if (e.target.data("isButton") || e.target.data("isDescriptor") || e.target.data("isSuggestion")) {
+ e.target.unselect();
+ }
+
+ e.target.addClass("selected");
+ });
+
+ cy.on("boxstart", (e) => {
+ console.log("START");
+ cy.removeListener("select");
+ });
+
+ cy.on("boxend", (e) => {
+ console.log("END: ", cy)
+ var cydata = cy.$(":selected").jsons();
+ if (cydata !== undefined && cydata !== null && cydata.length > 0) {
+ alert.success(`Selected ${cydata.length} element(s). CTRL+C to copy them.`);
+ }
+ });
+
+ 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));
+
+ document.title = "Workflow - " + workflow.name;
+ registerKeys();
+ }
+ //})
+
+ const stopSchedule = (trigger, triggerindex) => {
+ fetch(
+ `${globalUrl}/api/v1/workflows/${props.match.params.key}/schedule/${trigger.id}`,
+ {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for stream results :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ // No matter what, it's being stopped.
+ if (!responseJson.success) {
+ if (responseJson.reason !== undefined) {
+ alert.error("Failed to stop schedule: " + responseJson.reason);
+ }
+ } else {
+ alert.success("Successfully stopped schedule");
+ }
+
+ workflow.triggers[triggerindex].status = "stopped";
+ trigger.status = "stopped";
+ setSelectedTrigger(trigger);
+ setWorkflow(workflow);
+ saveWorkflow(workflow);
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("Stop schedule error: ", error.toString());
+ });
+ };
+
+ const submitSchedule = (trigger, triggerindex) => {
+ if (trigger.name.length <= 0) {
+ alert.error("Error: name can't be empty");
+ return;
+ }
+
+ var mappedStartnode = ""
+ const alledges = cy.edges().jsons()
+ if (alledges !== undefined && alledges !== null && alledges.length > 0) {
+ for (let edgekey in alledges) {
+ const tmp = alledges[edgekey]
+ console.log("TMP: ", tmp, tmp.data.source)
+ if (tmp.data.source === trigger.id) {
+ mappedStartnode = tmp.data.target
+ break
+ }
+ }
+ }
+
+ alert.info("Creating schedule with name " + trigger.name);
+ const data = {
+ name: trigger.name,
+ frequency: workflow.triggers[triggerindex].parameters[0].value,
+ execution_argument: workflow.triggers[triggerindex].parameters[1].value,
+ environment: workflow.triggers[triggerindex].environment,
+ id: trigger.id,
+ start: mappedStartnode,
+ }
+
+ fetch(
+ `${globalUrl}/api/v1/workflows/${props.match.params.key}/schedule`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(data),
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for stream results :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (!responseJson.success) {
+ alert.error("Failed to set schedule: " + responseJson.reason);
+ } else {
+ alert.success("Successfully created schedule");
+ workflow.triggers[triggerindex].status = "running";
+ trigger.status = "running";
+ setSelectedTrigger(trigger);
+ setWorkflow(workflow);
+ console.log("Should set the status to running and save");
+ saveWorkflow(workflow);
+ }
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("Get schedule error: ", error.toString());
+ });
+ };
+
+ const appViewStyle = {
+ marginLeft: 5,
+ marginRight: 5,
+ display: "flex",
+ flexDirection: "column",
+ minHeight: isMobile ? bodyHeight - appBarSize * 4 : "100%",
+ maxHeight: isMobile ? bodyHeight - appBarSize * 4 : "100%",
+ };
+
+ const paperAppStyle = {
+ borderRadius: theme.palette.borderRadius,
+ minHeight: isMobile ? 50 : 100,
+ maxHeight: isMobile ? 50 : 100,
+ minWidth: isMobile ? 50 : "100%",
+ maxWidth: isMobile ? 50 : "100%",
+ marginTop: "5px",
+ color: "white",
+ backgroundColor: surfaceColor,
+ cursor: "pointer",
+ display: "flex",
+ };
+
+ const paperVariableStyle = {
+ borderRadius: theme.palette.borderRadius,
+ minHeight: 50,
+ maxHeight: 50,
+ minWidth: "100%",
+ maxWidth: "100%",
+ marginTop: "5px",
+ color: "white",
+ backgroundColor: surfaceColor,
+ cursor: "pointer",
+ display: "flex",
+ };
+
+ const VariablesView = () => {
+ const [open, setOpen] = React.useState(false);
+ const [anchorEl, setAnchorEl] = React.useState(null);
+
+ const menuClick = (event) => {
+ setOpen(!open);
+ setAnchorEl(event.currentTarget);
+ };
+
+ const deleteVariable = (variableIndex) => {
+ //console.log("Delete:", variableName);
+ if (workflow.workflow_variables !== undefined && workflow.workflow_variables !== null && workflow.workflow_variables.length > variableIndex) {
+ workflow.workflow_variables.splice(variableIndex, 1)
+ }
+
+ //workflow.workflow_variables = workflow.workflow_variables.filter(
+ // (data) => data.name !== variableName
+ //);
+ setWorkflow(workflow);
+ };
+
+ const deleteExecutionVariable = (variableIndex) => {
+ if (workflow.execution_variables !== undefined && workflow.execution_variables !== null && workflow.execution_variables.length > variableIndex) {
+ workflow.execution_variables.splice(variableIndex, 1)
+ }
+
+ //workflow.execution_variables = workflow.execution_variables.filter(
+ // (data) => data.name !== variableName
+ //);
+ setWorkflow(workflow);
+ };
+
+ const variableScrollStyle = {
+ margin: 15,
+ overflow: "scroll",
+ height: isMobile ? "100%" : "66vh",
+ overflowX: "auto",
+ overflowY: "auto",
+ flex: "10",
+ };
+
+ return (
+
+
+ What are{" "}
+
+ WORKFLOW variables?
+
+ {workflow.workflow_variables === null
+ ? null
+ : workflow.workflow_variables.map((variable, index) => {
+ return (
+
+
{ }}>
+
+
+
{
+ setVariableInfo({
+ "name": variable.name,
+ "description": variable.description,
+ "value": variable.value,
+ "index": index,
+ })
+ setVariablesModalOpen(true);
+ }}
+ >
+ Name: {variable.name}
+
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+ {
+ setVariablesModalOpen(true);
+ setLastSaved(false);
+ }}
+ >
+ New workflow variable
+
+
+
+ What are{" "}
+
+ EXECUTION variables?
+
+ {workflow.execution_variables === null ||
+ workflow.execution_variables === undefined
+ ? null
+ : workflow.execution_variables.map((variable, index) => {
+ return (
+
+
{ }}>
+
+
+
{
+ //setNewVariableName(variable.name);
+ setVariableInfo({
+ "name": variable.name,
+ "description": variable.description,
+ "value": variable.value,
+ "index": index,
+ })
+ setExecutionVariablesModalOpen(true);
+ }}
+ >
+ Name: {variable.name}
+
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+ {
+ setExecutionVariablesModalOpen(true);
+ setLastSaved(false);
+ }}
+ >
+ New execution variable
+
+
+
+
+ );
+ };
+
+ const handleSetTab = (event, newValue) => {
+ setCurrentView(newValue);
+ };
+
+ const HandleLeftView = () => {
+ // Defaults to apps.
+ var thisview = (
+
+ );
+ if (currentView === 1) {
+ thisview = ;
+ } else if (currentView === 2) {
+ thisview = ;
+ }
+
+ const tabStyle = {
+ maxWidth: isMobile ? leftBarSize : leftBarSize / 3,
+ minWidth: isMobile ? leftBarSize : leftBarSize / 3,
+ flex: 1,
+ textTransform: "none",
+ };
+
+ const iconStyle = {
+ marginTop: 3,
+ marginRight: 5,
+ };
+
+ const parsedHeight = isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 50
+ return (
+
+
+ {thisview}
+
+
+
+
+
+
+
+ {isMobile ? null : Apps }
+
+ }
+ style={tabStyle}
+ />
+
+
+
+
+ {isMobile ? null : Triggers }
+
+ }
+ style={tabStyle}
+ />
+
+
+
+
+ {isMobile ? null : Variables }
+
+ }
+ style={tabStyle}
+ />
+
+
+ );
+ };
+
+ const triggers = [
+ {
+ name: "Webhook",
+ type: "TRIGGER",
+ status: "uninitialized",
+ trigger_type: "WEBHOOK",
+ errors: null,
+ large_image:
+ "data:image/png;base64,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",
+ is_valid: true,
+ label: "Webhook",
+ environment: "onprem",
+ description: "Custom HTTP input",
+ long_description: "Execute a workflow with an unauthicated POST request",
+ },
+ {
+ name: "Schedule",
+ type: "TRIGGER",
+ status: "uninitialized",
+ description: "Specify time",
+ trigger_type: "SCHEDULE",
+ errors: null,
+ large_image:
+ "data:image/png;base64,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",
+ label: "Schedule",
+ is_valid: true,
+ environment: "onprem",
+ long_description: "Create a schedule based on cron",
+ },
+ {
+ name: "Shuffle Workflow",
+ type: "TRIGGER",
+ status: "uninitialized",
+ trigger_type: "SUBFLOW",
+ errors: null,
+ large_image:
+ "data:image/png;base64,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",
+ is_valid: true,
+ label: "Subflow",
+ environment: "onprem",
+ description: "Control a workflow",
+ long_description: "Execute another workflow from this workflow",
+ },
+ {
+ name: "User Input",
+ type: "TRIGGER",
+ status: "running",
+ large_image:
+ "data:image/png;base64,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",
+ description: "Wait for user input",
+ trigger_type: "USERINPUT",
+ errors: null,
+ is_valid: cloudSyncEnabled || isCloud ? true : false,
+ label: "User input",
+ environment: "cloud",
+ long_description: "Take user input to continue execution",
+ },
+ {
+ name: "Office365",
+ type: "TRIGGER",
+ status: "uninitialized",
+ description: "O365 email trigger",
+ trigger_type: "EMAIL",
+ errors: null,
+ is_valid: isCloud ? true : false,
+ label: "Email",
+ environment: "cloud",
+ large_image:
+ "data:image/jpg;base64,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",
+ long_description: "Execute a workflow when you get an email",
+ },
+ {
+ name: "Gmail",
+ type: "TRIGGER",
+ status: "uninitialized",
+ description: "Gmail email trigger",
+ trigger_type: "EMAIL",
+ errors: null,
+ is_valid: isCloud ? true : false,
+ label: "Email",
+ environment: "cloud",
+ large_image:
+ "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAzFodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QTIyMjgyMEYwMDJDMTFFQkJBOEE5OUJBM0MzMTA2RDIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QTIyMjgyMTAwMDJDMTFFQkJBOEE5OUJBM0MzMTA2RDIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozQTMwMDQxRTAwMEUxMUVCQkE4QTk5QkEzQzMxMDZEMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBMjIyODIwRTAwMkMxMUVCQkE4QTk5QkEzQzMxMDZEMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAwICAwMCAwMDAwMEBwUEBAQECQYHBQcKCQsL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",
+ long_description: "Execute a workflow when you get an email",
+ },
+ ];
+
+ const TriggersView = () => {
+ const triggersViewStyle = {
+ marginLeft: "10px",
+ marginRight: "10px",
+ display: "flex",
+ flexDirection: "column",
+ };
+
+ // Predefined hurr
+
+ return (
+
+
+ {triggers.map((trigger, index) => {
+ var imageline =
+ trigger.large_image.length === 0 ? (
+
+ ) : (
+
+ );
+
+ const color = trigger.is_valid ? green : yellow;
+ return (
+
{
+ 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) {
+ alert.error("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 appScrollStyle = {
+ overflow: "scroll",
+ maxHeight: isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 55 - 50,
+ minHeight: isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 55 - 50,
+ marginTop: 1,
+ overflowY: "auto",
+ overflowX: "hidden",
+ };
+
+ const handleAppDrag = (e, app) => {
+ const cycontainer = cy.container();
+
+ //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.length === 0
+ ) {
+ alert.error(
+ "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 = ""
+
+ if (
+ app.actions[0].parameters !== undefined &&
+ app.actions[0].parameters !== null &&
+ app.actions[0].parameters.length > 0
+ ) {
+ parameters = app.actions[0].parameters;
+ }
+
+ if (
+ app.actions[0].returns !== undefined &&
+ app.actions[0].returns !== null &&
+ app.actions[0].returns.example !== undefined &&
+ app.actions[0].returns.example !== null &&
+ app.actions[0].returns.example.length > 0
+ ) {
+ example = app.actions[0].returns.example;
+ }
+
+ if (
+ app.actions[0].description !== undefined &&
+ app.actions[0].description !== null &&
+ app.actions[0].description.length > 0
+ ) {
+ description = app.actions[0].description
+ }
+
+ const parsedEnvironments =
+ environments === null || environments === []
+ ? "cloud"
+ : environments[defaultEnvironmentIndex] === undefined
+ ? "cloud"
+ : environments[defaultEnvironmentIndex].Name;
+
+ // activated: app.generated === true ? app.activated === false ? false : true : true,
+ const newAppData = {
+ name: app.actions[0].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,
+ 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
+
+ // 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;
+ //extraApps,
+ const [visibleApps, setVisibleApps] = React.useState(
+ Array.prototype.concat.apply(
+ prioritizedApps,
+ filteredApps.filter((innerapp) => !internalIds.includes(innerapp.id)),
+ )
+ );
+
+ var delay = -75
+ var runDelay = false
+
+ const ParsedAppPaper = (props) => {
+ const app = props.app;
+ const [hover, setHover] = React.useState(false);
+
+ if (app.id === "" || app.name === "") {
+ return null
+ }
+
+ const maxlen = 24;
+ var newAppname = app.name;
+ newAppname = newAppname.charAt(0).toUpperCase() + newAppname.substring(1);
+ if (newAppname.length > maxlen) {
+ newAppname = newAppname.slice(0, maxlen) + "..";
+ }
+
+ newAppname = newAppname.replaceAll("_", " ");
+
+ if (app.large_image === undefined || app.large_image === null || app.large_image === "") {
+ app.large_image = theme.palette.defaultImage
+ }
+
+ const image = app.large_image !== undefined && app.large_image !== null && app.large_image !== "" ? app.large_image : theme.palette.defaultImage
+
+ const newAppStyle = JSON.parse(JSON.stringify(paperAppStyle));
+ const pixelSize = !hover ? "2px" : "4px";
+ //newAppStyle.borderLeft = app.is_valid && app.actions !== null && app.actions !== undefined && app.actions.length > 0 && !(app.activated && app.generated)
+ newAppStyle.borderLeft = app.is_valid && app.actions !== null && app.actions !== undefined && app.actions.length > 0
+ ? `${pixelSize} solid ${green}`
+ : `${pixelSize} solid ${yellow}`;
+
+ if (!app.activated && app.generated) {
+ newAppStyle.borderLeft = `${pixelSize} solid ${yellow}`;
+ }
+
+ return (
+ {
+ handleAppDrag(e, app);
+ }}
+ onStop={(e) => {
+ handleDragStop(e, app);
+ }}
+ key={app.id}
+ dragging={false}
+ position={{
+ x: 0,
+ y: 0,
+ }}
+ >
+ {
+ setHover(true);
+ }}
+ onMouseOut={() => {
+ setHover(false);
+ }}
+ onClick={() => {
+ if (isMobile) {
+ newNodeId = uuidv4();
+ const actionType = "ACTION";
+ const actionLabel = getNextActionName(app.name);
+ var parameters = null;
+ var example = "";
+ var description = ""
+
+ if (
+ app.actions[0].parameters !== null &&
+ app.actions[0].parameters.length > 0
+ ) {
+ parameters = app.actions[0].parameters;
+ }
+
+ if (
+ app.actions[0].returns.example !== undefined &&
+ app.actions[0].returns.example !== null &&
+ app.actions[0].returns.example.length > 0
+ ) {
+ example = app.actions[0].returns.example;
+ }
+
+ if (
+ app.actions[0].description !== undefined &&
+ app.actions[0].description !== null &&
+ app.actions[0].description.length > 0
+ ) {
+ description = app.actions[0].description
+ }
+
+ const parsedEnvironments =
+ environments === null || environments === []
+ ? "cloud"
+ : environments[defaultEnvironmentIndex] === undefined
+ ? "cloud"
+ : environments[defaultEnvironmentIndex].Name;
+
+ // activated: app.generated === true ? app.activated === false ? false : true : true,
+ const newAppData = {
+ 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,
+ label: actionLabel,
+ type: actionType,
+ name: app.actions[0].name,
+ parameters: parameters,
+ isStartNode: false,
+ large_image: image,
+ run_magic_output: false,
+ authentication: [],
+ execution_variable: undefined,
+ example: example,
+ category:
+ app.categories !== null &&
+ app.categories !== undefined &&
+ app.categories.length > 0
+ ? app.categories[0]
+ : "",
+ authentication_id: "",
+ finished: false,
+ };
+
+ const nodeToBeAdded = {
+ group: "nodes",
+ data: newAppData,
+ renderedPosition: {
+ x: 100,
+ y: 100,
+ },
+ };
+
+ parsedApp = nodeToBeAdded;
+ cy.add(nodeToBeAdded);
+
+ }
+ }}
+ >
+
+
+
+
+ {isMobile ? null :
+
+
+
+ {newAppname}
+
+
+
+
+ Version: {app.app_version}
+
+
+
+
+ {app.description}
+
+
+
+ }
+
+
+
+ );
+ };
+
+ const runSearch = (value) => {
+ if (value.length > 0) {
+ var newApps = allApps.filter(
+ (app) =>
+ app.name.toLowerCase().includes(value.trim().toLowerCase())
+ ||
+ app.description.toLowerCase().includes(value.trim().toLowerCase())
+ )
+
+ // Extend search
+ if (newApps.length === 0) {
+ const searchvalue = value.trim().toLowerCase();
+ newApps = allApps.filter((app) => {
+ if (app.actions !== undefined && app.actions !== null) {
+ for (let actionkey in app.actions) {
+ const inneraction = app.actions[actionkey];
+ if (inneraction.name.toLowerCase().includes(searchvalue)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ });
+ }
+
+ setVisibleApps(newApps);
+ } else {
+ setVisibleApps(
+ prioritizedApps.concat(
+ filteredApps.filter(
+ (innerapp) => !internalIds.includes(innerapp.id)
+ )
+ )
+ );
+ }
+ };
+
+ const SearchBox = ({ currentRefinement, refine, isSearchStalled, }) => {
+
+ useEffect(() => {
+ if (document !== undefined) {
+ const appsearchValue = document.getElementById("appsearch")
+ if (appsearchValue !== undefined && appsearchValue !== null) {
+ if (appsearchValue.value !== undefined && appsearchValue.value !== null && appsearchValue.value.length > 0) {
+ refine(appsearchValue.value)
+ }
+ }
+ //}
+ }
+ }, [])
+
+ return (
+
+ )
+ }
+
+
+ const AppHits = ({ hits }) => {
+ const [mouseHoverIndex, setMouseHoverIndex] = useState(0)
+
+ //var tmp = searchOpen
+ //if (!searchOpen) {
+ // return null
+ //}
+
+ const positionInfo = document.activeElement.getBoundingClientRect()
+ const outerlistitemStyle = {
+ width: "100%",
+ overflowX: "hidden",
+ overflowY: "hidden",
+ borderBottom: "1px solid rgba(255,255,255,0.4)",
+ }
+
+ if (hits.length > 4) {
+ hits = hits.slice(0, 4)
+ }
+
+ var type = "app"
+ const baseImage =
+
+ return (
+
+
+ {hits.length === 0 ?
+
+ console.log(hits)}>
+
+
+
+
+
+
+ :
+ hits.map((hit, index) => {
+ const innerlistitemStyle = {
+ width: positionInfo.width + 35,
+ overflowX: "hidden",
+ overflowY: "hidden",
+ borderBottom: "1px solid rgba(255,255,255,0.4)",
+ backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit",
+ cursor: "pointer",
+ marginLeft: 0,
+ marginRight: 0,
+ maxHeight: 75,
+ minHeight: 75,
+ maxWidth: 420,
+ minWidth: "100%",
+ }
+
+ const name = hit.name === undefined ?
+ hit.filename.charAt(0).toUpperCase() + hit.filename.slice(1).replaceAll("_", " ") + " - " + hit.title :
+ (hit.name.charAt(0).toUpperCase() + hit.name.slice(1)).replaceAll("_", " ")
+
+ var secondaryText = hit.data !== undefined ? hit.data.slice(0, 40) + "..." : ""
+ const avatar = hit.image_url === undefined ?
+ baseImage
+ :
+
+
+ //console.log(hit)
+ if (hit.categories !== undefined && hit.categories !== null && hit.categories.length > 0) {
+ secondaryText = hit.categories.slice(0, 3).map((data, index) => {
+ if (index === 0) {
+ return data
+ }
+
+ return ", " + data
+
+ /*
+ {
+ //handleChipClick
+ }}
+ variant="outlined"
+ color="primary"
+ />
+ */
+ })
+ }
+
+ var parsedUrl = isCloud ? `/apps/${hit.objectID}` : `https://shuffler.io/apps/${hit.objectID}`
+ parsedUrl += `?queryID=${hit.__queryID}`
+
+ return (
+ {
+ //if (!isCloud) {
+ // alert.info("Since this is an on-prem instance. You will need to activate the app yourself. Opening link to download it in a new window.")
+ // setTimeout(() => {
+ // event.preventDefault()
+ // window.open(parsedUrl, '_blank')
+ // }, 2000)
+ //} else {
+ alert.info(`Activating ${name}`)
+ //}
+
+ console.log("CLICK: ", hit)
+
+ const queryID = hit.__queryID
+ console.log("QUERY: ", queryID)
+
+ if (queryID !== undefined && queryID !== null) {
+ aa('init', {
+ appId: "JNSS5CFDZZ",
+ apiKey: "db08e40265e2941b9a7d8f644b6e5240",
+ })
+
+ const timestamp = new Date().getTime()
+ aa('sendEvents', [
+ {
+ eventType: 'conversion',
+ eventName: 'Public App Activated',
+ index: 'appsearch',
+ objectIDs: [hit.objectID],
+ timestamp: timestamp,
+ queryID: queryID,
+ userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
+ }
+ ])
+ } else {
+ console.log("No query to handle when activating")
+ }
+
+ activateApp(hit.objectID, true)
+ }}>
+ {
+ setMouseHoverIndex(index)
+ }}>
+
+ {avatar}
+
+
+ {/*
+
+
+
+
+
+ */}
+
+
+ )
+ })
+ }
+
+
+ )
+ }
+
+
+ const CustomSearchBox = connectSearchBox(SearchBox)
+ const CustomAppHits = connectHits(AppHits)
+
+ return (
+
+
+
+
+
+
+
+ ),
+ }}
+ fullWidth
+ color="primary"
+ placeholder={"Search Active Apps"}
+ id="appsearch"
+ onKeyPress={(event) => {
+ if (event.key === "Enter") {
+ event.target.blur(event);
+ }
+ }}
+ onBlur={(event) => {
+
+ //navigate(`?q=${event.target.value}`)
+
+ runSearch(event.target.value);
+ }}
+ />
+ {visibleApps.length > extraApps.length ? (
+
+ {visibleApps.map((app, index) => {
+ if (app.invalid) {
+ return null;
+ }
+
+ var extraMessage = ""
+ if (index == 2) {
+ extraMessage =
+ }
+
+ delay += 75
+ return (
+ runDelay ?
+
+
+
+ :
+
+ )
+ })}
+
+ ) : apps.length > 0 ? (
+ {
+ console.log("Should load in extra apps?")
+ }}
+ >
+
+ Couldn't find the app you're looking for? Searching unactivated apps. Click one of the below apps to Activate it for your organization.
+
+ {
+ console.log("CLICKED")
+ }}>
+
+
+
+
+
+
+ ) : (
+
+
+
+ Loading Apps
+
+
+ )}
+
+
+ );
+ };
+
+ const getNextActionName = (appName) => {
+ var highest = "";
+
+ const allitems = workflow.actions.concat(workflow.triggers);
+ if (allitems !== undefined && allitems !== null) {
+ for (let itemkey in allitems) {
+ const item = allitems[itemkey];
+ if (
+ item.app_name === appName &&
+ item.label !== undefined &&
+ item.label !== null
+ ) {
+ var number = item.label.split("_");
+ if (
+ isNaN(number[-1]) &&
+ parseInt(number[number.length - 1]) > highest
+ ) {
+ highest = number[number.length - 1];
+ }
+ }
+ }
+ }
+
+ appName = appName.replaceAll(" ", "_")
+
+ console.log("Highest:", highest)
+
+ if (highest) {
+ return appName + "_" + (parseInt(highest) + 1);
+ } else {
+ return appName + "_" + 1;
+ }
+ };
+
+ const setNewSelectedAction = (e) => {
+ if (selectedApp.actions === undefined || selectedApp.actions === null) {
+ return
+ }
+
+ const newaction = selectedApp.actions.find(
+ (a) => a.name === e.target.value
+ );
+
+ if (newaction === undefined || newaction === null) {
+ alert.error("Failed to find the action");
+ return;
+ }
+
+ if (workflow.actions !== undefined && workflow.actions !== null) {
+ const foundInfo = workflow.actions.find(ac => ac.id === selectedAction.id)
+ }
+
+ // Setting an old reference just to use the same memory space elsewhere
+ // for selectedAction
+ const oldaction = JSON.parse(JSON.stringify(selectedAction))
+
+ // Does this one find the wrong one?
+ //var newSelectedAction = JSON.parse(JSON.stringify(selectedAction))
+ var newSelectedAction = selectedAction
+ newSelectedAction.name = newaction.name;
+ newSelectedAction.parameters = JSON.parse(JSON.stringify(newaction.parameters))
+ newSelectedAction.errors = [];
+ newSelectedAction.isValid = true;
+ newSelectedAction.is_valid = true;
+ //console.log(newSelectedAction)
+
+
+ // Simmple action swap autocompleter
+ if (oldaction.parameters !== undefined && oldaction.parameters !== null && newSelectedAction.parameters !== undefined && oldaction.id === newSelectedAction.id) {
+ var fileid_found = false
+ for (let [paramkey,paramkeyval] in Object.entries(oldaction.parameters)) {
+ const param = oldaction.parameters[paramkey];
+
+ if (param.name === "file_id") {
+ fileid_found = true
+ }
+
+ if (param.value === null || param.value === undefined || param.value.length === 0) {
+ continue
+ }
+
+ if (param.name === "body") {
+ //console.log("Param: ", param)
+ continue
+ }
+
+ if (param.name === "headers") {
+ console.log("Swap header? For now, yes. File found: ", fileid_found)
+
+ if (fileid_found) {
+ newSelectedAction.parameters[paramkey].value = ""
+ newSelectedAction.parameters[paramkey].autocompleted = true
+
+ continue
+ }
+ //newSelectedAction.parameters[newParamIndex].value = param.value
+ }
+
+ if (newSelectedAction.parameters === undefined || newSelectedAction.parameters === null) {
+ continue
+ }
+
+ // Not doing options fields
+ const newParamIndex = newSelectedAction.parameters.findIndex(paramdata => paramdata.name === param.name)
+ if (newParamIndex < 0) {
+ continue
+ }
+
+ newSelectedAction.parameters[newParamIndex].value = param.value
+ newSelectedAction.parameters[newParamIndex].autocompleted = true
+ if (param.options !== undefined && param.options !== null && param.options.length > 0) {
+ newSelectedAction.parameters[newParamIndex].autocompleted = false
+ }
+ }
+ }
+
+ if (newSelectedAction.app_name === "Shuffle Tools") {
+ const iconInfo = GetIconInfo(newSelectedAction);
+ console.log("ICONINFO: ", iconInfo);
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+ newSelectedAction.large_image = svgpin_Url;
+ newSelectedAction.fillGradient = iconInfo.fillGradient;
+ newSelectedAction.fillstyle = "solid";
+ if (
+ newSelectedAction.fillGradient !== undefined &&
+ newSelectedAction.fillGradient !== null &&
+ newSelectedAction.fillGradient.length > 0
+ ) {
+ newSelectedAction.fillstyle = "linear-gradient";
+ } else {
+ newSelectedAction.iconBackground = iconInfo.iconBackgroundColor;
+ }
+
+ const foundnode = cy.getElementById(newSelectedAction.id);
+ if (foundnode !== null && foundnode !== undefined) {
+ foundnode.data(newSelectedAction);
+ }
+ }
+
+
+ // Takes an action as input, then runs through and updates the relevant parameters based on previous actions' results (parent nodes)
+ // Further checks if those fields are already set in a previously used action
+ newSelectedAction = RunAutocompleter(newSelectedAction);
+
+ if (
+ newaction.returns.example !== undefined &&
+ newaction.returns.example !== null &&
+ newaction.returns.example.length > 0
+ ) {
+ newSelectedAction.example = newaction.returns.example;
+ }
+
+ if (
+ newaction.description !== undefined &&
+ newaction.description !== null &&
+ newaction.description.length > 0
+ ) {
+ newSelectedAction.description = newaction.description
+ }
+
+ // FIXME - this is broken sometimes lol
+ //var env = environments.find(a => a.Name === newaction.environment)
+ //if ((!env || env === undefined) && selectedAction.environment === undefined ) {
+ // env = environments[defaultEnvironmentIndex]
+ //}
+ //setSelectedActionEnvironment(env)
+
+
+ if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
+ const foundActionIndex = workflow.actions.findIndex(actiondata => actiondata.id === newSelectedAction.id)
+ console.log("Found action on index ", foundActionIndex)
+ if (foundActionIndex >= 0) {
+ workflow.actions[foundActionIndex] = newSelectedAction
+ setWorkflow(workflow)
+ }
+ }
+
+ console.log("NEW ACTION: ", newSelectedAction);
+ setSelectedAction(newSelectedAction);
+ setUpdate(Math.random());
+
+ // FIXME - should change icon-node (descriptor) as well
+ 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;
+ }
+ }
+ }
+ };
+
+ // APPSELECT at top
+ // 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;
+ setSelectedAction(selectedAction);
+ };
+
+ const actionDelayChange = (event) => {
+ if (isNaN(event.target.value)) {
+ console.log("NAN: ", event.target.value)
+ return
+ }
+
+ const parsedNumber = parseInt(event.target.value)
+ if (parsedNumber > 86400) {
+ console.log("Max number is 1 day (86400)")
+ return
+ }
+
+ selectedAction.execution_delay = parsedNumber
+ setSelectedAction(selectedAction)
+ }
+
+ const selectedTriggerChange = (event) => {
+ selectedTrigger.label = event.target.value;
+ setSelectedTrigger(selectedTrigger);
+ };
+
+ // Starts on current node and climbs UP the tree to the root object.
+ // Sends back everything in it's path
+ const getParents = (action) => {
+ if (action === undefined || action === null) {
+ return []
+ }
+
+ var allkeys = [action.id];
+ var handled = [];
+ var results = [];
+
+ // maxiter = max amount of parent nodes to loop
+ // also handles breaks if there are issues
+ var iterations = 0;
+ var maxiter = 10;
+ while (true) {
+ for (let parentkey in allkeys) {
+ var currentnode = cy.getElementById(allkeys[parentkey]);
+ if (currentnode === undefined || currentnode === null) {
+ continue;
+ }
+
+ if (currentnode.data() === undefined) {
+ handled.push(allkeys[parentkey]);
+ results.push({ id: allkeys[parentkey], type: "TRIGGER" });
+ } else {
+ if (handled.includes(currentnode.data().id)) {
+ continue;
+ } else {
+ handled.push(currentnode.data().id);
+ results.push(currentnode.data());
+ }
+ }
+
+ // Get the name / label here too?
+ if (currentnode.length === 0) {
+ continue;
+ }
+
+ const incomingEdges = currentnode.incomers("edge");
+ if (incomingEdges.length === 0) {
+ continue;
+ }
+
+ for (let i = 0; i < incomingEdges.length; i++) {
+ var tmp = incomingEdges[i];
+ if (tmp.data("decorator")) {
+ continue
+ }
+
+ if (!allkeys.includes(tmp.data("source"))) {
+ allkeys.push(tmp.data("source"));
+ }
+ }
+ }
+
+ if (results.length === allkeys.length || iterations === maxiter) {
+ break;
+ }
+
+ iterations += 1;
+ }
+
+ // Remove on the end as we don't want to remove everything
+ results = results.filter((data) => data.id !== action.id);
+ results = results.filter((data) => data.type === "ACTION" || data.app_name === "Shuffle Workflow" || data.app_name === "User Input");
+ results.push({ label: "Execution Argument", type: "INTERNAL" });
+ return results;
+ };
+
+ // BOLD name: type: required?
+ // FORM
+ // Dropdown -> static, action, local env, global env
+ // VALUE (JSON)
+ // {data.name}, {data.description}, {data.required}, {data.schema.type}
+
+ //height: "100%",
+ const appApiViewStyle = {
+ display: "flex",
+ flexDirection: "column",
+ backgroundColor: "#1F2023",
+ color: "white",
+ paddingRight: 15,
+ paddingLeft: 15,
+ minHeight: "100%",
+ zIndex: 1000,
+ resize: "vertical",
+ overflow: "auto",
+ };
+
+ var rightsidebarStyle = {
+ position: "fixed",
+ top: appBarSize + 25,
+ right: 25,
+ height: "80vh",
+ width: isMobile ? "100%" : 365,
+ minWidth: 200,
+ maxWidth: 600,
+ maxHeight: "100vh",
+ border: "1px solid rgb(91, 96, 100)",
+ zIndex: 1000,
+ borderRadius: theme.palette.borderRadius,
+ resize: "both",
+ overflow: "auto",
+ };
+
+ const setTriggerFolderWrapperMulti = (event) => {
+ const { options } = event.target;
+ var value = [];
+ for (let i = 0, l = options.length; i < l; i += 1) {
+ if (options[i].selected) {
+ value.push(options[i].value);
+ }
+ }
+
+ if (selectedTrigger.parameters === null) {
+ selectedTrigger.parameters = [[]];
+ workflow.triggers[selectedTriggerIndex].parameters = [[]];
+ }
+
+ // Max 1 folder for office for some reason. MailFolders('MAILBOX_ID') in resource
+ // Can't parse URL with multiple folders.
+ if (selectedTrigger.name === "Office365" & value !== undefined && value !== null && value.length > 1) {
+ alert.info("Max 1 folder at a time allowed for Office365")
+ console.log("VALUE: ", value)
+ value = [value[0]]
+ }
+
+ // This is a dirty workaround for the static values in the go backend and datastore db
+ const fixedValue = value.join(splitter);
+ selectedTrigger.parameters[0] = {
+ value: fixedValue,
+ name: "outlookfolder",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[0] = {
+ value: fixedValue,
+ name: "outlookfolder",
+ };
+
+ // This resets state for some reason (:
+ setSelectedAction({});
+ setSelectedTrigger({});
+ setSelectedApp({});
+ setSelectedEdge({});
+
+ // Set value
+ setSelectedTrigger(selectedTrigger);
+ setWorkflow(workflow);
+ };
+
+ const setTriggerCronWrapper = (value) => {
+ console.log("Cron Value: ", value)
+ if (selectedTrigger.parameters === null) {
+ selectedTrigger.parameters = [];
+ }
+
+ workflow.triggers[selectedTriggerIndex].parameters[0] = {
+ value: value,
+ name: "cron",
+ };
+ setWorkflow(workflow);
+ };
+
+ const setTriggerOptionsWrapper = (value) => {
+ if (selectedTrigger.parameters === null) {
+ selectedTrigger.parameters = [];
+ }
+
+ const splitItems =
+ workflow.triggers[selectedTriggerIndex].parameters[2].value.split(",");
+ console.log(splitItems);
+ if (splitItems.includes(value)) {
+ for (let i = 0; i < splitItems.length; i++) {
+ if (splitItems[i] === value) {
+ splitItems.splice(i, 1);
+ }
+ }
+ } else {
+ splitItems.push(value);
+ }
+
+ for (let i = 0; i < splitItems.length; i++) {
+ if (splitItems[i] === "") {
+ splitItems.splice(i, 1);
+ }
+ }
+
+ workflow.triggers[selectedTriggerIndex].parameters[2].value =
+ splitItems.join(",");
+
+ console.log(splitItems);
+ setWorkflow(workflow);
+ setLocalFirstrequest(!localFirstrequest);
+ };
+
+ const setTriggerTextInformationWrapper = (value) => {
+ if (selectedTrigger.parameters === null) {
+ selectedTrigger.parameters = [];
+ }
+
+ workflow.triggers[selectedTriggerIndex].parameters[0] = {
+ value: value,
+ name: "alertinfo",
+ };
+ setWorkflow(workflow);
+ };
+
+ const setTriggerBodyWrapper = (value) => {
+ if (selectedTrigger.parameters === null) {
+ selectedTrigger.parameters = [];
+ workflow.triggers[selectedTriggerIndex].parameters[0] = {
+ value: value,
+ name: "cron",
+ };
+ }
+
+ workflow.triggers[selectedTriggerIndex].parameters[1] = {
+ value: value,
+ name: "execution_argument",
+ };
+ setWorkflow(workflow);
+ };
+
+ const AppConditionHandler = (props) => {
+ const { tmpdata, type } = props;
+ const [data] = useState(tmpdata);
+ const [multiline, setMultiline] = useState(false);
+ const [showAutocomplete, setShowAutocomplete] = React.useState(false);
+ const [actionlist, setActionlist] = React.useState([]);
+
+ if (tmpdata === undefined) {
+ return tmpdata;
+ }
+
+ if (data.variant === "") {
+ data.variant = "STATIC_VALUE";
+ }
+
+ // Set actions based on NEXT node, since it should be able to involve those two
+ if (actionlist.length === 0) {
+ // FIXME: Have previous execution values in here
+ actionlist.push({
+ type: "Execution Argument",
+ name: "Execution Argument",
+ value: "$exec",
+ highlight: "exec",
+ autocomplete: "exec",
+ example: "tmp",
+ })
+ actionlist.push({
+ type: "Shuffle DB",
+ name: "Shuffle DB",
+ value: "$shuffle_cache",
+ highlight: "shuffle_cache",
+ autocomplete: "shuffle_cache",
+ example: "tmp",
+ })
+
+ if (
+ workflow.workflow_variables !== null &&
+ workflow.workflow_variables !== undefined &&
+ workflow.workflow_variables.length > 0
+ ) {
+ for (let varkey in workflow.workflow_variables) {
+ const item = workflow.workflow_variables[varkey];
+ actionlist.push({
+ type: "workflow_variable",
+ name: item.name,
+ value: item.value,
+ id: item.id,
+ autocomplete: `${item.name.split(" ").join("_")}`,
+ example: item.value,
+ });
+ }
+ }
+
+ // FIXME: Add values from previous executions if they exist
+ if (
+ workflow.execution_variables !== null &&
+ workflow.execution_variables !== undefined &&
+ workflow.execution_variables.length > 0
+ ) {
+ for (let varkey in workflow.execution_variables) {
+ const item = workflow.execution_variables[varkey];
+ actionlist.push({
+ type: "execution_variable",
+ name: item.name,
+ value: item.value,
+ id: item.id,
+ autocomplete: `${item.name.split(" ").join("_")}`,
+ example: "",
+ });
+ }
+ }
+
+ const destAction = cy.getElementById(selectedEdge.target);
+ var parents = getParents(destAction.data());
+ if (parents.length > 1) {
+ for (let parentkey in parents) {
+ const item = parents[parentkey];
+ if (item.label === "Execution Argument") {
+ continue;
+ }
+
+ // 1. Take
+ const actionvalue = {
+ type: "action",
+ id: item.id,
+ name: item.label,
+ autocomplete: `${item.label.split(" ").join("_")}`,
+ example: item.example === undefined ? "" : item.example,
+ };
+ actionlist.push(actionvalue);
+ }
+ }
+
+ setActionlist(actionlist);
+ }
+
+ if (
+ data.multiline !== undefined &&
+ data.multiline !== null &&
+ data.multiline === true
+ ) {
+ setMultiline(true);
+ }
+
+ var placeholder = "Static value";
+ if (
+ data.example !== undefined &&
+ data.example !== null &&
+ data.example.length > 0
+ ) {
+ placeholder = data.example;
+ }
+
+ var datafield = (
+
+ Use "Shuffle Tools" app with "Filter List" action to handle loops
+
+ ) : null
+ }
+ onBlur={(e) => {
+ changeActionVariable(data.action_field, e.target.value);
+ setUpdate(Math.random());
+ }}
+ />
+ );
+
+ const changeActionVariable = (variable, value) => {
+ // set the name
+ data.value = value;
+ data.action_field = variable;
+
+ if (type === "source") {
+ setSourceValue(data);
+ } else if (type === "destination") {
+ setDestinationValue(data);
+ }
+ };
+
+ return (
+
+
+ {datafield}
+ {actionlist.length === 0 ? null : (
+
+
+ Autocomplete
+
+ {
+ setShowAutocomplete(false);
+
+ setUpdate(Math.random());
+ }}
+ onClick={() => {
+ setShowAutocomplete(true);
+ }}
+ open={showAutocomplete}
+ fullWidth
+ style={{
+ borderBottom: `1px solid #f85a3e`,
+ color: "white",
+ height: 50,
+ marginTop: 2,
+ }}
+ onChange={(e) => {
+ const autocomplete = e.target.value.autocomplete;
+ const newValue = autocomplete.startsWith("$")
+ ? data.value + autocomplete
+ : `${data.value}$${autocomplete}`;
+ changeActionVariable(data.action_field, newValue);
+ }}
+ >
+ {actionlist.map((data) => {
+ const icon =
+ data.type === "action" ? (
+
+ ) : data.type === "workflow_variable" ||
+ data.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];
+
+ 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) => {
+ var node = cy.getElementById(actionId);
+ console.log("Hovering over action: " + actionId)
+ if (node.length > 0) {
+ if (inside) {
+ console.log("Hovering over action: " + actionId)
+ node.addClass("shuffle-hover-highlight");
+ } else {
+ node.removeClass("shuffle-hover-highlight");
+ }
+ }
+ };
+
+ return (
+ {
+ if (data.type === "Execution Argument") {
+ handleExecArgumentHover(true);
+ } else if (data.type === "action") {
+ handleActionHover(true, data.id);
+ }
+ }}
+ onMouseOut={() => {
+ if (data.type === "Execution Argument") {
+ handleExecArgumentHover(false);
+ } else if (data.type === "action") {
+ handleActionHover(false, data.id);
+ }
+ }}
+ >
+
+
+ {icon} {data.name}
+
+
+
+ );
+ })}
+
+
+ )}
+
+ );
+ };
+
+ const menuItemStyle = {
+ color: "white",
+ backgroundColor: inputColor,
+ };
+
+ const conditionsModal = (
+ {
+ }}
+ >
+
+ Conditions can't be used for loops [ .# ]{" "}
+
+ Learn more
+
+
+
+
+ Condition
+
+
+
+
+
+ {
+ conditionValue.configuration =
+ !conditionValue.configuration;
+ setConditionValue(conditionValue);
+ setUpdate(Math.random());
+ }}
+ >
+ {conditionValue.configuration ? "!" : "="}
+
+
+
+
+
+ {
+ setVariableAnchorEl(e.currentTarget);
+ }}
+ >
+ {conditionValue.value}
+
+
+
+
+
+
+
+ {
+ setConditionsModalOpen(false);
+ setSourceValue({});
+ setConditionValue({});
+ setDestinationValue({});
+ }}
+ color="secondary"
+ >
+ Cancel
+
+ {
+ setSelectedEdge({});
+
+ var data = {
+ condition: conditionValue,
+ source: sourceValue,
+ destination: destinationValue,
+ };
+
+ setConditionsModalOpen(false);
+ if (selectedEdge.conditions === undefined) {
+ selectedEdge.conditions = [data];
+ } else {
+ const curedgeindex = selectedEdge.conditions.findIndex(
+ (data) => data.source.id === sourceValue.id
+ );
+ if (curedgeindex < 0) {
+ selectedEdge.conditions.push(data);
+ } else {
+ selectedEdge.conditions[curedgeindex] = data;
+ }
+ }
+
+ var label = "";
+ if (selectedEdge.conditions.length === 1) {
+ label = selectedEdge.conditions.length + " condition";
+ } else if (selectedEdge.conditions.length > 1) {
+ label = selectedEdge.conditions.length + " conditions";
+ }
+
+ var currentedge = cy.getElementById(selectedEdge.id);
+ if (currentedge !== undefined && currentedge !== null && label !== undefined) {
+ currentedge.data("label", label)
+ //.label = label;
+ //oldstartnode[0].data("isStartNode", false);
+ }
+
+ setSelectedEdge(selectedEdge);
+ workflow.branches[selectedEdgeIndex] = selectedEdge;
+ setWorkflow(workflow);
+ }}
+ color="primary"
+ >
+ Submit
+
+
+
+
+ );
+
+ const EdgeSidebar = () => {
+ const ConditionHandler = (condition, index) => {
+ const [open, setOpen] = React.useState(false);
+ const [anchorEl, setAnchorEl] = React.useState(null);
+
+ const duplicateCondition = (conditionIndex) => {
+ var newEdge = JSON.parse(
+ JSON.stringify(selectedEdge.conditions[conditionIndex])
+ );
+ const newUuid = uuidv4();
+ newEdge.condition.id = newUuid;
+ newEdge.source.id = newUuid;
+ newEdge.destination.id = newUuid;
+ selectedEdge.conditions.push(newEdge);
+
+ setUpdate(Math.random());
+ };
+
+ const deleteCondition = (conditionIndex) => {
+ console.log(selectedEdge);
+ if (selectedEdge.conditions.length === 1) {
+ selectedEdge.conditions = [];
+ } else {
+ selectedEdge.conditions.splice(conditionIndex, 1);
+ }
+
+ setSelectedEdge(selectedEdge);
+ setOpen(false);
+ setUpdate(Math.random());
+ };
+
+ const paperVariableStyle = {
+ minHeight: 75,
+ maxHeight: 75,
+ minWidth: "100%",
+ maxWidth: "100%",
+ marginTop: "5px",
+ color: "white",
+ backgroundColor: surfaceColor,
+ cursor: "pointer",
+ display: "flex",
+ };
+
+ const menuClick = (event) => {
+ console.log("MENU CLICK");
+ setOpen(!open);
+ setAnchorEl(event.currentTarget);
+ };
+
+ return (
+ { }}
+ >
+
+
+
{
+ setSourceValue(condition.source);
+ setConditionValue(condition.condition);
+ setDestinationValue(condition.destination);
+ setConditionsModalOpen(true);
+ }}
+ >
+
+ {condition.source.value}
+
+
+
{ }}
+ >
+ {condition.condition.value}
+
+
+
+ {condition.destination.value}
+
+
+
+
+
+
+
+
+
+
+ );
+ };
+
+ var injectedData =
;
+
+ if (
+ selectedEdge.conditions !== undefined &&
+ selectedEdge.conditions !== null &&
+ selectedEdge.conditions.length > 0
+ ) {
+ injectedData = selectedEdge.conditions.map((condition, index) => {
+ return ConditionHandler(condition, index);
+ });
+ }
+
+ // FIXME - remove index
+ const conditionId = uuidv4();
+ return (
+
+
+
+
Conditions
+ {injectedData}
+
+
{
+ if (conditionsModalOpen) {
+ return
+ }
+
+ setSourceValue({
+ name: "source",
+ value: "",
+ variant: "STATIC_VALUE",
+ action_field: "",
+ id: conditionId,
+ });
+ setConditionValue({
+ name: "condition",
+ value: "equals",
+ id: conditionId,
+ });
+ setDestinationValue({
+ name: "destination",
+ value: "",
+ variant: "STATIC_VALUE",
+ action_field: "",
+ id: conditionId,
+ });
+
+ setConditionsModalOpen(true);
+ }}
+ fullWidth
+ >
+ New condition
+
+
+ );
+ };
+
+ // 1. GET the trigger authentication data
+ // 2. Parse the fields that are used (outlook & gmail)
+ // 3. Parse the folders that are selected
+ // 4. Start / stop
+ const EmailSidebar = () => {
+ if (Object.getOwnPropertyNames(selectedTrigger).length === 0) {
+ return null;
+ }
+
+ if (workflow.triggers[selectedTriggerIndex] === undefined) {
+ return null;
+ }
+
+ if (
+ workflow.triggers[selectedTriggerIndex].parameters === undefined ||
+ workflow.triggers[selectedTriggerIndex].parameters === null ||
+ workflow.triggers[selectedTriggerIndex].parameters.length === 0
+ ) {
+ workflow.triggers[selectedTriggerIndex].parameters = [
+ { value: "No folders selected yet", name: "outlookfolder" },
+ ];
+ selectedTrigger.parameters = [
+ { value: "No folders selected yet", name: "outlookfolder" },
+ ];
+ setWorkflow(workflow);
+ setSelectedTrigger(selectedTrigger);
+ }
+
+ const setGmailFolders = () => {
+ console.log("In set gmail folders");
+ fetch(
+ globalUrl +
+ "/api/v1/triggers/gmail/getFolders?trigger_id=" +
+ selectedTrigger.id,
+ {
+ method: "GET",
+ headers: { "content-type": "application/json" },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error("No folders :o!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (
+ responseJson !== undefined &&
+ responseJson !== null &&
+ responseJson.success !== false &&
+ responseJson.length > 0
+ ) {
+ setTriggerFolders(responseJson);
+ }
+
+ if (
+ workflow.triggers[selectedTriggerIndex].parameters.length === 0 &&
+ responseJson.length > 0
+ ) {
+ workflow.triggers[selectedTriggerIndex].parameters = [
+ {
+ value: responseJson[0].displayName,
+ name: "outlookfolder",
+ id: responseJson[0].id,
+ },
+ ];
+ selectedTrigger.parameters = [
+ {
+ value: responseJson[0].displayName,
+ name: "outlookfolder",
+ id: responseJson[0].id,
+ },
+ ];
+ setWorkflow(workflow);
+ setSelectedTrigger(selectedTrigger);
+ }
+ })
+ .catch((error) => {
+ console.log("Get gmail folder error: ", error.toString());
+ });
+ };
+
+ const setOutlookFolders = () => {
+ fetch(
+ globalUrl +
+ "/api/v1/triggers/outlook/getFolders?trigger_id=" +
+ selectedTrigger.id,
+ {
+ method: "GET",
+ headers: { "content-type": "application/json" },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error("No folders :o!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (
+ responseJson !== null &&
+ responseJson.success !== false &&
+ responseJson.length > 0
+ ) {
+ console.log("Got trigger folders: ", triggerFolders)
+ setTriggerFolders(responseJson);
+ }
+
+ if (
+ workflow.triggers[selectedTriggerIndex].parameters.length === 0 &&
+ responseJson.length > 0
+ ) {
+ workflow.triggers[selectedTriggerIndex].parameters = [
+ {
+ value: responseJson[0].displayName,
+ name: "outlookfolder",
+ id: responseJson[0].id,
+ },
+ ]
+ selectedTrigger.parameters = [
+ {
+ value: responseJson[0].displayName,
+ name: "outlookfolder",
+ id: responseJson[0].id,
+ },
+ ];
+ setWorkflow(workflow);
+ setSelectedTrigger(selectedTrigger);
+ }
+ })
+ .catch((error) => {
+ console.log("Get outlook folders error: ", error.toString());
+ });
+ };
+
+ const getTriggerAuth = () => {
+ fetch(`${globalUrl}/api/v1/triggers/${selectedTrigger.id}`, {
+ method: "GET",
+ headers: { "content-type": "application/json" },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error("No trigger info :o!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+
+ setTriggerAuthentication(responseJson);
+ })
+ .catch((error) => {
+ //console.log(error.toString());
+ console.log("Set trigger auth error: ", error.toString());
+ });
+ };
+
+ // Getting the triggers and the folders if they exist
+ if (localFirstrequest) {
+ //console.log("Trigger: ", selectedTrigger)
+ //console.log("Triggername: ", selectedTrigger.name)
+ //if (selectedTrigger.name.toLowerCase() === "gmail") {
+ setGmailFolders();
+ setOutlookFolders();
+
+ getTriggerAuth();
+ setLocalFirstrequest(false);
+ }
+
+ const gmailButton =
+ selectedTrigger.name !== "Gmail" ? null : (
+ {
+ console.log("HOST: ", window.location.host);
+ console.log("HOST: ", window.location);
+ const redirectUri = isCloud
+ ? window.location.host === "localhost:3002"
+ ? "http%3A%2F%2Flocalhost:5002%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister"
+ : "https%3A%2F%2Fshuffler.io%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister"
+ : window.location.protocol === "http:" ?
+ `http%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister`
+ :
+ `https%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister`
+
+ const client_id =
+ "253565968129-c0a35knic7q1pdk6i6qk9gdkvr07ci49.apps.googleusercontent.com";
+ const username = userdata.id;
+ console.log(redirectUri);
+ console.log("USER: ", username, userdata);
+
+ const branch = workflow.branches.find(
+ (branch) => branch.source_id === selectedTrigger.id
+ );
+ if (branch === undefined || branch === null) {
+ alert.error(
+ "No startnode connected to node. Connect it to an action."
+ );
+ return;
+ }
+
+ console.log("BRANCH: ", branch);
+ const startnode = branch.destination_id;
+ const scopes = "https://www.googleapis.com/auth/gmail.readonly";
+ const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`;
+ console.log("URL: ", url);
+
+ var newwin = window.open(url, "", "width=800,height=600");
+
+ // Check whether we got a callback somewhere
+ var id = setInterval(function () {
+ fetch(
+ `${globalUrl}/api/v1/triggers/${selectedTrigger.id}`,
+ {
+ method: "GET",
+ headers: { "content-type": "application/json" },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error("No trigger info :o!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ //console.log("RESPONSE: ");
+ setTriggerAuthentication(responseJson);
+ clearInterval(id);
+ newwin.close();
+ setGmailFolders();
+ })
+ .catch((error) => {
+ console.log("Set gmail trigg error: ", error.toString());
+ });
+ }, 2500);
+
+ saveWorkflow(workflow);
+ }}
+ >
+
+
+ Sign in with Google
+
+
+ );
+
+ const outlookButton =
+ selectedTrigger.name !== "Office365" ? null : (
+ {
+ console.log(window.location)
+ const redirectUri = isCloud
+ ? window.location.host === "localhost:3002"
+ ? "http%3A%2F%2Flocalhost:5002%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
+ : "https%3A%2F%2Fshuffler.io%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
+ : window.location.protocol === "http:" ?
+ `http%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister`
+ :
+ `https%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister`
+
+ //const client_id = "fd55c175-aa30-4fa6-b303-09a29fb3f750"
+ //const client_id = "bb4bff85-0d0b-4f5d-8a69-3cee8029b11a";
+ const client_id = "efe4c3fe-84a1-4821-a84f-23a6cfe8e72d";
+
+ const username = userdata.id;
+ console.log(redirectUri);
+ console.log("USER: ", username, userdata);
+
+ const branch = workflow.branches.find(
+ (branch) => branch.source_id === selectedTrigger.id
+ );
+ if (branch === undefined || branch === null) {
+ alert.error(
+ "No startnode connected to node. Connect it to an action."
+ );
+ return;
+ }
+
+ console.log("BRANCH: ", branch);
+ const startnode = branch.destination_id;
+ // prompt=login
+ const url = `https://login.microsoftonline.com/common/oauth2/authorize?access_type=offline&client_id=${client_id}&redirect_uri=${redirectUri}&resource=https%3A%2F%2Fgraph.microsoft.com&response_type=code&scope=Mail.Read+User.Read+https%3A%2F%2Foutlook.office.com%2Fmail.read&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Doutlook%26start%3d${startnode}`;
+ //const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`
+
+ //const scopes = "https://www.googleapis.com/auth/gmail.readonly"
+ //const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`
+
+ console.log("URL: ", url);
+
+ var newwin = window.open(url, "", "width=800,height=600");
+
+ // Check whether we got a callback somewhere
+ var id = setInterval(function () {
+ fetch(
+ `${globalUrl}/api/v1/triggers/${selectedTrigger.id}`,
+ {
+ method: "GET",
+ headers: { "content-type": "application/json" },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error("No trigger info :o!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ setTriggerAuthentication(responseJson);
+ clearInterval(id);
+ newwin.close();
+ setOutlookFolders();
+ })
+ .catch((error) => {
+ console.log("Set outlook trigger error: ", error.toString());
+ });
+ }, 2500);
+
+ saveWorkflow(workflow);
+ }}
+ >
+
+
+ Sign in with Microsoft
+
+
+ );
+
+ // FIXME - set everything in here to multifolder etc
+ var triggerInfo = "SET UP BUT NO TYPE :)";
+ if (Object.getOwnPropertyNames(triggerAuthentication).length > 0) {
+ // Should get the folders if they don't already exist
+
+ if (
+ triggerAuthentication.type === "outlook" ||
+ triggerAuthentication.type === "gmail"
+ ) {
+ triggerInfo = (
+
+ {selectedTrigger.status === "running" ? null : (
+
+
+ {outlookButton}
+ {gmailButton}
+
+ If you have trouble using this trigger, please contact us to get access
+
+
+ )}
+
+ {triggerFolders === undefined || triggerFolders === null ? null : (
+
+
+
+
+ Select {triggerAuthentication.type === "gmail" ? "labels" : "a folder"}
+
+
+ {triggerFolders.length === 0 ?
+
+ No folders found. Please authenticate and make sure the user has access folders available. If this persists, contact us .
+
+ :
+ {
+ setTriggerFolderWrapperMulti(e)
+ }}
+ fullWidth
+ input={ }
+ key={selectedTrigger}
+ >
+ {triggerFolders.map((folder) => {
+ var folderItem = (
+
+ {folder.displayName}
+
+ );
+
+ if (folder.childFolderCount > 0) {
+ // Here to handle subfolders sometime later
+ folderItem = (
+
+ {folder.displayName}
+
+ );
+ }
+
+ return folderItem;
+ })}
+
+ }
+
+ )}
+
+ );
+ } else if (triggerAuthentication.type === "gmail") {
+ console.log("AUTH: ", triggerAuthentication);
+ triggerInfo = "SPECIAL GMAIL";
+ }
+ }
+
+ // Check
+ const argumentView =
+ Object.getOwnPropertyNames(triggerAuthentication).length > 0 ? (
+ {triggerInfo}
+ ) : (
+
+
+ {outlookButton}
+ {gmailButton}
+
+ If you have trouble using this trigger, please {
+ if (window.drift !== undefined) {
+ window.drift.api.startInteraction({ interactionId: 340043 })
+ } else {
+ console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift)
+ }
+ }}>contact us to get access
+
+
+ );
+
+ return (
+
+
+
+
Name
+
+
+
+ Environment:
+
+
+
+ {argumentView}
+
+
+
+
+ {
+ startMailSub(selectedTrigger, selectedTriggerIndex);
+ }}
+ color="primary"
+ >
+ Start
+
+ {
+ stopMailSub(selectedTrigger, selectedTriggerIndex);
+ }}
+ color="primary"
+ >
+ Stop
+
+
+
+
+
+ );
+ };
+
+ const SubflowSidebar = () => {
+ const [menuPosition, setMenuPosition] = useState(null);
+ const [showDropdown, setShowDropdown] = React.useState(false);
+ const [actionlist, setActionlist] = React.useState([]);
+
+ if (actionlist.length === 0) {
+ // FIXME: Have previous execution values in here
+ actionlist.push({
+ type: "Execution Argument",
+ name: "Execution Argument",
+ value: "$exec",
+ highlight: "exec",
+ autocomplete: "exec",
+ example: "hello",
+ })
+ actionlist.push({
+ type: "Shuffle Database",
+ name: "Shuffle Database",
+ value: "$shuffle_cache",
+ highlight: "shuffle_db",
+ autocomplete: "shuffle_cache",
+ example: "hello",
+ })
+ if (
+ workflow.workflow_variables !== null &&
+ workflow.workflow_variables !== undefined &&
+ workflow.workflow_variables.length > 0
+ ) {
+ for (let varkey in workflow.workflow_variables) {
+ const item = workflow.workflow_variables[varkey];
+ actionlist.push({
+ type: "workflow_variable",
+ name: item.name,
+ value: item.value,
+ id: item.id,
+ autocomplete: `${item.name.split(" ").join("_")}`,
+ example: item.value,
+ });
+ }
+ }
+
+ // FIXME: Add values from previous executions if they exist
+ if (
+ workflow.execution_variables !== null &&
+ workflow.execution_variables !== undefined &&
+ workflow.execution_variables.length > 0
+ ) {
+ for (let varkey in workflow.execution_variables) {
+ const item = workflow.execution_variables[varkey];
+ actionlist.push({
+ type: "execution_variable",
+ name: item.name,
+ value: item.value,
+ id: item.id,
+ autocomplete: `${item.name.split(" ").join("_")}`,
+ example: "",
+ });
+ }
+ }
+
+ var parents = getParents(selectedTrigger);
+ if (parents.length > 1) {
+ for (let parentkey in parents) {
+ const item = parents[parentkey];
+ if (item.label === "Execution Argument") {
+ continue;
+ }
+
+ var exampledata = item.example === undefined ? "" : item.example;
+ // Find previous execution and their variables
+ if (workflowExecutions.length > 0) {
+ // Look for the ID
+ for (let execkey in workflowExecutions) {
+ if (
+ workflowExecutions[execkey].results === undefined ||
+ workflowExecutions[execkey].results === null
+ ) {
+ continue;
+ }
+
+ var foundResult = workflowExecutions[execkey].results.find(
+ (result) => result.action.id === item.id
+ );
+ if (foundResult === undefined) {
+ continue;
+ }
+
+ const validated = validateJson(foundResult.result)
+ if (validated.valid) {
+ exampledata = validateJson.result
+ break
+ }
+ }
+ }
+
+ // 1. Take
+ const actionvalue = {
+ type: "action",
+ id: item.id,
+ name: item.label,
+ autocomplete: `${item.label.split(" ").join("_")}`,
+ example: exampledata,
+ }
+ actionlist.push(actionvalue);
+ }
+ }
+
+ setActionlist(actionlist);
+ }
+
+ // Shows nested list of nodes > their JSON lists
+ const ActionlistWrapper = (props) => {
+ const { data } = props;
+
+ const handleMenuClose = () => {
+ setUpdate(Math.random());
+ setMenuPosition(null);
+ };
+
+ const handleItemClick = (values) => {
+ console.log("VALUES: ", values)
+ if (values === undefined || values === null || values.length === 0) {
+ return;
+ }
+
+
+ /*
+ workflow.triggers[selectedTriggerIndex].parameters[1].value
+ .trim()
+ .endsWith("$")
+ ? values[0].autocomplete
+ : "$" + values[0].autocomplete;
+
+ for (var key in values) {
+ if (key === 0 || values[key].autocomplete.length === 0) {
+ continue;
+ }
+
+ toComplete += values[key].autocomplete
+ }
+ */
+
+ console.log("SELECTED TRIGGER: ", selectedTrigger)
+ if (selectedTrigger.name === "Shuffle Workflow") {
+ const toComplete = selectedTrigger.parameters[1].value + "$" + values[0].autocomplete
+ selectedTrigger.parameters[1].value = toComplete
+ setSelectedTrigger(selectedTrigger)
+ }
+
+ setUpdate(Math.random());
+ setShowDropdown(false);
+ setMenuPosition(null);
+ };
+
+ const iconStyle = {
+ 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 = [];
+ if (typeof innerdata.example === "object") {
+ parsedPaths = GetParsedPaths(innerdata.example, "");
+ }
+
+ 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}
+
+
+
+ );
+ })}
+
+ ) : (
+ handleMouseover()}
+ onMouseOut={() => {
+ handleMouseOut();
+ }}
+ onClick={() => {
+ handleItemClick([innerdata]);
+ }}
+ >
+
+
+ {icon} {innerdata.name}
+
+
+
+ );
+ })}
+
+ );
+ };
+
+ if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
+ if (workflow.triggers[selectedTriggerIndex] === undefined) {
+ return null;
+ }
+
+ 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: "workflow",
+ value: "",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[1] = {
+ name: "argument",
+ value: "",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[2] = {
+ name: "user_apikey",
+ value: "",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[3] = {
+ name: "startnode",
+ value: "",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[4] = {
+ name: "check_result",
+ value: "false",
+ };
+
+ /*
+ // API-key has been replaced by auth key for the execution.
+ // Parents can now automatically execute children without auth from a user, as long as the subflow in question is owned by the same org and the subflow is actually referencing it during checkin.
+ console.log("SETTINGS: ", userSettings);
+ if (
+ userSettings !== undefined &&
+ userSettings !== null &&
+ userSettings.apikey !== null &&
+ userSettings.apikey !== undefined &&
+ userSettings.apikey.length > 0
+ ) {
+ workflow.triggers[selectedTriggerIndex].parameters[2] = {
+ name: "user_apikey",
+ value: userSettings.apikey,
+ };
+ }
+ */
+ }
+
+ const handleSubflowStartnodeSelection = (e) => {
+ setSubworkflowStartnode(e.target.value);
+
+ if (e.target.value === null || e.target.value === undefined) {
+ return
+ }
+
+ const branchId = uuidv4();
+ const newbranch = {
+ source_id: workflow.triggers[selectedTriggerIndex].id,
+ destination_id: e.target.value.id,
+ source: workflow.triggers[selectedTriggerIndex].id,
+ target: e.target.value.id,
+ has_errors: false,
+ id: branchId,
+ _id: branchId,
+ label: "Subflow",
+ decorator: true,
+ };
+
+ if (workflow.visual_branches !== undefined) {
+ if (workflow.visual_branches === null) {
+ workflow.visual_branches = [newbranch];
+ } else if (workflow.visual_branches.length === 0) {
+ workflow.visual_branches.push(newbranch);
+ } else {
+ const foundIndex = workflow.visual_branches.findIndex(
+ (branch) => branch.source_id === newbranch.source_id
+ );
+ if (foundIndex !== -1) {
+ const currentEdge = cy.getElementById(
+ workflow.visual_branches[foundIndex].id
+ );
+ if (
+ currentEdge !== undefined &&
+ currentEdge !== null
+ ) {
+ currentEdge.remove();
+ }
+ }
+
+ workflow.visual_branches.splice(foundIndex, 1);
+ workflow.visual_branches.push(newbranch);
+ }
+ }
+
+ if (workflow.id === subworkflow.id) {
+ const cybranch = {
+ group: "edges",
+ source: newbranch.source_id,
+ target: newbranch.destination_id,
+ id: branchId,
+ data: newbranch,
+ };
+
+ cy.add(cybranch);
+ }
+
+ console.log("Value to be set: ", e.target.value);
+ try {
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[3].value = e.target.value.id;
+ } catch {
+ workflow.triggers[selectedTriggerIndex].parameters[3] =
+ {
+ name: "startnode",
+ value: e.target.value.id,
+ };
+ }
+
+ setWorkflow(workflow);
+ }
+
+ const handleWorkflowSelectionUpdate = (e) => {
+ setUpdate(Math.random());
+
+ if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) {
+ return null
+ }
+
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[0].value = e.target.value.id;
+ setSubworkflow(e.target.value);
+
+ // Sets the startnode
+ if (e.target.value.id !== workflow.id) {
+ console.log("WORKFLOW: ", e.target.value);
+
+ const startnode = e.target.value.actions.find(
+ (action) => action.id === e.target.value.start
+ );
+ if (startnode !== undefined && startnode !== null) {
+ console.log("STARTNODE: ", startnode);
+ setSubworkflowStartnode(startnode);
+
+ try {
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[3].value = startnode.id;
+ } catch {
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[3] = {
+ name: "startnode",
+ value: startnode.id,
+ };
+ }
+
+ setWorkflow(workflow);
+ }
+ console.log("STARTNODE: ", startnode);
+ } else {
+ console.log("WORKFLOW: ", workflow);
+ }
+
+ setWorkflow(workflow);
+ }
+
+ return (
+
+
+
+
+
+ Name
+
+
+
+
+
+
+ Delay
+ {
+ if (isNaN(event.target.value)) {
+ console.log("NAN: ", event.target.value)
+ return
+ }
+
+ const parsedNumber = parseInt(event.target.value)
+ if (parsedNumber > 86400) {
+ console.log("Max number is 1 day (86400)")
+ return
+ }
+
+ selectedTrigger.execution_delay = parseInt(event.target.value)
+ setSelectedTrigger(selectedTrigger)
+ }}
+ />
+
+
+
+
+
+
{
+ const newvalue =
+ workflow.triggers[selectedTriggerIndex].parameters[4] ===
+ undefined ||
+ workflow.triggers[selectedTriggerIndex].parameters[4]
+ .value === "false"
+ ? "true"
+ : "false";
+ workflow.triggers[selectedTriggerIndex].parameters[4] = {
+ name: "check_result",
+ value: newvalue,
+ };
+
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ }}
+ color="primary"
+ value="Wait for results"
+ />
+ }
+ style={{ marginTop: 10 }}
+ label={Wait for results
}
+ />
+
+
+
+
+
+
+ Select a workflow to execute
+
+
+ {workflow.triggers[selectedTriggerIndex].parameters[0].value
+ .length === 0 ? null : workflow.triggers[selectedTriggerIndex]
+ .parameters[0].value === props.match.params.key ? null : (
+
+ )}
+
+ {workflows === undefined ||
+ workflows === null ||
+ workflows.length === 0 ? null : (
+
option.id === value.id}
+ getOptionLabel={(option) => {
+ if (
+ option === undefined ||
+ option === null ||
+ option.name === undefined ||
+ option.name === null
+ ) {
+ return "No Workflow Selected";
+ }
+
+ const newname = (
+ option.name.charAt(0).toUpperCase() + option.name.substring(1)
+ ).replaceAll("_", " ");
+ return newname;
+ }}
+ options={workflows}
+ fullWidth
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ height: 50,
+ borderRadius: theme.palette.borderRadius,
+ }}
+ onChange={(event, newValue) => {
+ handleWorkflowSelectionUpdate({ target: { value: newValue } })
+ }}
+ renderOption={(data, index) => {
+ if (data.id === workflow.id) {
+ data = workflow;
+ }
+
+ //key={index}
+ return (
+
+ {data.image !== undefined && data.image !== null && data.image.length > 0 ?
+
+ : null}
+
+ Choose {data.name}
+
+
+ } placement="bottom">
+
+ {data.name}
+
+
+ )
+ }}
+ renderInput={(params) => {
+ return (
+
+ );
+ }}
+ />
+ )}
+
+ {subworkflow === undefined ||
+ subworkflow === null ||
+ subworkflow.id === undefined ||
+ subworkflow.actions === null ||
+ subworkflow.actions === undefined ||
+ subworkflow.actions.length === 0 ? null : (
+
+
+
+ Select the Startnode
+
+
+ option.id === value.id}
+ getOptionLabel={(option) => {
+ if (
+ option === undefined ||
+ option === null ||
+ option.label === undefined ||
+ option.label === null
+ ) {
+ if (option.length === 36) {
+
+ }
+
+ return "TMP";
+ }
+
+ const newname = (
+ option.label.charAt(0).toUpperCase() + option.label.substring(1)
+ ).replaceAll("_", " ");
+ return newname;
+ }}
+ options={subworkflow.actions}
+ fullWidth
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ height: 50,
+ borderRadius: theme.palette.borderRadius,
+ }}
+ onChange={(event, newValue) => {
+ handleSubflowStartnodeSelection({ target: { value: newValue } })
+ }}
+ renderOption={(action) => {
+ const isParent = getParents(selectedTrigger).find(
+ (parent) => parent.id === action.id
+ )
+
+ return (
+ {
+ if (subworkflow.id === workflow.id) {
+ handleActionHover(true, action.id)
+ }
+ }}
+ onMouseOut={() => {
+ if (subworkflow.id === workflow.id) {
+ handleActionHover(false, action.id)
+ }
+ }}
+ disabled={isCloud && isParent}
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ color: isParent ? "red" : "white",
+ }}
+ value={action}
+ >
+ {action.label}
+
+ );
+ }}
+ renderInput={(params) => {
+ return (
+
+ );
+ }}
+ />
+
+ )}
+
+
+ Execution Argument
+
+
+
+
+ {
+ setMenuPosition({
+ top: event.pageY + 10,
+ left: event.pageX + 10,
+ });
+ //setShowDropdownNumber(3)
+ setShowDropdown(true);
+ }}
+ />
+
+
+ ),
+ }}
+ rows="6"
+ multiline
+ fullWidth
+ color="primary"
+ placeholder="Some execution data"
+ defaultValue={
+ workflow.triggers[selectedTriggerIndex].parameters[1].value
+ }
+ onBlur={(e) => {
+ workflow.triggers[selectedTriggerIndex].parameters[1].value =
+ e.target.value;
+ setWorkflow(workflow);
+ }}
+ />
+ {showDropdown ? (
+
+ ) : null}
+ {/*
+
+ {
+ workflow.triggers[selectedTriggerIndex].parameters[2].value =
+ e.target.value;
+ setWorkflow(workflow);
+ }}
+ />
+ */}
+
+
+
+ );
+ }
+
+ return null;
+ };
+
+ const CommentSidebar = () => {
+ if (Object.getOwnPropertyNames(selectedComment).length > 0) {
+ /*
+ if (workflow.triggers[selectedTriggerIndex] === undefined) {
+ return null
+ }
+
+ 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": "url", "value": referenceUrl+"webhook_"+selectedTrigger.id}
+ workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "tmp", "value": "webhook_"+selectedTrigger.id}
+ workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "auth_headers", "value": ""}
+ setWorkflow(workflow)
+ } else {
+ if (selectedTrigger.environment !== "cloud") {
+ const newUrl = referenceUrl+"webhook_"+selectedTrigger.id
+ if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) {
+ console.log("Url is wrong - should update. This functionality is temporarily disabled.")
+ //workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl
+ //setWorkflow(workflow)
+ }
+ }
+ }
+
+ const trigger_header_auth = workflow.triggers[selectedTriggerIndex].parameters.length > 2 ? workflow.triggers[selectedTriggerIndex].parameters[2].value : ""
+ */
+
+ return (
+
+
+
+
Name
+
{
+ selectedComment.label = event.target.value;
+ setSelectedComment(selectedComment);
+ }}
+ />
+
+
+
Height
+
{
+ selectedComment.height = event.target.value;
+ setSelectedComment(selectedComment);
+ }}
+ />
+
+
+
Width
+
{
+ selectedComment.width = event.target.value;
+ setSelectedComment(selectedComment);
+ }}
+ />
+
+
+
+
+
Background
+
{
+ selectedComment.backgroundcolor = event.target.value;
+ setSelectedComment(selectedComment);
+ }}
+ />
+
+
+
Text Color
+
{
+ selectedComment.color = event.target.value;
+ setSelectedComment(selectedComment);
+ }}
+ />
+
+
+ Background-Image
+ {
+ selectedComment.backgroundimage = event.target.value;
+ console.log("Comment: ", selectedComment)
+ setSelectedComment(selectedComment);
+ }}
+ />
+
+ );
+ }
+
+ return null;
+ };
+
+ const WebhookSidebar = () => {
+ if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
+ if (workflow.triggers[selectedTriggerIndex] === undefined) {
+ return null;
+ }
+
+ 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: "url",
+ value: referenceUrl + "webhook_" + selectedTrigger.id,
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[1] = {
+ name: "tmp",
+ value: "webhook_" + selectedTrigger.id,
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[2] = {
+ name: "auth_headers",
+ value: "",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[3] = {
+ name: "custom_response_body",
+ value: "",
+ };
+ setWorkflow(workflow);
+ } else {
+ // Always update
+ const newUrl = referenceUrl + "webhook_" + selectedTrigger.id;
+ //console.log("Validating webhook url: ", newUrl);
+ if (selectedTrigger.environment !== "cloud") {
+ if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) {
+ console.log("Url is wrong. NOT updating because of hybrid.");
+ //workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl;
+ //setWorkflow(workflow);
+ }
+ }
+ }
+
+ const trigger_header_auth =
+ workflow.triggers[selectedTriggerIndex].parameters.length > 2
+ ? workflow.triggers[selectedTriggerIndex].parameters[2].value
+ : "";
+
+ return (
+
+
+
+
Name
+
+ {apps !== undefined && apps !== null && apps.length > 0 ?
+
+
{
+ //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;
+ }}
+ options={sortByKey(apps, "name")}
+ fullWidth
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ height: 50,
+ borderRadius: theme.palette.borderRadius,
+ }}
+ onChange={(event, newValue) => {
+ // Workaround with event lol
+ console.log(event, newValue)
+ if (newValue !== undefined && newValue !== null) {
+ var parsedvalue = JSON.parse(JSON.stringify(newValue))
+ parsedvalue.actions = []
+ parsedvalue.authentication = {}
+ selectedTrigger.app_association = parsedvalue
+ setUpdate(Math.random());
+ }
+ // setNewSelectedAction({
+ // target: {
+ // value: newValue.name
+ // }
+ // });
+ //}
+ }}
+ renderOption={(app) => {
+ var appname = app.name.replaceAll("_", " ")
+ appname = appname.charAt(0).toUpperCase() + appname.substring(1)
+
+ return (
+
+
+
+
+
+
+
+ {appname}
+
+
+
+
+ )
+ }}
+ renderInput={(params) => {
+ return (
+
+ );
+ }}
+ />
+
+
+ : null}
+ {selectedTrigger.status === "running" ? null :
+
+ Environment
+ {
+ selectedTrigger.environment = e.target.value;
+ if (e.target.value === "cloud") {
+ const tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/");
+ const urlpath = tmpvalue.slice(3, tmpvalue.length);
+ const newurl = "https://shuffler.io/" + urlpath.join("/");
+ workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl;
+ } else {
+ const tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/");
+ const urlpath = tmpvalue.slice(3, tmpvalue.length);
+ const newurl = window.location.origin + "/" + urlpath.join("/");
+ workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl;
+ }
+
+ console.log("New value: ", workflow.triggers[selectedTriggerIndex].parameters[0])
+ selectedTrigger.parameters[0] = workflow.triggers[selectedTriggerIndex].parameters[0]
+ setSelectedTrigger(selectedTrigger);
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ }}
+ style={{
+ backgroundColor: inputColor,
+ color: "white",
+ height: 50,
+ }}
+ >
+ {triggerEnvironments.map((data) => {
+ if (data.archived) {
+ return null;
+ }
+
+ return (
+
+ {data}
+
+ );
+ })}
+
+
+ }
+
+
+
+
Parameters
+
+
{
+ }}
+ helperText={
+ workflow.triggers[selectedTriggerIndex].parameters[0].value !== undefined &&
+ workflow.triggers[selectedTriggerIndex].parameters[0].value !== null &&
+ (workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[0].value.includes("localhost") ||
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[0].value.includes("127.0.0.1")) ? (
+
+ PS: This does NOT work with localhost. Use your local IP
+ instead.
+
+ ) : null
+ }
+ InputProps={{
+ style: {
+ color: "white",
+ height: 50,
+ marginLeft: "5px",
+ maxWidth: "95%",
+ fontSize: "1em",
+ },
+ endAdornment:
+
+ {
+ var copyText = document.getElementById("webhook_uri_field");
+ if (copyText !== undefined && copyText !== null) {
+ console.log("NAVIGATOR: ", navigator);
+ const clipboard = navigator.clipboard;
+ if (clipboard === undefined) {
+ alert.error("Can only copy over HTTPS (port 3443)");
+ return;
+ }
+
+ navigator.clipboard.writeText(copyText.value);
+ copyText.select();
+ copyText.setSelectionRange(
+ 0,
+ 99999
+ ); /* For mobile devices */
+
+ /* Copy the text inside the text field */
+ document.execCommand("copy");
+ alert.success("Copied Webhook URL");
+ } else {
+ console.log("Couldn't find webhook URI field: ", copyText);
+ }
+ }}
+ edge="end"
+ >
+
+
+
+ }}
+ fullWidth
+ disabled
+ value={
+ workflow.triggers[selectedTriggerIndex].parameters[0].value
+ }
+ color="primary"
+ placeholder="defaultValue"
+ onBlur={(e) => {
+ setTriggerCronWrapper(e.target.value);
+ }}
+ />
+
+ {
+ newWebhook(workflow.triggers[selectedTriggerIndex]);
+ }}
+ color="primary"
+ >
+ Start
+
+ {
+ deleteWebhook(selectedTrigger, selectedTriggerIndex);
+ }}
+ color="primary"
+ >
+ Stop
+
+
+
+
+
+
+ Authentication headers
+
+
+
+
+
+
+
+ Custom Response
+
+
+
+
+ {isCloud && workflow.triggers[selectedTriggerIndex].parameters.length > 4 ?
+
+ {
+ if (selectedTrigger.parameters === null) {
+ selectedTrigger.parameters = [];
+ }
+
+ // Sets the webhook to run as version 2.. kinda
+ var value = "v2"
+ if (workflow.triggers[selectedTriggerIndex].parameters[4].value.includes("v2")) {
+ value = "v1"
+ }
+
+ workflow.triggers[selectedTriggerIndex].parameters[4] = {
+ name: "await_response",
+ value: value
+ }
+
+ setWorkflow(workflow)
+ setUpdate(Math.random())
+ }}
+ color="primary"
+ value="await_response"
+ />
+ }
+ label={Wait For Response
}
+ />
+
+ : null}
+
+
+
+ );
+ }
+
+ return null;
+ };
+
+ const stopMailSub = (trigger, triggerindex) => {
+ // DELETE
+ if (trigger.id === undefined) {
+ return;
+ }
+
+ alert.info("Stopping mail trigger");
+ const requesttype = triggerAuthentication.type;
+ fetch(
+ `${globalUrl}/api/v1/workflows/${props.match.params.key}/${requesttype}/${trigger.id}`,
+ {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error("Status not 200 for stream results :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success) {
+ alert.success("Successfully stopped trigger");
+ // Set the status
+ workflow.triggers[triggerindex].status = "stopped";
+ trigger.status = "stopped";
+ setWorkflow(workflow);
+ setSelectedTrigger(trigger);
+ saveWorkflow(workflow);
+ } else {
+ alert.error("Failed stopping trigger: " + responseJson.reason);
+ }
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("Stop mailsub error: ", error.toString());
+ });
+ };
+
+ const startMailSub = (trigger, triggerindex) => {
+ var folders = [];
+
+ if (triggerFolders === null || triggerFolders === undefined) {
+ return null;
+ }
+
+ const splitItem =
+ workflow.triggers[selectedTriggerIndex].parameters[0].value.split(
+ splitter
+ )
+
+ console.log("Starting mail sub: ", workflow.triggers[selectedTriggerIndex].parameters[0].value, splitItem);
+ for (let splitkey in splitItem) {
+ const item = splitItem[splitkey];
+ const curfolder = triggerFolders.find((a) => a.displayName === item);
+ if (curfolder === undefined) {
+ alert.error("Something went wrong with folder selection: " + item);
+ return;
+ }
+
+ folders.push(curfolder.id);
+ }
+
+ const data = {
+ name: trigger.name,
+ folders: folders,
+ id: trigger.id,
+ };
+
+ const requesttype = triggerAuthentication.type;
+ alert.info(
+ "Creating " + requesttype + " subscription with name " + trigger.name
+ );
+
+ fetch(
+ globalUrl +
+ "/api/v1/workflows/" +
+ props.match.params.key +
+ "/" +
+ requesttype,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(data),
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for stream results :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (!responseJson.success) {
+ alert.error("Failed to start trigger: " + responseJson.reason);
+ } else {
+ alert.success(
+ "Successfully started folder subscription trigger. Test it by sending yoursend an email"
+ );
+
+ workflow.triggers[triggerindex].status = "running";
+ trigger.status = "running";
+ setWorkflow(workflow);
+ setSelectedTrigger(trigger);
+ saveWorkflow(workflow);
+ }
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ console.log("Start mailsub error: ", error.toString());
+ });
+ };
+
+ const newWebhook = (trigger) => {
+ const hookname = trigger.label;
+ if (hookname.length === 0) {
+ alert.error("Missing name");
+ return;
+ }
+
+ if (trigger.id.length !== 36) {
+ alert.error("Missing id");
+ return;
+ }
+
+ // Check the node it's connected to
+ var startNode = workflow.start;
+ const branch = workflow.branches.find(
+ (branch) => branch.source_id === trigger.id
+ );
+ if (branch === undefined && (workflow.start === undefined || workflow.start === null || workflow.start.length === 0)) {
+ alert.error("No webhook node defined");
+ }
+
+ alert.info("Starting webhook");
+ if (branch !== undefined) {
+ startNode = branch.destination_id;
+ }
+
+ const param = trigger.parameters.find((param) => param.name === "auth_headers");
+ var auth = "";
+ if (param !== undefined && param !== null) {
+ auth = param.value;
+ }
+
+
+ // Version: v2 = await response for 30 sec
+ const await_resp = trigger.parameters.find((param) => param.name === "await_response");
+ var version = "";
+ if (await_resp !== undefined && await_resp !== null) {
+ version = await_resp.value;
+ }
+
+ const customRespParam = trigger.parameters.find(
+ (param) => param.name === "custom_response_body"
+ )
+ var custom_response = "";
+ if (customRespParam !== undefined && customRespParam !== null) {
+ custom_response = customRespParam.value;
+ }
+
+ const data = {
+ name: hookname,
+ type: "webhook",
+ id: trigger.id,
+ workflow: workflow.id,
+ start: startNode,
+ environment: trigger.environment,
+ auth: auth,
+ custom_response: custom_response,
+ version: version,
+ version_timeout: 15,
+ };
+
+ console.log("Trigger data: ", data)
+
+ fetch(globalUrl + "/api/v1/hooks/new", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify(data),
+ credentials: "include",
+ })
+ .then((response) => response.json())
+ .then((responseJson) => {
+ if (responseJson.success) {
+ // Set the status
+ alert.success("Successfully started webhook");
+ trigger.status = "running";
+ setSelectedTrigger(trigger);
+ workflow.triggers[selectedTriggerIndex].status = "running";
+ setWorkflow(workflow);
+ saveWorkflow(workflow);
+ } else {
+ alert.error("Failed starting webhook: " + responseJson.reason);
+ }
+ })
+ .catch((error) => {
+ //console.log(error.toString());
+ console.log("New webhook error: ", error.toString());
+ });
+ };
+
+ const deleteWebhook = (trigger, triggerindex) => {
+ if (trigger.id === undefined) {
+ return;
+ }
+
+ fetch(globalUrl + "/api/v1/hooks/" + trigger.id + "/delete", {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for stream results :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (workflow.triggers[triggerindex] !== undefined) {
+ workflow.triggers[triggerindex].status = "stopped";
+ }
+
+ if (responseJson.success) {
+ // Set the status
+ saveWorkflow(workflow);
+ } else {
+ if (responseJson.reason !== undefined) {
+ alert.error("Failed stopping webhook: " + responseJson.reason);
+ }
+ }
+
+ trigger.status = "stopped";
+ setWorkflow(workflow);
+ setSelectedTrigger(trigger);
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ alert.error("Delete webhook error. Contact support or check logs if this persists.")
+ });
+ };
+
+ 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: "hello this is an alert",
+ };
+
+ // boolean,
+ workflow.triggers[selectedTriggerIndex].parameters[1] = {
+ name: "options",
+ value: "boolean",
+ };
+
+ // email,sms,app ...
+ workflow.triggers[selectedTriggerIndex].parameters[2] = {
+ name: "type",
+ value: "email",
+ };
+
+ 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 (
+
+
+
+
Name
+
+
+
+ Environment:
+
+
+
+
+
Parameters
+
+
{
+ setTriggerTextInformationWrapper(e.target.value);
+ }}
+ />
+
+
+
+ Contact options
+
+
+
+ {
+ setTriggerOptionsWrapper("subflow");
+ }}
+ color="primary"
+ value="subflow"
+ disabled
+ />
+ }
+ label={Subflow
}
+ />
+ {
+ setTriggerOptionsWrapper("email");
+ }}
+ color="primary"
+ value="email"
+ />
+ }
+ label={Email
}
+ />
+ {
+ setTriggerOptionsWrapper("sms");
+ }}
+ color="primary"
+ value="sms"
+ />
+ }
+ label={SMS
}
+ />
+
+ {workflow.triggers[selectedTriggerIndex].parameters[2] !==
+ undefined &&
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[2].value.includes("email") ? (
+ {
+ workflow.triggers[selectedTriggerIndex].parameters[3].value =
+ event.target.value;
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ }}
+ />
+ ) : null}
+ {workflow.triggers[selectedTriggerIndex].parameters[2] !==
+ undefined &&
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[2].value.includes("sms") ? (
+ {
+ workflow.triggers[selectedTriggerIndex].parameters[4].value =
+ event.target.value;
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ }}
+ />
+ ) : null}
+ {workflow.triggers[selectedTriggerIndex].parameters[2] !==
+ undefined &&
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[2].value.includes("subflow") ? (
+ {
+ workflow.triggers[selectedTriggerIndex].parameters[5].value =
+ event.target.value;
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ }}
+ />
+ ) : null}
+
+
+ );
+ }
+
+ return null;
+ };
+
+ const ScheduleSidebar = () => {
+ 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: "cron",
+ value: isCloud ? "*/25 * * * *" : "60",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[1] = {
+ name: "execution_argument",
+ value: '{"example": {"json": "is cool"}}',
+ };
+ setWorkflow(workflow);
+ }
+
+ return (
+
+
+
+
Name
+
+
+ Environment
+ {
+ selectedTrigger.environment = e.target.value;
+ setSelectedTrigger(selectedTrigger);
+ if (e.target.value === "cloud") {
+ console.log("Set cloud config");
+ workflow.triggers[selectedTriggerIndex].parameters[0].value =
+ "*/25 * * * *";
+ } else {
+ console.log("Set cloud config");
+
+ workflow.triggers[selectedTriggerIndex].parameters[0].value =
+ "60";
+ }
+
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ }}
+ style={{
+ backgroundColor: inputColor,
+ color: "white",
+ height: 50,
+ }}
+ >
+ {triggerEnvironments.map((data) => {
+ if (data.archived) {
+ return null;
+ }
+
+ return (
+
+ {data}
+
+ );
+ })}
+
+
+
+
+
+
Parameters
+
+
{
+ setTriggerCronWrapper(e.target.value);
+ }}
+ />
+ {/*selectedTrigger.environment === "cloud" ?
+
+ :
+ null
+ */}
+
+
+
+ Execution argument:
+
+
+ {
+ setTriggerBodyWrapper(e.target.value);
+ }}
+ />
+
+
+ {
+ submitSchedule(selectedTrigger, selectedTriggerIndex);
+ }}
+ color="primary"
+ >
+ Start
+
+ {
+ stopSchedule(selectedTrigger, selectedTriggerIndex);
+ }}
+ color="primary"
+ >
+ Stop
+
+
+
+
+
+ );
+ }
+
+ return null;
+ };
+
+ const cytoscapeViewWidths = isMobile ? 50 : 850;
+ const bottomBarStyle = {
+ position: "fixed",
+ right: isMobile ? 20 : 20,
+ bottom: isMobile ? undefined : 0,
+ top: isMobile ? appBarSize + 55 : undefined,
+ left: isMobile ? undefined : leftBarSize,
+ minWidth: cytoscapeViewWidths,
+ maxWidth: cytoscapeViewWidths,
+ marginLeft: 20,
+ marginBottom: 20,
+ zIndex: 10,
+ };
+
+ const topBarStyle = {
+ position: "fixed",
+ right: 0,
+ left: isMobile ? 20 : leftBarSize + 20,
+ top: isMobile ? 30 : appBarSize + 20,
+ };
+
+ const TopCytoscapeBar = (props) => {
+ if (workflow.public === true) {
+ return null
+ }
+
+ return (
+
+
+
+
+
+
+ Workflows
+
+
+ {workflow.name}
+
+
+
+ );
+ };
+
+ const WorkflowMenu = () => {
+ const [newAnchor, setNewAnchor] = React.useState(null);
+ const [showShuffleMenu, setShowShuffleMenu] = React.useState(false);
+
+ return (
+
+
}
+ control={
+ {
+ workflow.configuration.exit_on_error =
+ !workflow.configuration.exit_on_error;
+ setWorkflow(workflow);
+ setUpdate(
+ "exit_on_error_" + workflow.configuration.exit_on_error
+ ? "true"
+ : "false"
+ );
+ }}
+ />
+ }
+ />
+ Start from top}
+ control={
+ {
+ workflow.configuration.start_from_top =
+ !workflow.configuration.start_from_top;
+ setWorkflow(workflow);
+ setUpdate(
+ "start_from_top_" + workflow.configuration.start_from_top
+ ? "true"
+ : "false"
+ );
+ }}
+ />
+ }
+ />
+
+
+ {/*
+
+
+ {
+ setShowShuffleMenu(!showShuffleMenu);
+ setNewAnchor(event.currentTarget);
+ }}
+ >
+
+
+
+
+ */}
+ {isMobile ?
+
+
+ {
+ console.log("Show apps!")
+ setLeftBarSize(leftViewOpen ? 0 : 60)
+ setLeftViewOpen(!leftViewOpen)
+ }}
+ >
+
+
+
+
+ : null}
+
+ );
+ };
+
+ 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 handleHistoryUndo = () => {
+ //console.log("history: ", history, "index: ", historyIndex);
+ var item = history[historyIndex - 1];
+ if (historyIndex === 0) {
+ item = history[historyIndex];
+ }
+
+ if (item === undefined) {
+ console.log("Couldn't find the action you're looking for");
+ return;
+ }
+
+ //console.log("HANDLE: ", item);
+ if (item.type === "node" && item.action === "removed") {
+ // Re-add the node
+ console.log("Item: ", item.data)
+
+ const edge = cy.getElementById(item.data.id).json()
+ if (edge !== null && edge !== undefined) {
+ console.log("Couldn't add node as it exists")
+ return
+ }
+
+ cy.add({
+ group: "nodes",
+ data: item.data,
+ position: item.data.position,
+ });
+ } else if (item.action === "added") {
+ //console.log("Should remove item!");
+ const currentitem = cy.getElementById(item.data.id);
+ if (currentitem !== undefined && currentitem !== null) {
+ currentitem.remove();
+ }
+
+ } else if (item.type === "edge" && item.action === "removed") {
+ const sourcenode = cy.getElementById(item.data.source)
+ const targetnode = cy.getElementById(item.data.target)
+ if (sourcenode === undefined || sourcenode === null || targetnode === undefined || targetnode === null) {
+ console.log("Can't readd bad edge!")
+ return
+ }
+
+ const edge = cy.getElementById(item.data.id).json()
+ if (edge !== null && edge !== undefined) {
+ console.log("Couldn't add edge as it exists")
+ return
+ }
+
+ cy.add({
+ group: "edges",
+ data: item.data,
+ });
+
+ } else {
+ console.log("UNHANDLED: ", item)
+ }
+
+ if (historyIndex > 0) {
+ setHistoryIndex(historyIndex - 1);
+ }
+ };
+
+ const BottomCytoscapeBar = () => {
+ if (workflow.id === undefined || workflow.id === null || (!workflow.public && apps.length === 0)) {
+ return null;
+ }
+
+ const boxSize = isMobile ? 50 : 100;
+ const executionButton = executionRunning ? (
+
+
+ {
+ abortExecution();
+ }}
+ >
+
+
+
+
+ ) : (
+
+
+ {
+ executeWorkflow(executionText, workflow.start, lastSaved);
+ }}
+ >
+
+
+
+
+ );
+
+ return (
+
+ {executionButton}
+
+ {isMobile || workflow.public ? null : (
+
+ {
+ setExecutionText(e.target.value);
+ }}
+ />
+
+ )}
+ {/*userdata.avatar === creatorProfile.github_avatar ? null :*/}
+
+
+ {
+ saveWorkflow()
+
+ if (workflow.public === true) {
+ console.log("Public!")
+
+ const tmpurl = new URL(window.location.href)
+ const searchParams = tmpurl.searchParams
+ const queryID = searchParams.get('queryID')
+
+ if (queryID !== undefined && queryID !== null) {
+ aa('init', {
+ appId: "JNSS5CFDZZ",
+ apiKey: "db08e40265e2941b9a7d8f644b6e5240",
+ })
+
+ const timestamp = new Date().getTime()
+ aa('sendEvents', [
+ {
+ eventType: 'conversion',
+ eventName: 'Public Workflow Saved',
+ index: 'workflows',
+ objectIDs: [workflow.id],
+ timestamp: timestamp,
+ queryID: queryID,
+ userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
+ }
+ ])
+ } else {
+ console.log("No query to handle")
+ }
+ }
+ }}
+ >
+ {savingState === 2 ? (
+
+ ) : savingState === 1 ? (
+
+ ) : (
+
+ )}
+
+
+
+ {workflow.public ? (
+
+
+ {
+ const data = workflow;
+ let exportFileDefaultName = data.name + ".json";
+
+ let dataStr = JSON.stringify(data);
+ let dataUri =
+ "data:application/json;charset=utf-8," +
+ encodeURIComponent(dataStr);
+ let linkElement = document.createElement("a");
+ linkElement.setAttribute("href", dataUri);
+ linkElement.setAttribute("download", exportFileDefaultName);
+ linkElement.click();
+ }}
+ >
+
+
+
+
+ ) : null}
+
+
+ cy.fit(null, 50)}
+ >
+
+
+
+
+
+
+ {
+ handleHistoryUndo(history);
+ }}
+ >
+
+
+
+
+
+
+ {
+ const selectedNode = cy.$(":selected");
+ if (selectedNode.data() === undefined) {
+ return
+ }
+
+ removeNode(selectedNode.data("id"))
+ }}
+ >
+
+
+
+
+
+
+ {
+ setExecutionModalOpen(true);
+ getWorkflowExecution(props.match.params.key, "");
+ }}
+ >
+ {/* */}
+
+ {/* */}
+
+
+
+
+
+ {
+ addCommentNode();
+ }}
+ >
+
+
+
+
+ {workflow.configuration !== null &&
+ workflow.configuration !== undefined &&
+ workflow.configuration.exit_on_error !== undefined ? (
+
+ ) : null}
+
+
+ {
+ console.log("SHOW EDIT VIEW!")
+
+ setEditWorkflowModalOpen(true)
+ }}
+ >
+
+
+
+
+
+
+ );
+ };
+
+ const addCommentNode = () => {
+ const newId = uuidv4();
+ const position = {
+ x: 300,
+ y: 300,
+ };
+
+ cy.add({
+ group: "nodes",
+ data: {
+ id: newId,
+ label: "Click to write a comment",
+ type: "COMMENT",
+ is_valid: true,
+ decorator: true,
+ width: 250,
+ height: 150,
+ position: position,
+ backgroundcolor: "#1f2023",
+ color: "#ffffff",
+ },
+ position: position,
+ });
+ };
+
+ 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) {
+ defaultReturn =
+ } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
+ if (selectedTrigger.trigger_type === "SCHEDULE") {
+ defaultReturn =
+ } else if (selectedTrigger.trigger_type === "WEBHOOK") {
+ defaultReturn =
+ } else 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;
+ } else {
+ console.log(
+ "Unable to handle invalid trigger type " +
+ selectedTrigger.trigger_type
+ );
+ return null;
+ }
+ } else if (Object.getOwnPropertyNames(selectedEdge).length > 0) {
+ defaultReturn =
+ }
+
+ const iOS = typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent);
+
+ const drawerBleeding = 56;
+ if (defaultReturn === undefined || defaultReturn === null) {
+ return null
+ }
+
+ return (
+ isMobile ?
+ {
+ console.log("Close!")
+ //setRightSideBarOpen(false)
+ cy.elements().unselect()
+ }}
+ disableSwipeToOpen={false}
+ ModalProps={{
+ keepMounted: true,
+ }}
+ PaperProps={{
+ style: {
+ maxHeight: "70%",
+ overflow: "auto",
+ }
+ }}
+ >
+ {defaultReturn}
+
+ :
+
+
+ {defaultReturn}
+
+
+ );
+
+ //return null;
+ };
+
+ // This can execute a workflow with firestore. Used for test, as datastore is old and stuff
+ // Too much work to move everything over alone, so won't touch it for now
+ // {
+ // executeWorkflowWebsocket()
+ //}}>Execute websocket
+ //
+
+ // A list used for FRONTEND handling of whether a public workflow
+ // should be change-able
+ const allowList = ["frikky", "m1nk-code", "DavidtheGoliath"]
+ // console.log(allowList, userdata.public_username)
+
+ const leftView = workflow.public === true ?
+
+
+ {workflow.name}
+
+
+ This workflow is public and {
+ saveWorkflow()
+ }}>must be saved or exported before use.
+
+ {Object.getOwnPropertyNames(creatorProfile).length !== 0 && creatorProfile.github_avatar !== undefined && creatorProfile.github_avatar !== null ?
+
+
{
+ }}>
+
+
+
+
+
+ Shared by {creatorProfile.github_username}
+
+
+ : null}
+
+ {workflow.tags !== undefined && workflow.tags !== null && workflow.tags.length > 0 ?
+
+
+ Tags
+
+
+ {workflow.tags.map((tag, index) => {
+ if (index >= 3) {
+ return null;
+ }
+
+ return (
+
+ );
+ })}
+
+
+ : null}
+
+ {workflow.blogpost !== undefined && workflow.blogpost !== null && workflow.blogpost.length > 0 ?
+
+ : null
+ }
+
+ {appGroup.length > 0 ?
+
+
+
+ Apps
+
+
+ {appGroup.map((data, index) => {
+ return (
+
+
+
+ )
+ })}
+
+
+
+ : null}
+
+ {triggerGroup.length > 0 ?
+
+
+ Triggers
+
+
+ {triggerGroup.map((data, index) => {
+ return (
+
+ )
+ })}
+
+
+ : null}
+
+ {/*
+
+
+ Mitre Att&ck:
+
+
+ TBD
+
+
+ */}
+
+ {/*
+
+
+ Related Workflows:
+
+
+ TBD
+
+
+ */}
+
+ {workflow.video !== undefined && workflow.video !== null && workflow.video.length > 0 ?
+
+
+ Video
+
+ {
+ workflow.video.includes("loom.com/share") && workflow.video.split("/").length > 4 ?
+
+
+
+ :
+ workflow.video.includes("youtube.com") && workflow.video.split("/").length > 3 && workflow.video.includes("v=")
+ ?
+
+ VIDEO
+
+ :
+
+ {workflow.video}
+
+ }
+
+ : null}
+
+ {workflow.description !== undefined && workflow.description !== null && workflow.description.length > 0 ?
+
+
+ Description
+
+
+ {workflow.description}
+
+
+ : null}
+
+ {/*
+
+ {
+ setSelectionOpen(true)
+ }}
+ >
+ Statistics
+
+
+ */}
+ {userdata.avatar !== undefined && (userdata.avatar === creatorProfile.github_avatar || allowList.includes(userdata.public_username)) ?
+
+
+ You can see these buttons because you may have the correct access rights as a creator to help modify this workflow.
+
+ {
+ // Further checks are being done on the backend
+ // Even if the user can "edit" a workflow on the frontend,
+ // that doesn't necessarily mean anything
+
+ //setEditWorkflowDetails(true)
+ workflow.public = false
+ setUserediting(true)
+ setWorkflow(workflow)
+
+ getAppAuthentication();
+ getEnvironments();
+ getWorkflowExecution(props.match.params.key, "");
+ getAvailableWorkflows(-1);
+ getSettings();
+ getFiles()
+
+ setUpdate(Math.random());
+ }}
+ >
+ Edit Workflow
+
+ {
+ // setEditWorkflowDetails(true)
+ // workflow.public = false
+ // setWorkflow(workflow)
+ // setUpdate(Math.random());
+ }}
+ >
+ Unpublish Workflow
+
+
+ : null}
+
+ : isMobile && leftViewOpen ?
+
+
+
+ : leftViewOpen ? (
+
+
+
+ ) : (
+
+
{
+ setLeftViewOpen(true);
+ setLeftBarSize(350);
+ }}
+ >
+
+
+
+
+
+ );
+
+ const executionPaperStyle = {
+ minWidth: "95%",
+ maxWidth: "95%",
+ marginTop: "5px",
+ color: "white",
+ marginBottom: 10,
+ padding: 5,
+ backgroundColor: surfaceColor,
+ cursor: "pointer",
+ display: "flex",
+ minHeight: 40,
+ maxHeight: 40,
+ };
+
+ const parsedExecutionArgument = () => {
+ var showResult = executionData.execution_argument.trim();
+ const validate = validateJson(showResult);
+
+ if (validate.valid) {
+ if (typeof validate.result === "string") {
+ try {
+ validate.result = JSON.parse(validate.result);
+ } catch (e) {
+ console.log("Error: ", e);
+ validate.valid = false;
+ }
+ }
+
+ return (
+
+
{
+ if (validate.valid) {
+ //console.log("Find and change result: ",
+ //const oldstartnode = cy.getElementById(selectedResult.action.id);
+ //if (oldstartnode !== undefined && oldstartnode !== null) {
+ // const foundname = oldstartnode.data("label")
+ // if (foundname !== undefined && foundname !== null) {
+ // result.action.label = foundname
+ // }
+ //}
+ }
+
+ setSelectedResult({
+ "action": {
+ "label": "Execution Argument",
+ "name": "Execution Argument",
+ "large_image": theme.palette.defaultImage,
+ "image": theme.palette.defaultImage,
+ },
+ "result": validate.valid ? JSON.stringify(validate.result) : validate.result,
+ "status": "SUCCESS"
+ })
+
+ setCodeModalOpen(true);
+ }}
+ >
+
+
+
+
+
{
+ handleReactJsonClipboard(copy);
+ }}
+ displayDataTypes={false}
+ onSelect={(select) => {
+ HandleJsonCopy(validate.result, select, "exec");
+ }}
+ name={"Execution Argument"}
+ />
+
+ )
+ }
+
+ return (
+
+
Execution Argument
+
+ {executionData.execution_argument}
+
+
+ );
+ };
+
+ const getExecutionSourceImage = (execution) => {
+ // 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==";
+ const size = 40;
+ if (
+ execution.execution_source === undefined ||
+ execution.execution_source === null ||
+ execution.execution_source.length === 0
+ ) {
+ return (
+
+ );
+ }
+
+ if (execution.execution_source === "webhook") {
+ return (
+ trigger.trigger_type === "WEBHOOK")
+ .large_image
+ }
+ style={{ width: size, height: size }}
+ />
+ );
+ } else if (execution.execution_source === "outlook") {
+ return (
+ trigger.trigger_type === "EMAIL")
+ .large_image
+ }
+ style={{ width: size, height: size }}
+ />
+ );
+ } else if (execution.execution_source === "schedule") {
+ return (
+ trigger.trigger_type === "SCHEDULE")
+ .large_image
+ }
+ style={{ width: size, height: size }}
+ />
+ );
+ } else if (execution.execution_source === "EMAIL") {
+ return (
+ trigger.trigger_type === "EMAIL")
+ .large_image
+ }
+ style={{ width: size, height: size }}
+ />
+ );
+ }
+
+ if (
+ execution.execution_parent !== null &&
+ execution.execution_parent !== undefined &&
+ execution.execution_parent.length > 0
+ ) {
+ return (
+ trigger.trigger_type === "SUBFLOW")
+ .large_image
+ }
+ style={{ width: size, height: size }}
+ />
+ );
+ }
+
+ return (
+
+ );
+ };
+
+ const handleReactJsonClipboard = (copy) => {
+ console.log("COPY: ", copy);
+
+ const elementName = "copy_element_shuffle";
+ var copyText = document.getElementById(elementName);
+ if (copyText !== null && copyText !== undefined) {
+ if (
+ copy.namespace !== undefined &&
+ copy.name !== undefined &&
+ copy.src !== undefined
+ ) {
+ copy = copy.src;
+ }
+
+ const clipboard = navigator.clipboard;
+ if (clipboard === undefined) {
+ alert.error("Can only copy over HTTPS (port 3443)");
+ return;
+ }
+
+ var stringified = JSON.stringify(copy);
+ if (stringified.startsWith('"') && stringified.endsWith('"')) {
+ stringified = stringified.substring(1, stringified.length - 1);
+ }
+
+ navigator.clipboard.writeText(stringified);
+ copyText.select();
+ copyText.setSelectionRange(0, 99999); /* For mobile devices */
+
+ /* Copy the text inside the text field */
+ document.execCommand("copy");
+
+ console.log("COPYING!");
+ alert.info("Copied value to clipboard, NOT json path.")
+ } else {
+ console.log("Failed to copy from " + elementName + ": ", copyText);
+ }
+ };
+
+ const HandleJsonCopy = (base, copy, base_node_name) => {
+ if (typeof copy.name === "string") {
+ copy.name = copy.name.replaceAll(" ", "_");
+ }
+
+ //lol
+ if (typeof base === 'object' || typeof base === 'dict') {
+ base = JSON.stringify(base)
+ }
+
+ if (base_node_name === "execution_argument" || base_node_name === "Execution Argument") {
+ base_node_name = "exec"
+ }
+
+ console.log("COPY: ", base_node_name, copy);
+
+ //var newitem = JSON.parse(base);
+ var newitem = validateJson(base).result
+
+ // Check if base_node_name has changed
+ if (cy !== undefined && cy !== null) {
+ console.log("Change name?")
+ //const allNodes = cy.nodes().jsons();
+ //for (var key in allNodes) {
+ //const currentNode = allNodes[key];
+
+ //if (currentNode.
+ //}
+
+ //const nodedata = cy.getElementById(data.action.id).data();
+ //base_node_name =
+ }
+
+ to_be_copied = "$" + base_node_name.toLowerCase().replaceAll(" ", "_");
+ for (let copykey in copy.namespace) {
+ if (copy.namespace[copykey].includes("Results for")) {
+ continue;
+ }
+
+ if (newitem !== undefined && newitem !== null) {
+ newitem = newitem[copy.namespace[copykey]];
+ if (!isNaN(copy.namespace[copykey])) {
+ to_be_copied += ".#";
+ } else {
+ to_be_copied += "." + copy.namespace[copykey];
+ }
+ }
+ }
+
+ if (newitem !== undefined && newitem !== null) {
+ newitem = newitem[copy.name];
+ if (!isNaN(copy.name)) {
+ to_be_copied += ".#";
+ } else {
+ to_be_copied += "." + copy.name;
+ }
+ }
+
+ to_be_copied.replaceAll(" ", "_");
+ const elementName = "copy_element_shuffle";
+ var copyText = document.getElementById(elementName);
+ if (copyText !== null && copyText !== undefined) {
+ console.log("NAVIGATOR: ", navigator);
+ const clipboard = navigator.clipboard;
+ if (clipboard === undefined) {
+ alert.error("Can only copy over HTTPS (port 3443)");
+ return;
+ }
+
+ navigator.clipboard.writeText(to_be_copied);
+ copyText.select();
+ copyText.setSelectionRange(0, 99999); /* For mobile devices */
+
+ /* Copy the text inside the text field */
+ document.execCommand("copy");
+ console.log("COPYING!");
+ alert.info("Copied JSON path to clipboard.")
+ } else {
+ console.log("Couldn't find element ", elementName);
+ }
+ }
+
+ // Not used because of issue with state updates.
+ const ShowReactJsonField = (props) => {
+ const { validate, jsonValue, collapsed, label, autocomplete } = props
+
+ const [parsedCollapse, setParsedCollapse] = React.useState(collapsed)
+ const [open, setOpen] = React.useState(false);
+ const [anchorPosition, setAnchorPosition] = React.useState({
+ top: 750,
+ left: 16,
+ });
+
+ const isFirstRender = React.useRef(true)
+ useEffect(() => {
+ console.log("IN useeffectt " + autocomplete)
+
+ if (isFirstRender.current) {
+ isFirstRender.current = false;
+ console.log("IN useeffectt (2)" + collapsed)
+ return;
+ }
+ })
+ /*
+ componentWillUpdate = (nextProps, nextState) => {
+ console.log(nextProps, nextState)
+ //nextState.value = nextProps.a + nextProps.b;
+ }
+ */
+
+ const jsonRef = React.useRef()
+
+ return (
+
+ {
+ console.log("FIELD: ", field)
+ }}
+ enableClipboard={(copy) => {
+ handleReactJsonClipboard(copy);
+ }}
+ displayDataTypes={false}
+ onClick={(event) => {
+ const pos = {
+ top: event.screenX,
+ left: event.screenY,
+ }
+
+ console.log("POS CLICK: ", pos)
+
+ setAnchorPosition(pos)
+ }}
+ onSelect={(select) => {
+ setOpen(true)
+
+ setTimeout(() => {
+ setOpen(false)
+ }, 2500)
+
+ //setAnchorPosition({
+ // top: 300,
+ // right: 300,
+ //})
+ //setAnchorEl(jsonRef.current)
+ HandleJsonCopy(jsonValue, select, autocomplete);
+ console.log("SELECTED!: ", select);
+ }}
+ name={label}
+ />
+ {anchorPosition !== null ?
+ {
+ setAnchorPosition({
+ top: 750,
+ left: 16,
+ })
+ }}
+ disableRestoreFocus
+ >
+
+ Copying
+
+
+ : null}
+
+ )
+ }
+
+ const ShowCopyingTooltip = () => {
+ const [showCopying, setShowCopying] = React.useState(true)
+
+ if (!showCopying) {
+ return false
+ }
+
+ return (
+
+
+
+ )
+ }
+
+ var nonskippedResults = []
+ if (executionData.results !== undefined) {
+ const newSkipped = executionData.results.find((result) => result.status !== "SKIPPED")
+ if (newSkipped !== undefined) {
+ nonskippedResults = newSkipped
+ }
+ }
+
+
+ var executionDelay = -75
+ const executionModal = (
+ {
+ setExecutionModalOpen(false)
+ }}
+ style={{ resize: "both", overflow: "auto", zIndex: 10005 }}
+ hideBackdrop={false}
+ variant="temporary"
+ BackdropProps={{
+ style: {
+ //backgroundColor: "transparent",
+ }
+ }}
+ PaperProps={{
+ style: {
+ resize: "both",
+ overflow: "auto",
+ minWidth: isMobile ? "100%" : 420,
+ maxWidth: isMobile ? "100%" : 420,
+ backgroundColor: "#1F2023",
+ color: "white",
+ fontSize: 18,
+ zIndex: 10005,
+ borderLeft: theme.palette.defaultBorder,
+ },
+ }}
+ >
+ {isMobile ?
+
+ {
+ e.preventDefault();
+ setExecutionModalOpen(false)
+ }}
+ >
+
+
+
+ : null}
+ {executionModalView === 0 ? (
+
+
+
+
+ All Executions
+
+
+
{
+ getWorkflowExecution(props.match.params.key, "");
+ }}
+ color="primary"
+ >
+
+ Refresh executions
+
+
+ {workflowExecutions.length > 0 ? (
+
+ {workflowExecutions.map((data, index) => {
+ executionDelay += 50
+
+ const statusColor =
+ data.status === "FINISHED"
+ ? green : data.status === "ABORTED" || data.status === "FAILED"
+ ? "red"
+ : yellow;
+ const resultsLength =
+ data.results !== undefined && data.results !== null
+ ? data.results.length
+ : 0;
+
+ const timestamp = new Date(data.started_at * 1000)
+ .toLocaleString("en-GB")
+ .split(".")[0]
+ .split("T")
+ .join(" ");
+
+ var calculatedResult =
+ data.workflow.actions !== undefined &&
+ data.workflow.actions !== null
+ ? data.workflow.actions.length
+ : 0;
+
+ if (data.workflow.triggers !== undefined && data.workflow.triggers !== null) {
+ for (let triggerkey in data.workflow.triggers) {
+ const trigger = data.workflow.triggers[triggerkey];
+ if (
+ (trigger.app_name === "User Input" &&
+ trigger.trigger_type === "USERINPUT") ||
+ (trigger.app_name === "Shuffle Workflow" &&
+ trigger.trigger_type === "SUBFLOW")
+ ) {
+ calculatedResult += 1;
+ }
+ }
+ }
+
+ return (
+
+
+
+ { }}
+ onMouseOut={() => { }}
+ onClick={() => {
+ if (
+ (data.result === undefined ||
+ data.result === null ||
+ data.result.length === 0) &&
+ data.status !== "FINISHED" &&
+ data.status !== "ABORTED"
+ ) {
+ start();
+ setExecutionRunning(true);
+ setExecutionRequestStarted(false);
+ }
+
+
+ // Ensuring we have the latest version of the result.
+ // Especially important IF the result is > 1 Mb in cloud
+ 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);
+
+ console.log("Clearing colors during click for: !", data)
+
+ 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);
+ }
+ }}
+ >
+
+
+
+ {getExecutionSourceImage(data)}
+
+
+ {timestamp}
+
+ {data.workflow.actions !== null ? (
+
+
+ {resultsLength}/{calculatedResult}
+
+
+ ) : null}
+
+
+ {lastExecution === data.execution_id ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ );
+ })}
+
+ ) : (
+
There are no executions yet, or they are not loaded.
+ )}
+
+ ) : (
+
+
+ {
+ setExecutionRunning(false);
+ stop();
+ getWorkflowExecution(props.match.params.key, "");
+ setExecutionModalView(0);
+ setLastExecution(executionData.execution_id);
+ }}
+ >
+ { }}
+ >
+
+
+ { }}
+ >
+ See other Executions
+
+
+
+
+
+
Execution info
+
+
+ {
+ //console.log("DATA: ", executionData);
+ executeWorkflow(
+ executionData.execution_argument,
+ executionData.start,
+ lastSaved
+ )
+
+ if (executionText === undefined || executionText === null || executionText.length === 0) {
+ setExecutionText(executionData.execution_argument)
+ }
+
+ setExecutionModalOpen(false);
+ }}
+ >
+
+
+
+
+ {executionData.status === "EXECUTING" ? (
+
+
+ {
+ abortExecution();
+ }}
+ >
+
+
+
+
+ ) : null}
+
+ {executionData.status !== undefined &&
+ executionData.status.length > 0 ? (
+
+
+ Status
+
+
+ {executionData.status}
+
+
+ ) : null}
+ {executionData.execution_source !== undefined &&
+ executionData.execution_source !== null &&
+ executionData.execution_source.length > 0 &&
+ executionData.execution_source !== "default" ? (
+
+
+ Source
+
+
+ {executionData.execution_parent !== null &&
+ executionData.execution_parent !== undefined &&
+ executionData.execution_parent.length > 0 ? (
+ executionData.execution_source === props.match.params.key ? (
+ {
+ getWorkflowExecution(
+ props.match.params.key,
+ executionData.execution_parent
+ );
+ }}
+ >
+ Parent Execution
+
+ ) : (
+
+ Parent Workflow
+
+ )
+ ) : (
+ executionData.execution_source
+ )}
+
+
+ ) : null}
+ {executionData.started_at !== undefined ? (
+
+
+ Started
+
+
+ {new Date(executionData.started_at * 1000).toLocaleString("en-GB")}
+
+
+ ) : null}
+ {executionData.completed_at !== undefined &&
+ executionData.completed_at !== null &&
+ executionData.completed_at > 0 ? (
+
+ {
+ console.log(executionData)
+ }}>
+ Finished
+
+
+ {new Date(executionData.completed_at * 1000).toLocaleString("en-GB")}
+
+
+ ) : null}
+
+ {executionData.execution_argument !== undefined &&
+ executionData.execution_argument.length > 0
+ ? parsedExecutionArgument()
+ : null}
+
+ {executionData.results !== undefined &&
+ executionData.results !== null &&
+ executionData.results.length > 1 &&
+ executionData.results.find(
+ (result) =>
+ result.status === "SKIPPED"
+ ) ? (
+
+ Show skipped actions
+
+ }
+ control={
+ {
+ setShowSkippedActions(!showSkippedActions);
+ }}
+ />
+ }
+ />
+ ) : null}
+
+
+ {executionData.status !== undefined &&
+ executionData.status !== "ABORTED" &&
+ executionData.status !== "FINISHED" &&
+ executionData.status !== "FAILURE" &&
+ executionData.status !== "WAITING" &&
+ !(executionData.results === undefined || executionData.results === null || (executionData.results.length === 0 && executionData.status === "EXECUTING")) ? (
+
+
{
+ console.log(environments, defaultEnvironmentIndex, nonskippedResults)
+ }} />
+
+ {environments.length > 0 && defaultEnvironmentIndex < environments.length && nonskippedResults.length === 0 && environments[defaultEnvironmentIndex].Name !== "Cloud" ?
+
+ No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? Learn more . If the Workflow doesn't start within 30 seconds with Orborus running, contact support: support@shuffler.io
+
+ : null}
+
+ ) : null}
+
+
+ {
+ executionData.results === undefined ||
+ executionData.results === null ||
+ (executionData.results.length === 0 && executionData.status === "EXECUTING") ? (
+
+
+
+ {environments.length > 0 && defaultEnvironmentIndex < environments.length && nonskippedResults.length === 0 && environments[defaultEnvironmentIndex].Name !== "Cloud" ?
+
+ No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? Learn more . If the Workflow doesn't start within 30 seconds with Orborus running, contact support: support@shuffler.io
+
+ :
+ null}
+
+ ) : (
+ executionData.results.map((data, index) => {
+ if (executionData.results.length !== 1 && !showSkippedActions && (data.status === "SKIPPED")) {
+ return null;
+ }
+
+ // FIXME: The latter replace doens't really work if ' is used in a string
+ var showResult = data.result.trim();
+ const validate = validateJson(showResult);
+
+ const curapp = apps.find(
+ (a) =>
+ a.name === data.action.app_name &&
+ a.app_version === data.action.app_version
+ );
+ const imgsize = 50;
+ const statusColor =
+ data.status === "FINISHED" || data.status === "SUCCESS"
+ ? green
+ : data.status === "ABORTED" || data.status === "FAILURE"
+ ? "red"
+ : yellow;
+
+ var imgSrc = curapp === undefined ? "" : curapp.large_image;
+ if (
+ imgSrc.length === 0 &&
+ workflow.actions !== undefined &&
+ workflow.actions !== null
+ ) {
+ // Look for the node in the workflow
+ const action = workflow.actions.find(
+ (action) => action.id === data.action.id
+ );
+ if (action !== undefined && action !== null) {
+ imgSrc = action.large_image;
+ }
+ }
+
+ var actionimg =
+ curapp === null ? null : (
+
+ );
+
+ if (triggers.length > 2) {
+ if (data.action.app_name === "shuffle-subflow") {
+ const parsedImage = triggers[2].large_image;
+ actionimg = (
+
+ );
+ }
+
+ if (data.action.app_name === "User Input") {
+ actionimg = (
+
+ );
+ }
+ }
+
+ if (
+ data.action.app_name === "Shuffle Tools" &&
+ data.action.id !== undefined &&
+ cy !== undefined
+ ) {
+ const nodedata = cy.getElementById(data.action.id).data();
+ if (
+ nodedata !== undefined &&
+ nodedata !== null &&
+ nodedata.fillstyle === "linear-gradient"
+ ) {
+ var imgStyle = {
+ marginRight: 20,
+ width: imgsize,
+ height: imgsize,
+ border: `2px solid ${statusColor}`,
+ borderRadius:
+ executionData.start === data.action.id ? 25 : 5,
+ background: `linear-gradient(to right, ${nodedata.fillGradient})`,
+ };
+
+ actionimg = (
+
+ );
+ }
+ }
+
+ if (validate.valid && typeof validate.result === "string") {
+ validate.result = JSON.parse(validate.result);
+ }
+
+ if (validate.valid && typeof validate.result === "object") {
+ if (
+ validate.result.result !== undefined &&
+ validate.result.result !== null
+ ) {
+ try {
+ validate.result.result = JSON.parse(validate.result.result);
+ } catch (e) {
+ //console.log("ERROR PARSING: ", e)
+ }
+ }
+ }
+
+
+ var similarActionsView = null
+ if (data.similar_actions !== undefined && data.similar_actions !== null) {
+ var minimumMatch = 85
+ var matching_executions = []
+ if (data.similar_actions !== undefined && data.similar_actions !== null) {
+ for (let [k,kval] in Object.entries(data.similar_actions)){
+ if (data.similar_actions.hasOwnProperty(k)) {
+ if (data.similar_actions[k].similarity > minimumMatch) {
+ matching_executions.push(data.similar_actions[k].execution_id)
+ }
+ }
+ }
+ }
+
+ if (matching_executions.length !== 0) {
+ var parsed_url = matching_executions.join(",")
+
+ similarActionsView =
+
+ {
+ navigate(`?execution_highlight=${parsed_url}`)
+ }}
+ >
+
+
+
+ }
+ }
+
+
+ return (
+ {
+ var currentnode = cy.getElementById(data.action.id);
+ if (currentnode !== undefined && currentnode !== null && currentnode.length !== 0) {
+ currentnode.addClass("shuffle-hover-highlight");
+ }
+
+ // Add a hover highlight
+
+ //var copyText = document.getElementById(
+ // "copy_element_shuffle"
+ //)
+ }}
+ onMouseOut={() => {
+ var currentnode = cy.getElementById(data.action.id);
+ if (currentnode.length !== 0) {
+ currentnode.removeClass("shuffle-hover-highlight");
+ }
+ }}
+ >
+
+
+
{
+ const oldstartnode = cy.getElementById(data.action.id);
+ console.log("FOUND NODe: ", oldstartnode)
+ if (oldstartnode !== undefined && oldstartnode !== null) {
+ const foundname = oldstartnode.data("label")
+ if (foundname !== undefined && foundname !== null) {
+ data.action.label = foundname
+ }
+ }
+
+ console.log("Click data: ", data)
+ //data.action.label = ""
+ setSelectedResult(data);
+ setCodeModalOpen(true);
+ }}
+ >
+
+
+
+
+ {actionimg}
+
+
+ {data.action.label}
+
+
+
+ {data.action.name}
+
+
+
+
+ {data.action.app_name === "shuffle-subflow" &&
+ validate.result.success !== undefined &&
+ validate.result.success === true ? (
+
+ {validate.valid &&
+ data.action.parameters !== undefined &&
+ data.action.parameters !== null &&
+ data.action.parameters.length > 0 ? (
+ data.action.parameters[0].value ===
+ props.match.params.key ? (
+ {
+ getWorkflowExecution(
+ props.match.params.key,
+ validate.result.execution_id
+ );
+ }}
+ >
+
+
+ ) : (
+ { }}
+ >
+
+
+ )
+ ) : (
+ ""
+ )}
+
+ ) : null}
+
+
+
+ Status
+
+
+ {data.status}
+
+ {similarActionsView}
+
+ {validate.valid ? (
+
+ {
+ handleReactJsonClipboard(copy);
+ }}
+ displayDataTypes={false}
+ onSelect={(select) => {
+ HandleJsonCopy(showResult, select, data.action.label);
+ console.log("SELECTED!: ", select);
+ }}
+ name={"Results for " + data.action.label}
+ />
+
+
+ ) : (
+
+
+ Result
+
+
+ {data.result}
+
+
+ )}
+
+ );
+ })
+ )}
+
+ )}
+
+ );
+
+ // This sucks :)
+ const curapp = !codeModalOpen
+ ? {}
+ : selectedResult.action.app_name === "shuffle-subflow"
+ ? triggers[2]
+ : selectedResult.action.app_name === "User Input"
+ ? triggers[3]
+ : apps.find(
+ (a) =>
+ a.name === selectedResult.action.app_name &&
+ a.app_version === selectedResult.action.app_version
+ );
+ const imgsize = 50;
+ const statusColor = !codeModalOpen
+ ? "red"
+ : selectedResult.status === "FINISHED" ||
+ selectedResult.status === "SUCCESS"
+ ? green
+ : selectedResult.status === "ABORTED" || selectedResult.status === "FAILURE"
+ ? "red"
+ : yellow;
+
+ const validate = !codeModalOpen
+ ? ""
+ : validateJson(selectedResult.result.trim());
+
+ if (validate.valid && typeof validate.result === "string") {
+ validate.result = JSON.parse(validate.result);
+ }
+
+ const AppResultVariable = ({ data }) => {
+ const [open, setOpen] = React.useState(false)
+ const showVariable = data.value.length < 60
+
+ return (
+
+ {data.value.length > 60 ?
+ {
+ if (!showVariable) {
+ setOpen(!open)
+ }
+ }}
+ >
+
+ {data.name} {showVariable ? data.value : null}
+
+
+ :
+
+ {data.name} : {showVariable ? data.value : null}
+
+ }
+ {open ?
+
+ {data.value}
+
+ : null}
+
+ )
+ }
+
+ var draggingDisabled = false;
+ const codePopoutModal = !codeModalOpen ? null : (
+
+
+
+ {
+ e.preventDefault();
+
+ if (workflowExecutions !== null) {
+ for (let execkey in workflowExecutions) {
+ const execution = workflowExecutions[execkey];
+ if (execution.execution_argument.includes("too large")) {
+ continue
+ }
+
+ const result = execution.results.find((data) => data.status === "SUCCESS" && data.action.id === selectedResult.action.id)
+
+ if (result !== undefined) {
+ const oldstartnode = cy.getElementById(selectedResult.action.id);
+ if (oldstartnode !== undefined && oldstartnode !== null) {
+ const foundname = oldstartnode.data("label")
+ if (foundname !== undefined && foundname !== null) {
+ result.action.label = foundname
+ }
+ }
+
+ setSelectedResult(result);
+ setUpdate(Math.random());
+ break;
+ }
+ }
+ }
+ }}
+ >
+
+
+
+
+ {
+ e.preventDefault();
+ for (let execkey in workflowExecutions) {
+ const execution = workflowExecutions[execkey];
+ const result = execution.results.find(
+ (data) =>
+ data.action.id === selectedResult.action.id &&
+ data.status !== "SUCCESS" &&
+ data.status !== "SKIPPED" &&
+ data.status !== "WAITING"
+ );
+
+ if (result !== undefined) {
+ const oldstartnode = cy.getElementById(selectedResult.action.id);
+ if (oldstartnode !== undefined && oldstartnode !== null) {
+ const foundname = oldstartnode.data("label")
+ if (foundname !== undefined && foundname !== null) {
+ result.action.label = foundname
+ }
+ }
+
+ setSelectedResult(result);
+ setUpdate(Math.random());
+ break;
+ }
+ }
+ }}
+ >
+
+
+
+
+ {
+ e.preventDefault();
+ const executionIndex = workflowExecutions.findIndex((data) => data.execution_id === selectedResult.execution_id);
+
+ if (executionIndex !== -1) {
+ setExecutionModalOpen(true);
+ setExecutionModalView(1);
+
+ if (workflowExecutions[executionIndex] !== undefined && workflowExecutions[executionIndex] !== null && workflowExecutions[executionIndex].execution_argument.includes("too large")) {
+ //checkStarted = true
+ setExecutionData({});
+ start();
+ setExecutionRunning(true);
+ setExecutionRequestStarted(false);
+ } else {
+ setExecutionData(workflowExecutions[executionIndex]);
+ }
+ }
+ }}
+ >
+
+
+
+
+ {
+ e.preventDefault();
+ setCodeModalOpen(false);
+ }}
+ >
+
+
+
+
+
+
+ {curapp === null ? null : (
+
+ )}
+
+
+
+ {selectedResult.action.label}
+
+
{selectedResult.action.name}
+
+
+
+ Status {selectedResult.status}
+
+ {validate.valid ? (
+
{
+ handleReactJsonClipboard(copy);
+ }}
+ displayDataTypes={false}
+ onSelect={(select) => {
+ HandleJsonCopy(validate.result, select, selectedResult.action.label);
+ }}
+ name={"Results for " + selectedResult.action.label}
+ />
+ ) : (
+
+ Result
+ {
+ console.log("IN HERE TO CLICK");
+ to_be_copied = selectedResult.result;
+ var copyText = document.getElementById(
+ "copy_element_shuffle"
+ );
+ console.log("PRECOPY: ", to_be_copied);
+ if (copyText !== null && copyText !== undefined) {
+ console.log("COPY: ", copyText);
+ console.log("NAVIGATOR: ", navigator);
+ const clipboard = navigator.clipboard;
+ if (clipboard === undefined) {
+ alert.error("Can only copy over HTTPS (port 3443)");
+ return;
+ }
+
+ navigator.clipboard.writeText(to_be_copied);
+
+ copyText.select();
+ copyText.setSelectionRange(
+ 0,
+ 99999
+ ); /* For mobile devices */
+
+ /* Copy the text inside the text field */
+ document.execCommand("copy");
+ } else {
+ console.log(
+ "Failed to copy. copy_element_shuffle is undefined"
+ );
+ }
+ }}
+ >
+ {selectedResult.result}
+
+
+ )}
+
+ {selectedResult.action.parameters !== null &&
+ selectedResult.action.parameters !== undefined ? (
+
+
+
+ Variables (click to expand)
+
+ {selectedResult.action.parameters.map((data, index) => {
+ if (data.value.length === 0) {
+ return null;
+ }
+
+ if (
+ data.example !== undefined &&
+ data.example !== null &&
+ data.example.includes("***")
+ ) {
+ return null;
+ }
+
+ return (
+
+ );
+ })}
+
+ ) : null}
+
+
+
+ );
+
+ const newView = (
+
+
+ {/*isMobile ? null : leftView*/}
+ {leftView}
+ {workflow.id === undefined ||
+ workflow.id === null ||
+ appsLoaded === false ? (
+
+
+
+ Loading Workflow
+
+
+ ) : (
+
+ {
+ // FIXME: There's something specific loading when
+ // you do the first hover of a node. Why is this different?
+ //console.log("CY: ", incy)
+ setCy(incy);
+ }}
+ />
+
+ )}
+
+ {executionModal}
+
+
+
+
+ );
+
+ const editWorkflowModal =
+ {
+ setEditWorkflowDetails(false)
+ }}
+ PaperProps={{
+ style: {
+ pointerEvents: "auto",
+ backgroundColor: surfaceColor,
+ color: "white",
+ border: theme.palette.defaultBorder,
+ maxWidth: "100%",
+ padding: 50,
+ },
+ }}
+ >
+ Edit workflow!
+
+
+ const ExecutionVariableModal = (props) => {
+ const { variableInfo } = props
+
+ const [newVariableName, setNewVariableName] = React.useState(variableInfo.name !== undefined ? variableInfo.name : "");
+ const [newVariableDescription, setNewVariableDescription] = React.useState(variableInfo.description !== undefined ? variableInfo.description : "");
+ const [newVariableValue, setNewVariableValue] = React.useState(variableInfo.value !== undefined ? variableInfo.value : "");
+
+ if (!executionVariablesModalOpen) {
+ return null
+ }
+
+ return (
+ {
+ setNewVariableName("");
+ setExecutionVariablesModalOpen(false);
+ }}
+ PaperProps={{
+ style: {
+ pointerEvents: "auto",
+ backgroundColor: surfaceColor,
+ color: "white",
+ border: theme.palette.defaultBorder,
+ maxWidth: "100%",
+ },
+ }}
+ >
+
+
+ Execution Variable
+
+
+ Execution Variables are TEMPORARY variables that you can only be set
+ and used during execution. Learn more{" "}
+
+ here
+
+ setNewVariableName(event.target.value)}
+ color="primary"
+ placeholder="Name"
+ style={{ marginTop: 25 }}
+ InputProps={{
+ style: {
+ color: "white",
+ },
+ }}
+ margin="dense"
+ fullWidth
+ defaultValue={newVariableName}
+ />
+
+
+ {
+ setNewVariableName("");
+ setExecutionVariablesModalOpen(false);
+ }}
+ color="primary"
+ >
+ Cancel
+
+ {
+ console.log("VARIABLES! ", newVariableName);
+ if (
+ workflow.execution_variables === undefined ||
+ workflow.execution_variables === null
+ ) {
+ workflow.execution_variables = [];
+ }
+
+ // try to find one with the same name
+ const found = workflow.execution_variables.findIndex(
+ (data) => data.name === newVariableName
+ );
+ //console.log(found)
+ if (found !== -1) {
+ if (newVariableName.length > 0) {
+ workflow.execution_variables[found].name = newVariableName;
+ }
+ } else {
+ workflow.execution_variables.push({
+ name: newVariableName,
+ description: "An execution variable",
+ value: "",
+ id: uuidv4(),
+ });
+ }
+
+ setExecutionVariablesModalOpen(false);
+ setNewVariableName("");
+ setWorkflow(workflow);
+ }}
+ color="primary"
+ >
+ Submit
+
+
+ {workflowExecutions.length > 0 ? (
+
+
+ Values from last 3 executions
+ {workflowExecutions.slice(0, 3).map((execution, index) => {
+ if (
+ execution.execution_variables === undefined ||
+ execution.execution_variables === null ||
+ execution.execution_variables === 0
+ ) {
+ return null;
+ }
+
+ const variable = execution.execution_variables.find(
+ (data) => data.name === newVariableName
+ )
+ if (variable === undefined || variable.value === undefined) {
+ return null;
+ }
+
+ return (
+
+ {index + 1}: {variable.value}
+
+ );
+ })}
+
+ ) : null}
+
+
+ )
+ }
+
+ const VariablesModal = (props) => {
+ const { setVariableInfo, variableInfo } = props
+
+ const [newVariableName, setNewVariableName] = React.useState(variableInfo.name !== undefined ? variableInfo.name : "");
+ const [newVariableDescription, setNewVariableDescription] = React.useState(variableInfo.description !== undefined ? variableInfo.description : "");
+ const [newVariableValue, setNewVariableValue] = React.useState(variableInfo.value !== undefined ? variableInfo.value : "");
+
+ if (!variablesModalOpen) {
+ return null
+ }
+
+ return (
+ {
+ setNewVariableName("");
+ setNewVariableDescription("");
+ setNewVariableValue("");
+ setVariablesModalOpen(false);
+ }}
+ PaperProps={{
+ style: {
+ pointerEvents: "auto",
+ backgroundColor: surfaceColor,
+ color: "white",
+ border: theme.palette.defaultBorder,
+ maxWidth: isMobile ? bodyWidth - 100 : "100%",
+ },
+ }}
+ >
+
+
+ Workflow Variable
+
+
+ setNewVariableName(event.target.value)}
+ color="primary"
+ placeholder="Name"
+ InputProps={{
+ style: {
+ color: "white",
+ },
+ }}
+ margin="dense"
+ fullWidth
+ defaultValue={newVariableName}
+ />
+ setNewVariableDescription(event.target.value)}
+ color="primary"
+ placeholder="Description"
+ margin="dense"
+ fullWidth
+ InputProps={{
+ style: {
+ color: "white",
+ },
+ }}
+ defaultValue={newVariableDescription}
+ />
+ setNewVariableValue(event.target.value)}
+ rows="6"
+ multiline
+ color="primary"
+ placeholder="Value"
+ margin="dense"
+ InputProps={{
+ style: {
+ color: "white",
+ },
+ }}
+ fullWidth
+ defaultValue={newVariableValue}
+ />
+
+
+ {
+ setNewVariableName("");
+ setNewVariableDescription("");
+ setNewVariableValue("");
+ setVariablesModalOpen(false);
+ }}
+ color="primary"
+ >
+ Cancel
+
+ {
+ var handled = false
+ if (
+ workflow.workflow_variables === undefined ||
+ workflow.workflow_variables === null
+ ) {
+ workflow.workflow_variables = [];
+ } else {
+ if (variableInfo.index !== undefined && variableInfo.index !== null && variableInfo.index >= 0) {
+ if (newVariableName.length > 0) {
+ workflow.workflow_variables[variableInfo.index].name = newVariableName;
+ }
+ if (newVariableDescription.length > 0) {
+ workflow.workflow_variables[variableInfo.index].description =
+ newVariableDescription;
+ }
+ if (newVariableValue.length > 0) {
+ workflow.workflow_variables[variableInfo.index].value = newVariableValue;
+ }
+
+ handled = true
+ }
+ }
+
+ if (!handled) {
+ // try to find one with the same name
+ const found = workflow.workflow_variables.findIndex(
+ (data) => data.name === newVariableName
+ );
+ if (found !== -1) {
+ if (newVariableName.length > 0) {
+ workflow.workflow_variables[found].name = newVariableName;
+ }
+ if (newVariableDescription.length > 0) {
+ workflow.workflow_variables[found].description =
+ newVariableDescription;
+ }
+ if (newVariableValue.length > 0) {
+ workflow.workflow_variables[found].value = newVariableValue;
+ }
+ } else {
+ workflow.workflow_variables.push({
+ name: newVariableName,
+ description: newVariableDescription,
+ value: newVariableValue,
+ id: uuidv4(),
+ });
+ }
+ }
+
+ setVariableInfo({})
+ setWorkflow(workflow);
+ setVariablesModalOpen(false);
+ setNewVariableName("");
+ setNewVariableDescription("");
+ setNewVariableValue("");
+ }}
+ color="primary"
+ >
+ Submit
+
+
+
+
+ )
+ }
+
+ const AuthenticationData = (props) => {
+ const selectedApp = props.app;
+
+ const [authenticationOption, setAuthenticationOptions] = React.useState({
+ app: JSON.parse(JSON.stringify(selectedApp)),
+ fields: {},
+ label: "",
+ usage: [
+ {
+ workflow_id: workflow.id,
+ },
+ ],
+ id: uuidv4(),
+ active: true,
+ });
+
+ if (
+ selectedApp.authentication === undefined ||
+ selectedApp.authentication.parameters === null ||
+ selectedApp.authentication.parameters === undefined ||
+ selectedApp.authentication.parameters.length === 0
+ ) {
+ return (
+
+
+ {selectedApp.name} does not require authentication
+
+
+ );
+ }
+
+ authenticationOption.app.actions = [];
+
+ for (let paramkey in selectedApp.authentication.parameters) {
+ if (
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[paramkey].name
+ ] === undefined
+ ) {
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[paramkey].name
+ ] = "";
+ }
+ }
+
+ const handleSubmitCheck = () => {
+ if (authenticationOption.label.length === 0) {
+ authenticationOption.label = `Auth for ${selectedApp.name}`;
+ }
+
+ // Automatically mapping fields that already exist (predefined).
+ // Warning if fields are NOT filled
+ for (let paramkey in selectedApp.authentication.parameters) {
+ if (
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[paramkey].name
+ ].length === 0
+ ) {
+ if (
+ selectedApp.authentication.parameters[paramkey].value !== undefined &&
+ selectedApp.authentication.parameters[paramkey].value !== null &&
+ selectedApp.authentication.parameters[paramkey].value.length > 0
+ ) {
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[paramkey].name
+ ] = selectedApp.authentication.parameters[paramkey].value;
+ } else {
+ if (
+ selectedApp.authentication.parameters[paramkey].schema.type === "bool"
+ ) {
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[paramkey].name
+ ] = "false";
+ } else {
+ alert.info(
+ "Field " +
+ selectedApp.authentication.parameters[paramkey].name +
+ " can't be empty"
+ );
+ return;
+ }
+ }
+ }
+ }
+
+ console.log("Action: ", selectedAction);
+ selectedAction.authentication_id = authenticationOption.id;
+ selectedAction.selectedAuthentication = authenticationOption;
+ if (
+ selectedAction.authentication === undefined ||
+ selectedAction.authentication === null
+ ) {
+ selectedAction.authentication = [authenticationOption];
+ } else {
+ selectedAction.authentication.push(authenticationOption);
+ }
+
+ setSelectedAction(selectedAction);
+
+ var newAuthOption = JSON.parse(JSON.stringify(authenticationOption));
+ var newFields = [];
+ console.log("Fields: ", newAuthOption.fields)
+ for (let authkey in newAuthOption.fields) {
+ const value = newAuthOption.fields[authkey];
+ newFields.push({
+ "key": authkey,
+ "value": value,
+ });
+ }
+
+ newAuthOption.fields = newFields;
+ setNewAppAuth(newAuthOption);
+
+ if (configureWorkflowModalOpen) {
+ setSelectedAction({});
+ }
+
+ setUpdate(authenticationOption.id);
+ };
+
+ if (
+ authenticationOption.label === null ||
+ authenticationOption.label === undefined
+ ) {
+ authenticationOption.label = selectedApp.name + " authentication";
+ }
+
+ return (
+
+
+
+ Authentication for {selectedApp.name}
+
+
+
+
+ What is app authentication?
+
+
+ These are required fields for authenticating with {selectedApp.name}
+
+ Name - what is this used for?
+ {
+ authenticationOption.label = event.target.value;
+ }}
+ />
+
+
+ {selectedApp.authentication.parameters.map((data, index) => {
+ return (
+
+
+ {data.name}
+
+ {data.schema !== undefined &&
+ data.schema !== null &&
+ data.schema.type === "bool" ? (
+ {
+ console.log("Value: ", e.target.value);
+ authenticationOption.fields[data.name] = e.target.value;
+ }}
+ style={{
+ backgroundColor: theme.palette.surfaceColor,
+ color: "white",
+ height: 50,
+ }}
+ >
+
+ false
+
+
+ true
+
+
+ ) : (
+ {
+ authenticationOption.fields[data.name] =
+ event.target.value;
+ }}
+ />
+ )}
+
+ );
+ })}
+
+
+ {
+ setAuthenticationModalOpen(false);
+ }}
+ color="primary"
+ >
+ Cancel
+
+ {
+ setAuthenticationOptions(authenticationOption);
+ handleSubmitCheck();
+ }}
+ color="primary"
+ >
+ Submit
+
+
+
+ );
+ };
+
+ const configureWorkflowModal =
+ configureWorkflowModalOpen && apps.length !== 0 ? (
+
+ {
+ setConfigureWorkflowModalOpen(false);
+ }}
+ >
+
+
+
+
+ ) : null;
+
+ // This whole part is redundant. Made it part of Arguments instead.
+ const authenticationModal = authenticationModalOpen ? (
+ {
+ //if (configureWorkflowModalOpen) {
+ // setSelectedAction({});
+ //}
+ }}
+ PaperProps={{
+ style: {
+ pointerEvents: "auto",
+ backgroundColor: surfaceColor,
+ color: "white",
+ minWidth: 1100,
+ minHeight: 700,
+ maxHeight: 700,
+ padding: 15,
+ overflow: "hidden",
+ zIndex: 10012,
+ border: theme.palette.defaultBorder,
+ },
+ }}
+ >
+
+ {selectedApp.reference_info === undefined ||
+ selectedApp.reference_info === null ||
+ selectedApp.reference_info.github_url === undefined ||
+ selectedApp.reference_info.github_url === null ||
+ selectedApp.reference_info.github_url.length === 0 ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ {
+ setAuthenticationModalOpen(false);
+ if (configureWorkflowModalOpen) {
+ setSelectedAction({});
+ }
+ }}
+ >
+
+
+
+
+ {authenticationType.type === "oauth2" ? (
+
+ ) : (
+
+ )}
+
+
+ {selectedApp.documentation === undefined ||
+ selectedApp.documentation === null ||
+ selectedApp.documentation.length === 0 ? (
+
+
+ {selectedApp.description}
+
+
+
+ There is currently no extended documentation available for this
+ app.
+
+
+ Want help help making or using this app?{" "}
+
+ Join the community on Discord!
+
+
+
+
+ Want to help change this app directly?
+
+ {selectedApp.reference_info === undefined ||
+ selectedApp.reference_info === null ||
+ selectedApp.reference_info.github_url === undefined ||
+ selectedApp.reference_info.github_url === null ||
+ selectedApp.reference_info.github_url.length === 0 ? (
+
+
+
+ Check it out on Github!
+
+
+
+ ) : (
+
+
+
+ Check it out on Github!
+
+
+
+ )}
+
+ ) : (
+
+ )}
+
+
+
+ ) : null;
+ const loadedCheck =
+ isLoaded && workflowDone ? (
+
+ {newView}
+
+
+ {conditionsModal}
+ {authenticationModal}
+ {codePopoutModal}
+ {configureWorkflowModal}
+ {editWorkflowModal}
+
+ {editWorkflowModalOpen === true ?
+
+ : null}
+
+ {/*selectionOpen === true ?
+
+
+ {
+ setSelectionOpen(false)
+ }}
+ >
+
+
+
+
+
+ : null*/}
+
+ {showVideo !== undefined && showVideo.length > 0 ?
+
+ {
+ setShowVideo("")
+ }}
+ >
+
+
+
+
+ : null}
+
+
+
+ ) : (
+
+ );
+
+ // Awful way of handling scroll
+ if (
+ scrollConfig !== undefined &&
+ setScrollConfig !== undefined &&
+ Object.getOwnPropertyNames(selectedAction).length !== 0
+ ) {
+ const rightSideActionView = document.getElementById("rightside_actions");
+ if (rightSideActionView !== undefined && rightSideActionView !== null) {
+ if (
+ scrollConfig.top !== 0 &&
+ scrollConfig.top !== undefined &&
+ scrollConfig.top !== 0
+ ) {
+ setTimeout(() => {
+ if (
+ scrollConfig.selected !== undefined &&
+ scrollConfig.selected !== null
+ ) {
+ const selectedField = document.getElementById(
+ scrollConfig.selected
+ );
+ if (selectedField !== undefined && selectedField !== null) {
+ selectedField.focus();
+ }
+ }
+ }, 5);
+ } else {
+ if (rightSideActionView.scrollTop !== scrollConfig.top) {
+ setScrollConfig({
+ top: rightSideActionView.scrollTop,
+ left: 0,
+ selected: "",
+ });
+ }
+ }
+ }
+ }
+
+ return (
+
+ {/* Removed due to missing react router features
+
+ */}
+ {loadedCheck}
+
+ );
+};
+
+export default AngularWorkflow;