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 {props.alt}; + } + + 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} +
+
+ + + + { + setOpen(false); + setAnchorEl(null); + }} + > + { + setOpen(false); + setVariableInfo({ + "name": variable.name, + "description": variable.description, + "value": variable.value, + "index": index, + }) + setVariablesModalOpen(true); + }} + key={"Edit"} + > + {"Edit"} + + { + deleteVariable(index); + setOpen(false); + }} + key={"Delete"} + > + {"Delete"} + + +
+
+ +
+ ); + })} +
+ +
+ + 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} +
+
+ + + + { + setOpen(false); + setAnchorEl(null); + }} + > + { + setOpen(false); + //setNewVariableName(variable.name); + setVariableInfo({ + "name": variable.name, + "description": variable.description, + "value": variable.value, + "index": index, + }) + setExecutionVariablesModalOpen(true); + }} + key={"Edit"} + > + {"Edit"} + + { + deleteExecutionVariable(index); + setOpen(false); + }} + key={"Delete"} + > + {"Delete"} + + +
+
+ +
+ ); + })} +
+ +
+
+
+ ); + }; + + 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); + + } + }} + > + + + {newAppname} + + {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 ( +