/* eslint-disable react/no-multi-comp */
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,
Circle as CircleIcon,
SquareFoot as SquareFootIcon,
} 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";
export const triggers = [
{
name: "Webhook",
type: "TRIGGER",
status: "uninitialized",
trigger_type: "WEBHOOK",
errors: null,
large_image:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4wYNAxEP4A5uKQAAGipJREFUeNrtXHt4lNWZf8853zf3SSZDEgIJJtxCEnLRLSkXhSKgTcEL6yLK1hZWWylVbO1q7SKsSu3TsvVZqF2g4haoT2m9PIU+gJVHtFa5NQRD5FICIUAumBAmc81cvss5Z/845MtkAskEDJRu3r8Y8n3nfc/vvOe9zyDOOQxScoRvtAA3Ew2C1Q8aBKsfNAhWP2gQrH7QIFj9oEGw+kGDYPWDBsHqBw2C1Q+SbrQAPSg+/ULoRkvTjf4uwOKMAeeAEMI4AaBuf7rRhG5kIs05Zxxh1AUQ5yymUkVFgLBFxhZzbw///wGLUyZ2zikLn2oIVJ3o+NtZ5Xyb5u/gmgYAyCTLLqdlRKajaFRqeZFtTA7C+BJk5MZo2Y0Ai3EOHGGshyIX393btnNv5FQjjSoIYyQRRDBgdOkxyriuc8aJzeIozMu4d2rG16YQm4UzhtANULHrDRZnDGHMGW/b9lHzxh3RxlZslrHFjDAG4JxziBcHAUIIAHHGWFRhqmYblZ3z7bmZc24HAM75dTZk1xUsThkiWPn84umVv/btrSF2K7aYOGPA+pYBYQQIs5hCo8qQGRNGP/9vpky3WPAfECyxseCntSef+6Xq8UupDk4Z9Jc7QohgzR+yDMsY9/OlzpIx1xOv6wSW2JJvb03tM78AxrHVzHWaiAJGAMA5F4p2KYzgnHMG3WVEEqGRGDbJhWt+kFpedN3wuh5gCTsVPHzyb9/9L845Nkmcsm5CEMw0yiIxzhg2ycgkAQemqFyniBBiMyOJXOYVRcNmuXjDMntBnmBx84PFOSCktvmOLHxR9fiJ1Ry/bYQxZ0wPhk3pLtek4tQJhda84cRpA8Y0bzBS3xz4tDZYXav5QlKKXTwcjxcNxyy3ZJVufkFKtQtG/whg1f77Lzy7K+U0Z/ztQwTTiIJN0rAFdw97+G5TRlrihjkAAuVzT8tbu1ve3s01SmzdsZaI5g1m/cudY158/Doo18CCJTbg2V158plfXLLocUjpoYhtdE7+T5bYx+WKaJNR2mW8GOMciERESBWuPXdq2brIuRbJYU3QTRqOFq37oWtSyUDjNZBHwQFhzCk9v3knkqV4Iy2QcpaMKfnf5fZxuZxSRhlgREwykSVMCCaEyLJkkhHGlFKuU3tBXvH/LncU5Okd0QRzzjk/v2kngAjKBpAGULPEOXv/8umJ7/+3lGLvUgeMuKKZhrpLNv6nKcPFKeUIYYxVVa2srKyurm5ra+Ocp6enl5WVTZo0yW63M8YQ40giyueeo4+u1HwhJEtG2IEwohFl/Gv/kTqhcECVa8CrDhd3HUhw/MCBUzbquYXxSFVVVb322mv19fWcc0IIAFBKt2/fnp2dvWjRorvuuosBA52ah6ePfPYbtc++KpnkrmNGiGm65739qRMKYSAt8ICBxTnCWPd3hGrqsNXEO2N0RLAeDKffNTHtjjLOmEBq+/bta9askSQpJSUFRKjVeafa29tXrlx57ty5b3/72wwYMDZkZrl76q3eTw5LDptwjpxxYjEFDp2gkRixWQbOLQ6UxooNh+sa1Yu++CvDOUeEDJ03AwAYYxjjysrKNWvW2Gw2i8VCKaWUMsYYY+LfJpPJ7Xa/8cYbW7duxRgzygAga96M7k6TI5OstHgi9ecN1jcTWOI6hOuamKp12V2EWEyz5g1LuTUfAIgkqaq6YcMGSZIwxoyxnssI4FJSUjZv3nz+/HkiSxwg5bYCS3YGUzUDMoQRjamR+maD9U0FFgAAKOfb4j8ijLiq2gvzsNlENR0A/vrXv545c8ZqtV4WqUuwcy5JUiAQePfddwGA6TpxWO1jb2GKGuf+EHDeye6m0ywEAKB6At19E+KM20ZmCwwA4NChQ8ncGsaY2WyuqakBAIwwAFhGZHLGoOsuckBIC3R08b6ZwAIEACyqAEJxJ80BITnNCSJPBmhpaSGEJIMXIcTr9YZCIRFkSSkO4Im4cI32uc7fJ1gCMdTjUvD4/I5Smnwk2R3TnvhybJYHdDcDBxYHAOKwivwu/r/VNp+xc5fL1Yu1iidKqdvtdjgcwBgA6MEIksilAjQAAAIO8pDUK+D4dw4WBwAwZ6bF6xFwQBIJ1zaI3QFAfn5+Msol4vuioiKEEKUMAMKnGvVAB4sqAIAIRhghgq25WWAsfTOBBQAA1lHZ8QER54xYzKEjdbHzF4kkAcC0adNSU1P7xIsxJsvytGnToNPYDX/kazmP3WcdORwY1/whzRfEZpM9PxcGMkMcqAheSOwoHNmtSMAByUT1Bi5s+yj3yflU1YYPH37fffdt3rw5IyND07TLLoUxjkQixcXFZWVlAIAJBoC020vTbi/llEbPtgQ/O+X75DAgZM0bBgBxd/MmAUtIbBuTYxuT03H8LLaZRbGYMyY5bK1v7U6/a5J93C3A2KJFi86ePfvxxx+73W6EEO8kYyURZ/l8Pr/f73K5OOcIIc4YcECECBZZ/zIjsU49AERefPHFAVpatFH1QNi3t4ZYLV1FAkJoROk4Vp9RMRmbTRjgjqlTFUU5fvx4OBxmjBFCJEmKx0uW5ZaWFoTQhAkTRJKEuspeHBgDQNehDD+AYCEEgJBleMbFXQeYonRFp5xjsxxrbus4cTZ9ZjkyyRjQpMmTJk2a5HA4CCHBYDAYDJrNXb17zrnZbD516tTkyZPdbrdQrk4uCGE80JWsAQdLtOYlp412RPz7jxK7pas/yDmxWiKnmwNVf3NNKpZTHUyn6RkZEyZMqKiomDlzJqX02LFjstwVNxFCOjo6gsHgV77ylXiwricNJFidymUvHOn96FPNF8Iy6YqBOCdWS6y5zbPrgGVYhn3sCM454xwB2Gy2iRMnyrJ84MABi8Ui7iPn3GKxnD59urCwMCcnh4kO/j8SWIAQZ4xYTObsjIvv7sMmU7euKufEYqJR5eJ7+yOnmx35t5jcKUh08TkvLS2tra09d+6c2Ww2Klyapn3++ecVFRX4RkwgDXyvDWPOmHvabTmL7lG9ASSR+L9yypAkSU57+wdVNQ8tC59sAIRwZ1S5cOFCWe6qiDLG7Hb70aNH33vvPQCgdMDd3/UGS+AFnOc+9VBGxRTN40+skXMOCBBBriml9nG5wAEwEuWtwsLCmTNnhkIhUWgWeFkslt///vfBYDDJDPwmA6sTMzT25e+4Z5TTmJI43kcZtphzn3jwEnaXHkcAsGDBApfLpeu6+CjcYlNT0zvvvCOwS2DSM0z7uwNLVIF7ExEhTimxmrMeuJPTbrYZEaIHw8Mevts2dgSnzIi/EUKU0pycnHvuuaejo8MwUowxh8Oxffv2pqYmQohRiTbsmiDOuZAqyUR9wMHinAuMMMaEkN7dEyKEa3rz5h0ovm6DEI0ptlHZ2YvuATFXFC8cxgDw4IMPZmdnK4piKJckScFg8Le//a1AhxAiwlRKaSwWi8ViQhOFVBhjQ85rBOvq0x3hvAkhuq7X1tZ++OGHxcXFM2fOFBF2IqyUIYJb3v4gWH1STnMa2SLCiCvaiMUPSE5bz2EYsf/U1NSHHnpo9erVZrNZGHVKqcPh+Oijj2bNmjV06NCqqqqzZ8+2trYGAgFFUcRVTU1NzcrKys/PLykpycvLEwbusrIlT1fTZBVGAWOsKMr777+/Y8eOc+fOeb3emTNnrlq16jICcQ4IKa3tRx75T70jiiQiDJPoS6fdXlr0Pz/svX+l6/rSpUtPnz4dX60XKqZpWjgcRgiJrodgLVRJ3EGbzTZ69OhZs2bNmjXL4XCI168Osn6DZSB18ODB1157ra6uzmw2WywW4dc3bNhg5LpdrzCGMD790uutf/hIdjl5nMvnjJX8eoWjaOSlSeTLkUB///79K1asEN3pS6IjZGi3IVjXxhASMlBKFUVRVTU7O3vBggWzZ88mhFydivXvBYECY2z9+vXPPfdcY2Ojy+Uym82Ct8fjOXnyJHSv/wqkAlV/a9uxV0qxG0hdsuvzZjqKRl6aXL6SiBhzzqdMmTJ58uR4S28cSbyNN8joPAKA1Wp1uVxer/fnP//5s88+29zcjDG+ijCtH2AJ4SKRyIoVK7Zs2eJwOERb1HBDlFLRgOl2whhzxhrXvgPxCQpCLKZYc4flPHYf9LDrl2UNAN/85jeFCvd3kwI4WZbdbndNTc3SpUurqqqEJx0QsARSsVhs+fLl+/btGzJkiGh/xj9gsVg+++wzADBiSGHIW9/5MFBdSzq77QIdqqgjFv+z5HJyyvrstosYNT8/v6KioqOjw1i/60jifJ+4gD0dNOdc13Wn0xmLxZ5//vmPP/64v3j17xquWrWqqqoqLS1N1/WEzXDOI5GI1+v1+/1CMuAcEay2+Zp/vZ3YrF1ICbs+pSzz3qnimWRYGzGqqKkaKAhQdF0PhUJ+vz8cDiuKEovFxEdVVRMgEyomy/JPfvKTQ4cOCfuV5PaTMvDCJG3ZsuVXv/qV2+1OQIoQEo1GAWDOnDmPPPJIenr6pWImZYjg+pc3try9W3aldLPrlJX8erlj/Khe7HpPopQSQt56661169aJthBCKBqNapqWlZVVXFxcVFSUk5PjcDgope3t7bW1tZWVlWfPnrVarbIsx4MiOiB2u/2Xv/zl8OHDk6z59A2WQOrUqVPf+973hJLHvyJqdaNGjXr66adLSkqEQgHnopETrD55bPFPsVmOL5NqvmD2wntGPvP1/k4Ziy0pivLEE080NzcDgKIo48ePv/fee6dMmeJ0Onu+oqrqn//8502bNnk8HgFivOShUKi8vHzVqlVJgtW3rGKVTZs2xWKxhNwVYxwIBO666661a9eWlJRQTeeMI4wRJqK60LD2HR7fuUGIKYr1lqycbyVl13tKIvr43/jGN/x+//Dhw1944YVXX331q1/9ak+kdF3XNE2W5YqKivXr1992220i9zYeoJQ6nc7Kyspdu3aJlfsWoHfNEmpVXV397LPP2my2BE0OBoMPP/zwkiVLOOeMUiJJwHnH8TO+/UeiDa1Ki6fj+JluI3oEa/6O/Je/k3nftGsZXldVdffu3VOnTk1JSaGUCn1vbW09d+5cOBx2OBy5ublZWVnQOYQjYteXXnpp37594hUDfVVVc3Jy1q9fbzKZ+tSvpNKdnTt3JgBPCAkEAnPnzl2yZAljDBgnktRx4lzDq28Gqk4wRUUYIUnCVnPcMCPWO6JpU0oy75uWvF2/LMmyPGfOHM650J36+vrNmzfX1NSIfgfG2OFwFBUVPfzww7feeit0th2XL1/+gx/8oK6uzkgDhAc/c+bM/v37p0+f3idYvUksIvW2trbDhw8b5V2hU+FwuLS0dOmTS4WRwhK5+O7eo4te8h84hq0mOS1FSnUQmxl6qO2wf60A0ZK5NtJ1Xdd1WZb37Nnz5JNP7tmzR6QQTqfTbrfrun7w4MGnn3769ddfN3Jsi8Xy/e9/32QyJUQ8CKEPP/wwGaa9gSUWPXLkiNfrja9YiqTs8ccfl2SJ6RQT4v3o01PPr0cESyl2YJxTyinrhghCTNflNKejIA/6b60SSKQ4Aqkf//jHACDmK1knIYQcDofD4di0adPatWtF2EUp7RmpCeWqra31er0iALpKsAQdO3as2wsYh8Ph8vLykpISRimRJc0XPLPqDWySESH8ijEeRwhxnTJNv/yfe6WEhzVNa2xsXLt27cqVKyVJkiSpZ2wpUov09PS33nrrk08+MZz47Nmz7Xa78bw4eK/XW1dXB32NWPZms0QW1tDQEN/yFI7jjjvuABGgE3Lhjx/Hmi/IQ1J76wlzQBLR/aHQkTpLdgZw0HTtpZdeunDhgqGz8ZoLcSMLRj3PCFxCodCFCxcikYhwgldyZAJok8n05ptvTps2Texi9OjRY8eOPXbsmOGvEEK6rp85c2bixIlXCZYR1Hi93viIgVJqs9ny8/MBQMQH/n2fYbOczHcGOQf/viMZX5sCCBBAQ0NDY2OjyMMNXC4rSYJUGGNZlsVESe8cRc3+9OnTtbW1BQUFlFJJkvLz82tqarpVaxFqaWnpU/4+vGEsFotGo0aiLyyl3W53uVwAgDGm4ZjS6kXdu+1XQh+bpGhDi1iIcS5JktVqFT4brnwFeiIoVCbJtE7U3c6ePVtQUCBOJTs7O1EwjEWWdk2hA6XUaBYYS4uzvfSRMZ5kbsUBEKLRGNN0LEuqoookySif94JyUuv3SqFQyPh3zwhWdCT7BKsPA08Iib+D4hCi0WhHR4f4KDltl8rEffo3BMA5sVmQLAFAIBgIh8M9HRBOmvrVkbbZbMa/e842iX31uUgfmmWxWGw2WyAQiIcvHA6fP38+JyeH6TqRZcf40aGj9cRm4dDbvUAIMVW3jc4RW2xtbY1EIglZAQBEo9FkWvPC5ffp7MWTsizn5nbNuXk8noS3OOd2ux3iCor9A0v4HbPZ7Ha7m5ubDdcrzNaRI0cmTpwoBhIzKiZf+MOfk/i2M0IYDZn5ZfHh5MmT8ZUWQ+hx48bFB8BXIoxxXV2dqMD08rDwUSNGjCgoKIDOQlt9fX38W8K/Z2RkwLWEDmJUatSoUdXV1cauGGMmk+ngwYOLFi2SZZkzlvJP49Lvnti2Y4+c7uJXCKOQLGntAff0L6XdUSbKMtXV1fGBrrAamZmZr7zyitVq7f2ERU6za9eun/70p737REJIJBKZO3euLMuiwhMMBk+cOGHMTxjcher1cUJ9PlFaWhpflhH6X19ff+DAAQBgjAPAyB9+016Qp3mDSJa6RecIxE9baN6AbXTOmBWPcgCEUV1d3fHjx+NrxMJnFRUVWa1WsfleYlShCxUVFXPnzvV4PKKv01OnJElqb2+/884777//fuP/9+3b19raGn9OIhgaM2YMXIuBFxKUlZVlZmYmXBlRC1RVlUiEMyanOYvW/tBVXqRe9NGoIhwfIMQp18NRzRtMu71s/Gv/Ycp0i2+hbNu2LRqNxhdMBARixBbiAtFeiHP+1FNPPfDAA+3t7bFYTJRMBWGMNU3zeDzTp09ftmyZWJ8QEovFtm7dmqDRqqrm5uaOHDkS+mqR9TZyJA7QarU2NzcfO3ZM3A7oHDg4f/68pmnl5eWMMQQgOW0Zs6eYh6Vr7UE9GGaKyhmT7NaU0rF5Tz2Uu/QhyWnTNU2S5YMHD77++uvxpt0olSxevFiSJKOL1QsZKnb77bePGDGiqanJ4/FEIhHRkWaMDRs27Fvf+tbixYvFRJy4uZs2bfrLX/5idA+h01/df//9ZWVlotrTC9Ok6lmnT59eunRpwkIY446OjieeeGLevHmMMc4YxgRhxClTWjxaewAINg8dYspwAQCjjDEqyXJTU9Mzzzzj9/vjj5cQ4vf7n3zyyfnz5wvL0jtSRtdPhKaiXFVbW1tfX9/R0WGz2UaOHFlYWGhcc1HS2r1796pVq4wjN0CXJGn9+vXJFJeTLSuvXr1627ZtLpcrvnIGAOFweMGCBY899pjwL1TTESGks1bFAZiuIwBECELoxIkTK1eu9Hg88dbKMO3r1693OBy9SywagoSQ999/v7m5+ZFHHjFKLglnKXA0ksrdu3e/8sorwrolHNK8efOWLl2aTNu1b7CE9F6v97vf/a7P54uvBwlRgsFgUVHRwoULy8vLeyqFeP3ixYtbt27dtm2bqAvHx1aijvjCCy/MmDGjd4kNULZs2bJx40Zd10ePHj1//vzp06dbLBYDSsFRHJ5o3/3mN795++23E+IykT87nc5169YZTZZrBctQrn379okGekLZRLhnSunYsWPLy8sLCgoyMzOtVquu636/v6Gh4bPPPqupqfH5fA6HI+FLmGLAfc6cOc8991zvSInNqKq6Zs2anTt3ulwukUsoipKXlzdt2rSJEyfm5eWJ2FLI3NLScuDAgR07djQ0NIgUJ0HsQCCwbNmyu+++O8lufrKzDmK53/3ud+vWrXO73QkJneAUi8UURcEYm81mSZIYY6qqappGCLFarT2rTuIrl+PHj+8zthJ/8vl8K1eurK6uNqyBuGLCqJvN5vT09IyMDNHF8Xq9LS0twWDQYrGIznkC6/b29vnz5yd5AfsHloHXhg0b3njjjbS0tJ5lOSF6fMXOqED1fFiSJL/fn5+fv2rVqoTR9ssiFYlEHn/88aampiFDhqiq2pMvY0zTNF3XjWkRWZbFmfVk3d7ePmvWrBUrViTjeQ3qx7SyiCQmTJhgNpsPHDggiko9k6wEX9MTJoGg1+udMGHCyy+/LPS0l7MVcJtMJrvdXl1dLZQoIaMULAghpk4yRmsSWAOAz+ebPXv2j370I/FM8mD1e+RIuPa9e/euXr3a4/E4nU5xqsmsI2AKh8MA8OCDDz722GOiUZzMLRD6dfz48Z/97GeNjY0pKSn9moQRrCORCAAsWrTo61//ekI9dkDAMvDyeDwbN2784IMPFEWx2Wwi9rvs3RQC6bouClhlZWWPPvpoaWmpuC/JiytgDYVCGzdu/NOf/iT678LrXbZUD3GWQdjT4uLiJUuWFBcX95f11YMFnTOSCKFTp0798Y9/rKysbG9vFwGeMcoCnbM+uq5zzlNSUkpLS++9994vf/nLQhmvQlzjrZMnT7755ptVVVWhUEiWZXHv4hcUcZamaaqqSpI0ZsyYBx54YMaMGcKKXafJP4PEYRpW4PDhw0ePHhXzkiKSEG4xLS1txIgR48eP/9KXvjRs2LCEF6+Rb1NT0549ew4dOtTY2BgMBjVNM7YjSZLNZhs6dGhxcfHUqVNLS0tFw+JaWF/rD/f0jJ5VVY1EIrquY4ytVqvVau3l4S+Kr8/na2lpuXjxomhKWywWt9udlZU1dOhQw9KL0P9amH4xv3IkRIFOO9pzY+I8v/CvJiWzskh6vpAT+uJ/Eqqngf9i178S0wQbb1RyvkAuN/S3lW82uvE/hX0T0SBY/aBBsPpBg2D1gwbB6gcNgtUPGgSrHzQIVj9oEKx+0CBY/aD/A/ORNiwv2PAfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE5LTA2LTEzVDAzOjE3OjE2LTA0OjAwj3mANAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOS0wNi0xM1QwMzoxNzoxNS0wNDowMM/MIhUAAAAASUVORK5CYII=",
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,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAAAAABVicqIAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfjCB8QNSt2pVcCAAAIxUlEQVRo3u2aa4xV1RXH/2vtfWeAYQbBBwpUBqGkjQLKw4LQ1NqmDy2RxmKJSGxiaatttbWhabQPSFuT2i+NqdFKbVqjjDZGYxqgxFbbWgHlNSkBChQj8ii+ZV4Mc/be/34459w5995zH4zYpA3709x7z9m/vdbae6/XCPH+D/0vMM5AzkDOQBoY9hSfJ4n4khCISGMvySlcKww0UvYNpAFdNAxhEAXQc/T1g2/2RFJoOW/8OeNHAaDXepwGIYEGOL5146a9/z5R/LJp7KRp86+4UOJf3yskUKVv/ZN/OQoAogIIQQYAaJ117aL2ehjWHcEF7lsxEQK1RgeNLaLGKgSti5919L76DPUhzvPAV0cAanL3khgDyMf+GOjCUCHB8Z0VI6GFGsYVq8Cnt1cXpg7Eez7ZXiKEiKgxqqqSFUcx7M5+uqFAHLuWQwYRYqzNfsjAjWLmdka5Kqu5u5ztvOkfNoTko4oHICNHtzSHqK+rywMwCEyZruX+ZV5zLFcL4uzTy7qtT24RZUDh4ivmTL2wdbgN/mTvkZd3bO58F2KKTwT+aGXIu2uq6yribxQmMYRRYMZPO8uVfmTNouGx4QFALW6OQqX5q0McV8MkR0wNdOEzAyRd5H2Ih3cuMHD3N0ZB0+ea8MUBhoYhER9GcimJUXz0eTJEvvx9H/nAA19pQrwFRApY6iueqgZxXF9ItCsWZz0Y6KocNu8Ct8xBqjKL2+kbg3juH5vao4D5/6SrcgRiDPu/J4nYanF/+XnJhwT2z0v8mRjc0l/jyojldvz9qHhRotL81zJKPsTxjoShBj+uefklq4q4KRXd4NKeUuMjn7E21ZXFPVWOcdkY4PaxScRgcUepKHmQwJ5Liqta1RiDjLi5LaaImL+VUPIgjj+LlSUWt1Saw3vvK7cpGbEjsb7BfJdVWA4k8Nj5UAHEYt5JNiZHTFmZWNLgt1lRciCOK2FFAEHLjvLdGHhi+eev/8J112ytOA0MwX08VrPi0uzBr4QEvj4BEhvwbkYVv3adC4HiDznOw3P7SKgAMOjI/F7p8AI6DlsCUDf9NlTGB9oMaw3yAgd1l92exqSrs8FppSBuNgwAUTxeudrA7gugUKzNc4OBr10YvyzmpUF9VkhCbN4qAYCGuYvz1pveaHkeSPx5X4MAoPonUHRVOZCnYeOfbxWfM1F/BIJVInXFstFOABDrnWEVCI1/BgGA+kmfy5mJ6Pf5q0tEmXAtFACxcweqQrBnFwIAwWdH+0qdCOqF8tcjAKDBCwhVIS9GhgCIhVVmqRnYKha0J4Z+oWi3Sqm3xSsO4y8fSoYkvnV+bHp09qVGKZuHBjuT72eOCQ3mOGVjbiLvkWPIhwA9h0EAgukYYtllNrwA1BP7qkCI195EbJIZQ4MIJoyGAFAcqirJWz0ggICx+ecNI5sACFxVyLjk6sOR9Dsbrx+gAEDQ4xACQnsWSEr65uAwAmH1PSbUs5M/j6bv2XSO9LLoSyLXEaOgjWa3pRqXtuSv3hLIu7CuRwHAjetKfjFvjxgQFlrAUOgbMbxyruqYpmSKgYy6gm5dYk2/gBA2DcADII5/UgBqE2GOT3tqOCuFCrWz6+wqSHr+LqP28tkUk3YP3tqBPRdAIZj5HENuNOa5EAaAxQ3pa4gd7mNGraqKDKYXoiKipoCp+zOeNrB7AgQQmGUH6XN9ypUJZHnqcpCEAB2an3gWcNG+rHsKfOccWADG4Oyf91XGfYH+kgTyw9R5Iw001qjmeCiDSbvLXGC4pxWqcTo6fV2FzjwPnYXYzT9YBmHEx4ya8j1r0b67Ml751xKBUYEayOL99CUYx01ITvz6UnWlGtPSjK+Ai/ZUunIXuGE21ApgDNpW9ZbozPGXsZfH8AMlhk8ppnRTWkzZmxcueMeT950LNRCxig894TM7w/FGGACKD/aloVcmWonYYTIFH7GYvK9KZu48jy63MCqiRnDN7mIkF9jVDgVgcF3x5WxIVLrHCphSjRHXWzYuiFNSNWi+N5XFcV186Vr8ohgZlsRdA+wwYlJdTd7L2ulVeGgcjAGkGb9KH3W8KTGJbCkqsTS4i7hGjYEILCbVZCQ6+3oTjLH41ODWezX1JtOiog7LIsiIHUaNqEX7rjoMMjjP7VdBTWFTuuiId8PGBv3u4PvlYWpsfYuJ9Rmxzvyjk/Ht9NnAYx+AojxMrYiFI3YYg8m7G2GQdI5v/eRQqpiId8IKAItP1EwdIj5e3x5ZnYXidJ7bW9KsY01mhpwkKOKvX2qYQTKkSWUI0VVpEnRZ7SSI9GTdnDpvRFyVuHODh+ukcyy9vxvmRVwTuyOxWFAvMS0XyzeaYm9sjXM5Eft8ydrqQTx39IdGhBngtrGIfYXFd+oXC0qW9xDuyvWypSNE3DhY9pje1UDZI8NYrYovdderSjjHx9riEwLBsL83VMApMh6BqsWcnTWF8Y4nVkBt6iEeaKwUlbycuDGD1ntdmZfNIgI3zoLR1EN8q9GiWsx4NHFiRvGRZ8ngqpQHv9wEW4xIbwwNlwdJz4Nj0kaRGshn1p4k6SJXVujcdWsb1CYBtcWSUyl0koFrmpEWIo0CF6/aVm6Zw49cMywtgYsYi5tdzoavVXwOuuGGtwsuU3y2H55/+ZT21hE2hP7eI/s7X+w8DjFJCVyUIb/4XLOM7s3+pVuSMrqARjwBtI5qbeZAb/fxAMBISDppYtzIB5bmltHrNQR6bwNsMbQULekBZD6INZi1o8p5qt/a2DC1rD8jqqpamiEZg+HfPzG01gYZHLt/0AYxtZo0RoGrO4fcpCHpPF/5ZhugNie9E1FrAblyHd9Du4mxg335rilp4yyrN2MNBK1LnvM1a8cNtwBP/OmpP78aLz4WiHF3pm3u1Ysm1mkBnkozs3vbxk17jmabmRfNmD9vwmlqZsYLhwHQe/SNV97ojrTQcv74MRPaAIRwutqyCYdl853uBnM6LdNqxPvUKh/y+P/594UzkDOQ/3HIfwCAE6puXSx5zQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOS0wOC0zMVQxNjo1Mzo0My0wNDowMGtSg1gAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTktMDgtMzFUMTY6NTM6NDMtMDQ6MDAaDzvkAAAAAElFTkSuQmCC",
label: "Schedule",
is_valid: true,
environment: "onprem",
long_description: "Create a schedule based on cron",
},
{
name: "Office365",
type: "TRIGGER",
status: "uninitialized",
description: "O365 email trigger",
trigger_type: "EMAIL",
errors: null,
is_valid: true,
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: true,
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",
},
{
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",
is_valid: true,
errors: null,
label: "User input",
environment: "cloud",
long_description: "Take user input to continue execution",
},
];
// is_valid: cloudSyncEnabled || isCloud ? true : false,
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 const green = "#86c142";
export const yellow = "#FECC00";
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 theme = useTheme();
var to_be_copied = "";
const [firstrequest, setFirstrequest] = React.useState(true);
const [cystyle] = useState(cytoscapestyle);
const [cy, setCy] = React.useState();
const [toolsApp, setToolsApp] = React.useState({});
const [currentView, setCurrentView] = React.useState(0);
const [triggerAuthentication, setTriggerAuthentication] = React.useState({});
const [triggerFolders, setTriggerFolders] = React.useState([]);
const [workflows, setWorkflows] = React.useState([]);
const [parentWorkflows, setParentWorkflows] = 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);
const [workflowRecommendations, setWorkflowRecommendations] = React.useState([]);
// 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) {
// 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];
// User Input & Subflow nodes
if (param.name === "workflow" || param.name === "subflow") {
const paramIndex = param.name === "workflow" ? 0 : 5
console.log("Current vs new: ", workflow.triggers[trigger_index].parameters[paramIndex].value, subworkflow.id)
if (workflow.triggers[trigger_index].parameters[paramIndex].value !== subworkflow.id) {
if (param.value === workflow.id) {
setSubworkflow(workflow);
baseSubflow = workflow
} else {
const sub = responseJson.find((data) => data.id === param.value);
if (sub !== undefined && subworkflow.id !== sub.id) {
baseSubflow = sub
setSubworkflow(sub);
}
}
}
}
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);
}
}
}
}
}
if (workflows.length === 0) {
//console.log("First request. Checking for parent trigger (if this is subflow")
var parentworkflows = []
var parent_ids = []
for (let workflowkey in responseJson) {
const innerworkflow = responseJson[workflowkey]
for (let triggerkey in innerworkflow.triggers) {
const trigger = innerworkflow.triggers[triggerkey]
if (trigger.trigger_type === "SUBFLOW" || trigger.trigger_type === "USERINPUT") {
for (let paramkey in trigger.parameters) {
const param = trigger.parameters[paramkey]
if ((param.name === "workflow" || param.name === "subflow") && param.value === props.match.params.key && !parent_ids.includes(innerworkflow.id)) {
parent_ids.push(innerworkflow.id)
parentworkflows.push({
id: innerworkflow.id,
name: innerworkflow.name,
image: innerworkflow.image,
})
}
}
}
}
}
if (parentworkflows.length > 0) {
setParentWorkflows(parentworkflows.filter(wf => wf.id !== props.match.params.key))
}
}
setWorkflows(responseJson);
}
})
.catch((error) => {
//alert.error(error.toString());
console.log("Workflow error: ", error.toString())
});
};
function OuterLink(props) {
if (props.href.includes("http") || props.href.includes("mailto")) {
return (
{props.children}
);
}
return (
{props.children}
);
}
function Img(props) {
return ;
}
function CodeHandler(props) {
return (
{props.value}
);
}
function Heading(props) {
const element = React.createElement(
`h${props.level}`,
{ style: { marginTop: 40 } },
props.children
);
return (
{props.level !== 1 ? (
) : null}
{element}
);
}
const generateApikey = () => {
fetch(globalUrl + "/api/v1/generateapikey", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for APIKEY gen :O!");
}
return response.json();
})
.then((responseJson) => {
setUserSettings(responseJson);
})
.catch((error) => {
console.log("Apikey error: ", error);
});
};
const getSettings = () => {
fetch(globalUrl + "/api/v1/getsettings", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for get settings :O!");
}
return response.json();
})
.then((responseJson) => {
if (
responseJson.success === true &&
(responseJson.apikey === undefined ||
responseJson.apikey.length === 0 ||
responseJson.apikey === null)
) {
generateApikey();
}
if (responseJson.success === true) {
setUserSettings(responseJson)
}
})
.catch((error) => {
console.log("Settings error: ", error);
});
};
const setNewAppAuth = (appAuthData) => {
fetch(globalUrl + "/api/v1/apps/authentication", {
method: "PUT",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(appAuthData),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for setting app auth :O!");
}
return response.json();
})
.then((responseJson) => {
if (!responseJson.success) {
alert.error("Failed to set app auth: " + responseJson.reason);
} else {
getAppAuthentication(true, false);
setAuthenticationModalOpen(false);
// Needs a refresh with the new authentication..
//alert.success("Successfully saved new app auth")
}
})
.catch((error) => {
//alert.error(error.toString());
console.log("New auth error: ", error.toString());
});
};
const getWorkflowExecution = (id, execution_id) => {
fetch(globalUrl + "/api/v1/workflows/" + id + "/executions", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.length > 0) {
// FIXME: Sort this by time
// - means it's opposite
const newkeys = sortByKey(responseJson, "-started_at");
setWorkflowExecutions(newkeys);
const cursearch =
typeof window === "undefined" || window.location === undefined
? ""
: window.location.search;
var tmpView = new URLSearchParams(cursearch).get("execution_id");
if (
execution_id !== undefined && execution_id !== null &&
execution_id.length > 0 && (tmpView === undefined || tmpView === null || tmpView.length === 0)
) {
tmpView = execution_id;
}
if (tmpView !== undefined && tmpView !== null && tmpView.length > 0) {
const execution = responseJson.find((data) => data.execution_id === tmpView);
if (execution !== null && execution !== undefined) {
if (execution.execution_argument.includes("too large")) {
setExecutionData({});
setExecutionRunning(true);
setExecutionRequestStarted(false);
} else {
setExecutionData(execution);
}
setExecutionModalView(1);
start();
setExecutionRequest({
execution_id: execution.execution_id,
authorization: execution.authorization,
});
const newitem = removeParam("execution_id", cursearch);
navigate(curpath + newitem)
//props.history.push(curpath + newitem);
} else {
console.log("Couldn't find execution for execution ID. Retrying as user to get ", tmpView)
//setExecutionRequestStarted(true);
const cur_execution = {
execution_id: tmpView,
//authorization: data.authorization,
}
setExecutionModalView(1);
setExecutionRequest(cur_execution);
start();
const newitem = removeParam("execution_id", cursearch);
navigate(curpath + newitem)
setTimeout(() => {
stop()
}, 5000);
}
}
}
})
.catch((error) => {
//alert.error(error.toString());
console.log("Get execution error: ", error.toString());
});
};
const fetchUpdates = () => {
fetch(globalUrl + "/api/v1/streams/results", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(executionRequest),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for stream results :O!");
stop();
}
return response.json();
})
.then((responseJson) => {
//console.log("RESPONSE: ", responseJson)
handleUpdateResults(responseJson, executionRequest);
})
.catch((error) => {
console.log("Execution result Error: ", error);
stop();
});
};
const abortExecution = () => {
setExecutionRunning(false);
fetch(globalUrl +"/api/v1/workflows/" +props.match.params.key +"/executions/" +executionRequest.execution_id +"/abort",
{
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for ABORT EXECUTION :O!");
}
return response.json();
})
.catch((error) => {
//alert.error(error.toString());
console.log("Abort error: ", error.toString());
});
};
const handleColoring = (actionId, status, label) => {
if (cy === undefined) {
return
}
var currentnode = cy.getElementById(actionId);
if (currentnode.length === 0) {
return
//continue;
}
currentnode = currentnode[0];
const outgoingEdges = currentnode.outgoers("edge");
const incomingEdges = currentnode.incomers("edge");
switch (status) {
case "EXECUTING":
currentnode.removeClass("not-executing-highlight");
currentnode.removeClass("success-highlight");
currentnode.removeClass("failure-highlight");
currentnode.removeClass("shuffle-hover-highlight");
currentnode.removeClass("awaiting-data-highlight");
incomingEdges.addClass("success-highlight");
currentnode.addClass("executing-highlight");
break;
case "SKIPPED":
currentnode.removeClass("not-executing-highlight");
currentnode.removeClass("success-highlight");
currentnode.removeClass("failure-highlight");
currentnode.removeClass("shuffle-hover-highlight");
currentnode.removeClass("awaiting-data-highlight");
currentnode.removeClass("executing-highlight");
currentnode.addClass("skipped-highlight");
break;
case "WAITING":
currentnode.removeClass("not-executing-highlight");
currentnode.removeClass("success-highlight");
currentnode.removeClass("failure-highlight");
currentnode.removeClass("shuffle-hover-highlight");
currentnode.removeClass("awaiting-data-highlight");
currentnode.addClass("executing-highlight");
if (!visited.includes(label)) {
if (executionRunning) {
visited.push(label);
setVisited(visited);
}
}
// FIXME - add outgoing nodes to executing
//const outgoingNodes = outgoingEdges.find().data().target
if (outgoingEdges.length > 0) {
outgoingEdges.addClass("success-highlight");
}
break;
case "SUCCESS":
currentnode.removeClass("not-executing-highlight");
currentnode.removeClass("executing-highlight");
currentnode.removeClass("failure-highlight");
currentnode.removeClass("shuffle-hover-highlight");
currentnode.removeClass("awaiting-data-highlight");
currentnode.addClass("success-highlight");
incomingEdges.addClass("success-highlight");
outgoingEdges.addClass("success-highlight");
if (visited !== undefined && visited !== null && !visited.includes(label)) {
if (executionRunning) {
visited.push(label);
setVisited(visited);
}
}
// FIXME - add outgoing nodes to executing
//const outgoingNodes = outgoingEdges.find().data().target
if (outgoingEdges.length > 0) {
for (let i = 0; i < outgoingEdges.length; i++) {
const edge = outgoingEdges[i];
const targetnode = cy.getElementById(edge.data().target);
if (
targetnode !== undefined &&
!targetnode.classes().includes("success-highlight") &&
!targetnode.classes().includes("failure-highlight")
) {
targetnode.removeClass("not-executing-highlight");
targetnode.removeClass("success-highlight");
targetnode.removeClass("shuffle-hover-highlight");
targetnode.removeClass("failure-highlight");
targetnode.removeClass("awaiting-data-highlight");
targetnode.addClass("executing-highlight");
}
}
}
break;
case "FAILURE":
//When status comes as failure, allow user to start workflow execution
if (executionRunning) {
setExecutionRunning(false);
}
currentnode.removeClass("not-executing-highlight");
currentnode.removeClass("executing-highlight");
currentnode.removeClass("success-highlight");
currentnode.removeClass("awaiting-data-highlight");
currentnode.removeClass("shuffle-hover-highlight");
currentnode.addClass("failure-highlight");
if (!visited.includes(label)) {
//if (item.action.result !== undefined && item.action.result !== null && !item.action.result.includes("failed condition")) {
// alert.error("Error for " + item.action.label + " with result " + item.result);
//}
visited.push(label);
setVisited(visited);
}
break;
case "AWAITING_DATA":
currentnode.removeClass("not-executing-highlight");
currentnode.removeClass("executing-highlight");
currentnode.removeClass("success-highlight");
currentnode.removeClass("failure-highlight");
currentnode.removeClass("shuffle-hover-highlight");
currentnode.addClass("awaiting-data-highlight");
break;
default:
currentnode.removeClass("not-executing-highlight");
currentnode.removeClass("executing-highlight");
currentnode.removeClass("success-highlight");
currentnode.removeClass("failure-highlight");
currentnode.removeClass("shuffle-hover-highlight");
currentnode.removeClass("awaiting-data-highlight");
currentnode.addClass("not-executing-highlight");
//console.log("DEFAULT -> Clearing!");
break;
}
}
// Controls the colors and direction of execution results.
// Style is in defaultCytoscapeStyle.js
const handleUpdateResults = (responseJson, executionRequest) => {
if (responseJson === undefined || responseJson === null || responseJson.success === false) {
return
}
//console.log(responseJson)
// Loop nodes and find results
// Update on every interval? idk
ReactDOM.unstable_batchedUpdates(() => {
if (JSON.stringify(responseJson) !== JSON.stringify(executionData)) {
// FIXME: If another is selected, don't edit..
// Doesn't work because this is some async garbage
if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) {
if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || (executionData.results !== undefined && responseJson.results !== null && executionData.results.length !== responseJson.results.length)) {
console.log("Updating data!")
setExecutionData(responseJson)
} else {
console.log("NOT updating executiondata state.");
}
}
}
if (responseJson.execution_id !== executionRequest.execution_id) {
cy.elements().removeClass("success-highlight failure-highlight executing-highlight");
return;
}
if (responseJson.results !== null && responseJson.results.length > 0) {
// First clear current nodes
if (responseJson.workflow.actions !== undefined && responseJson.workflow.actions !== null) {
// In clearing of actions
for (let actionKey in responseJson.workflow.actions) {
var item = responseJson.workflow.actions[actionKey];
handleColoring(item.id, "", item.label)
}
}
for (let resultKey in responseJson.results) {
var item = responseJson.results[resultKey];
handleColoring(item.action.id, item.status, item.action.label)
}
}
if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING") {
stop();
if (executionRunning) {
setExecutionRunning(false);
}
var curelements = cy.elements();
for (let i = 0; i < curelements.length; i++) {
if (curelements[i].classes().includes("executing-highlight")) {
curelements[i].removeClass("executing-highlight");
curelements[i].addClass("failure-highlight");
}
}
getWorkflowExecution(props.match.params.key, "");
} else if (responseJson.status === "FINISHED") {
setExecutionRunning(false);
stop();
getWorkflowExecution(props.match.params.key, "");
setUpdate(Math.random());
}
})
};
const sendStreamRequest = (body) => {
console.log("Stream not activated yet.")
return
// Session may be important here huh
body.user_id = userdata.id
fetch(globalUrl + "/api/v1/workflows/" + props.match.params.key + "/stream", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(body),
credentials: "include",
})
.then((response) => {
setSavingState(0);
if (response.status !== 200) {
console.log("Status not 200 for setting workflows :O!");
}
return response.json();
})
.then((responseJson) => {
console.log("RESP: ", responseJson)
})
.catch((error) => {
console.log("Stream send error: ", error.toString())
//alert.error(error.toString());
})
}
const saveWorkflow = (curworkflow, executionArgument, startNode) => {
var success = false;
if (isCloud && !isLoggedIn) {
console.log("Should redirect to register with redirect.");
window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle`;
return;
}
setSavingState(2);
// This might not be the right course of action, but seems logical, as items could be running already
// Makes it possible to update with a version in current render
stop();
var useworkflow = workflow;
if (curworkflow !== undefined) {
useworkflow = curworkflow;
}
var cyelements = cy.elements();
var newActions = [];
var newTriggers = [];
var newBranches = [];
var newVBranches = [];
var newComments = [];
for (let cyelementsKey in cyelements) {
if (cyelements[cyelementsKey].data === undefined) {
continue;
}
var type = cyelements[cyelementsKey].data()["type"];
if (type === undefined) {
if (
cyelements[cyelementsKey].data().source === undefined ||
cyelements[cyelementsKey].data().target === undefined
) {
continue;
}
var parsedElement = {
id: cyelements[cyelementsKey].data().id,
source_id: cyelements[cyelementsKey].data().source,
destination_id: cyelements[cyelementsKey].data().target,
conditions: cyelements[cyelementsKey].data().conditions,
decorator: cyelements[cyelementsKey].data().decorator,
};
if (parsedElement.decorator) {
newVBranches.push(parsedElement);
} else {
newBranches.push(parsedElement);
}
} else {
if (type === "ACTION") {
const cyelement = cyelements[cyelementsKey].data();
const elementid =
cyelement.id === undefined || cyelement.id === null
? cyelement["_id"]
: cyelement.id;
var curworkflowAction = useworkflow.actions.find(
(a) =>
a !== undefined &&
(a["id"] === elementid || a["_id"] === elementid)
);
if (curworkflowAction === undefined) {
curworkflowAction = cyelements[cyelementsKey].data();
}
curworkflowAction.position = cyelements[cyelementsKey].position();
// workaround to fix some edgecases
if (
curworkflowAction.parameters === "" ||
curworkflowAction.parameters === null
) {
curworkflowAction.parameters = [];
}
if (
curworkflowAction.example === undefined ||
curworkflowAction.example === "" ||
curworkflowAction.example === null
) {
if (cyelements[cyelementsKey].data().example !== undefined) {
curworkflowAction.example = cyelements[cyelementsKey].data().example;
}
}
// Override just in this place
curworkflowAction.errors = [];
curworkflowAction.isValid = true;
// Cleans up OpenAPI items
var newparams = [];
for (let parametersKey in curworkflowAction.parameters) {
const thisitem = curworkflowAction.parameters[parametersKey];
if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) {
continue;
}
newparams.push(thisitem);
}
curworkflowAction.parameters = newparams;
newActions.push(curworkflowAction);
} else if (type === "TRIGGER") {
if (useworkflow.triggers === undefined || useworkflow.triggers === null) {
useworkflow.triggers = [];
}
var curworkflowTrigger = useworkflow.triggers.find(
(a) => a.id === cyelements[cyelementsKey].data()["id"]
);
if (curworkflowTrigger === undefined) {
curworkflowTrigger = cyelements[cyelementsKey].data();
}
curworkflowTrigger.position = cyelements[cyelementsKey].position();
if (curworkflowTrigger.canConnect === false) {
continue
}
newTriggers.push(curworkflowTrigger);
} else if (type === "COMMENT") {
if (useworkflow.comments === undefined || useworkflow.comments === null) {
useworkflow.comments = [];
}
var curworkflowComment = useworkflow.comments.find(
(a) => a.id === cyelements[cyelementsKey].data()["id"]
)
if (curworkflowComment === undefined) {
curworkflowComment = cyelements[cyelementsKey].data();
try {
curworkflowComment.position.x = parseInt(curworkflowComment.position.x)
} catch (e) {
console.log("Failed to parse position Y of comment: ", curworkflowComment.position.x)
}
try {
curworkflowComment.position.y = parseInt(curworkflowComment.position.y)
} catch (e) {
console.log("Failed to parse position Y of comment: ", curworkflowComment.position.y)
}
}
const parsedHeight = parseInt(curworkflowComment["height"])
if (!isNaN(parsedHeight)) {
curworkflowComment.height = parsedHeight
} else {
curworkflowComment.width = 150
}
const parsedWidth = parseInt(curworkflowComment["width"])
if (!isNaN(parsedWidth)) {
curworkflowComment.width = parsedWidth
} else {
curworkflowComment.width = 200
}
curworkflowComment.position = cyelements[cyelementsKey].position();
//console.log(curworkflowComment)
newComments.push(curworkflowComment);
} else {
alert.info("No handler for type: " + type);
}
}
}
if (userediting === true) {
useworkflow.user_editing = true
}
useworkflow.actions = newActions;
useworkflow.triggers = newTriggers;
useworkflow.branches = newBranches;
useworkflow.comments = newComments;
useworkflow.visual_branches = newVBranches;
// Errors are backend defined
useworkflow.errors = [];
useworkflow.previously_saved = true;
if (cy !== undefined) {
// scale: 0.3,
// bg: "#27292d",
const cyImageData = cy.png({
output: "base64uri",
maxWidth: 480,
maxHeight: 270,
})
if (cyImageData !== undefined && cyImageData !== null && cyImageData.length > 0) {
useworkflow.image = cyImageData
}
}
setLastSaved(true);
fetch(`${globalUrl}/api/v1/workflows/${useworkflow.id}`, {
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) => {
if (response.status !== 200) {
console.log("Status not 200 for apps. Setting to pretend apps!");
const pretend_apps = [{
"name": "TBD",
"app_name": "TBD",
"app_version": "TBD",
"description": "TBD",
"version": "TBD",
"large_image": "",
}]
setAppsLoaded(true)
setApps(pretend_apps)
setFilteredApps(pretend_apps)
setPrioritizedApps(pretend_apps);
alert.error("Something went wrong while loading apps. Please refresh the window to try again.")
return
}
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": "",
}]
setAppsLoaded(true)
setApps(pretend_apps)
setFilteredApps(pretend_apps)
setPrioritizedApps(pretend_apps);
return
}
if (responseJson.success === false) {
return
}
// Used for e.g. Liquid testing
const foundTools = responseJson.find((app) => app.name === "Shuffle Tools")
if (foundTools !== undefined && foundTools !== null) {
setToolsApp(foundTools)
}
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)));
}
setAppsLoaded(true)
})
.catch((error) => {
console.log("App loading error: " + error.toString());
setAppsLoaded(true)
//alert.error("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 = [];
}
fetchRecommendations(responseJson)
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;
var target = sourcenode.parameters.find((item) => item.name === "startnode");
console.log("Got rightclick target: ", target)
if (target === undefined || target === null) {
target = {
"name": "startnode",
"value": responseJson.start
}
}
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)
//setSubworkflow({})
ReactDOM.unstable_batchedUpdates(() => {
setSelectedAction({});
setSelectedApp({});
setSelectedTrigger({});
setSelectedComment({})
setSelectedEdge({});
setSelectedEdge({})
setSelectedActionEnvironment({})
setTriggerAuthentication({})
setSelectedTriggerIndex(-1)
setTriggerFolders([])
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" || nodedata.app_name === "User Input")) {
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" || param.name === "subflow");
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.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;
}
}
if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") {
console.log("Found triggers. Add!")
if (!found) {
console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions)
// Find how many executions it has
var executions = 0
const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase()))
console.log("Matches: ", matchingExecutions.length)
const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436"
const decoratorNode = {
position: {
x: event.target.position().x + 44,
y: event.target.position().y + 44,
},
locked: true,
data: {
isDescriptor: true,
isValid: true,
is_valid: true,
isTrigger: true,
label: `${matchingExecutions.length}`,
attachedTo: nodedata.id,
imageColor: color,
hasExecutions: true,
},
};
cy.add(decoratorNode)
}
} else {
// 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");
if (data.isTrigger) {
console.log("But maybe we can select trigger descriptor? Maybe open execution tab?")
setExecutionModalOpen(true)
}
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 && curaction.name !== undefined) {
//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" || data.app_name === "User Input") {
console.log("In Workflow loading for user input & subflow")
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) {
console.log("Setting edge curve: ", edgeCurve)
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 addRunCountButton = (event) => {
// Count executions?
// Maybe it shouldn't be onclick?
}
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 = (nodedata, event) => {
console.log("In suggestion buttons: ", nodedata, ". This is not enabled yet. Backend needs further work.")
return
//setWorkflowRecommendations(responseJson.actions)
if (workflowRecommendations.length === 0) {
return
}
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 startHeight = 0
for (let recKey in workflowRecommendations) {
const rec = workflowRecommendations[recKey]
if (rec.action_id !== nodedata.id) {
continue
}
if (rec.recommendations === undefined || rec.recommendations.length === 0) {
continue
}
for (let recIndex in rec.recommendations) {
const parsedRec = rec.recommendations[recIndex]
startHeight += 100
const foundVersion = parsedRec.app_version !== undefined && parsedRec.app_version !== null && parsedRec.app_version !== "" ? parsedRec.app_version : "1.1.0"
const largeImage = parsedRec.large_image !== undefined && parsedRec.large_image !== null && parsedRec.large_image !== "" ? parsedRec.large_image : theme.palette.defaultImage
var newapp = {
app_name: parsedRec.app_name,
app_version: foundVersion,
app_id: parsedRec.app_id,
sharing: false,
private_id: false,
isStartNode: false,
label: "Suggestion "+recIndex,
large_image: largeImage,
finished: false,
is_valid: true,
isSuggestion: true,
attachedTo: event.target.data("id"),
}
cy.add({
group: "nodes",
data: newapp,
position: {
x: px,
y: py+startHeight,
},
locked: true,
});
}
console.log("Got Rec: ", rec)
}
}
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();
if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") {
console.log("In this :)")
var found = false;
for (let nodekey in allNodes) {
const currentNode = allNodes[nodekey];
if (
currentNode.data.attachedTo === nodedata.id &&
currentNode.data.isDescriptor
) {
found = true;
console.log("FOUND THE NODE!");
break;
}
}
if (!found) {
console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions)
// Find how many executions it has
var executions = 0
const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase()))
console.log("Matches: ", matchingExecutions.length)
const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436"
const decoratorNode = {
position: {
x: event.target.position().x + 44,
y: event.target.position().y + 44,
},
locked: true,
data: {
isDescriptor: true,
isValid: true,
is_valid: true,
isTrigger: true,
label: `${matchingExecutions.length}`,
attachedTo: nodedata.id,
imageColor: color,
hasExecutions: true,
},
};
cy.add(decoratorNode)
}
}
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 {
// Check how many executions from the source
addRunCountButton(event);
}
} else {
addCopyButton(event);
addStartnodeButton(event);
}
// autocomplete
// right click
// suggestions
addSuggestionButtons(nodedata, 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,
// }
//)
}
}
// Thanks to:
// Calculates how a branch should curve (it's still weird~)
// 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!")
}
}
*/
if (isNaN(controlPointDistance[0])) {
controlPointDistance[0] = 0
}
if (isNaN(controlPointDistance[1])) {
controlPointDistance[1] = 0
}
if (isNaN(controlPointWeight[0])) {
controlPointWeight[0] = 0
}
if (isNaN(controlPointWeight[1])) {
controlPointWeight[1] = 0
}
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 foundtriggers = 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, foundtriggers, 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 = foundtriggers.find(node => node.data._id === branch.source_id || node.data.id === branch.source_id)
}
if (destinationnode === undefined) {
destinationnode = foundtriggers.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 fetchRecommendations = (inputWorkflow) => {
const parsedWorkflow = JSON.parse(JSON.stringify(inputWorkflow))
fetch(globalUrl + "/api/v1/workflows/recommend", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(parsedWorkflow),
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("recommendations: ", responseJson);
if (responseJson.actions !== undefined && responseJson.actions !== null) {
setWorkflowRecommendations(responseJson.actions)
}
} else {
}
})
.catch((error) => {
//alert.error("ERROR: " + error.toString());
console.log("ERROR getting usecases: " + error.toString());
})
}
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 (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
setGraphSetup(true);
setupGraph();
console.log("In graph setup")
// 2nd load - configures cytoscape
} else if (!established && cy !== undefined && ((apps !== null && apps !== undefined && apps.length > 0) || workflow.public === true) && Object.getOwnPropertyNames(workflow).length > 0 && appAuthentication !== undefined) {
console.log("In POST graph setup!")
//This part has to load LAST, as it's kind of not async.
//This means we need everything else to happen first.
setEstablished(true);
// Validate if the node is just a node lol
console.log("CY grid: ", cy.gridGuide)
// https://www.npmjs.com/package/cytoscape-grid-guide
//
if (cy.gridGuide !== undefined) {
cy.gridGuide({
gridSpacing: 30,
guidelinesStyle: {
strokeStyle: "#8b7d6b", // color of geometric guidelines
geometricGuidelineRange: 400, // range of geometric guidelines
range: 100, // max range of distribution guidelines
minDistRange: 10, // min range for distribution guidelines
distGuidelineOffset: 10, // shift amount of distribution guidelines
horizontalDistColor: "#ff0000", // color of horizontal distribution alignment
verticalDistColor: "#00ff00", // color of vertical distribution alignment
initPosAlignmentColor: "#0000ff", // color of alignment to initial mouse location
lineDash: [0, 0], // line style of geometric guidelines
horizontalDistLine: [0, 0], // line style of horizontal distribution guidelines
verticalDistLine: [0, 0], // line style of vertical distribution guidelines
initPosAlignmentLine: [0, 0], // line style of alignment to initial mouse position
}
})
} else {
console.log("ERROR: Failed to render grid as it's unitialized")
}
if (cy.edgehandles !== undefined) {
cy.edgehandles({
handleNodes: (el) => {
if (el.isNode() &&
!el.data("isButton") &&
!el.data("isDescriptor") &&
!el.data("isSuggestion") &&
el.data("type") !== "COMMENT") {
return true
}
return false
},
preview: false,
toggleOffOnLeave: true,
loopAllowed: function (node) {
return false;
},
});
//cy.edgehandles({
// preview: false,
//})
//cy.edgehandles().enable()
} else {
console.log("ERROR: Failed to initialize edgehandler")
}
// preview: true,
cy.fit(null, 200);
cy.on("boxselect", "node", (e) => {
if (e.target.data("isButton") || e.target.data("isDescriptor") || e.target.data("isSuggestion")) {
e.target.unselect();
}
e.target.addClass("selected");
});
cy.on("boxstart", (e) => {
console.log("START");
cy.removeListener("select");
});
cy.on("boxend", (e) => {
console.log("END: ", cy)
var cydata = cy.$(":selected").jsons();
if (cydata !== undefined && cydata !== null && cydata.length > 0) {
alert.success(`Selected ${cydata.length} element(s). CTRL+C to copy them.`);
}
});
cy.on("select", "node", (e) => {
onNodeSelect(e, appAuthentication);
});
cy.on("select", "edge", (e) => onEdgeSelect(e));
cy.on("unselect", (e) => onUnselect(e));
cy.on("add", "node", (e) => onNodeAdded(e));
cy.on("add", "edge", (e) => onEdgeAdded(e));
cy.on("remove", "node", (e) => onNodeRemoved(e));
cy.on("remove", "edge", (e) => onEdgeRemoved(e));
cy.on("mouseover", "edge", (e) => onEdgeHover(e));
cy.on("mouseout", "edge", (e) => onEdgeHoverOut(e));
cy.on("mouseover", "node", (e) => onNodeHover(e));
cy.on("mouseout", "node", (e) => onNodeHoverOut(e));
// Handles dragging
cy.on("drag", "node", (e) => onNodeDrag(e, selectedAction));
cy.on("free", "node", (e) => onNodeDragStop(e, selectedAction));
cy.on("cxttap", "node", (e) => onCtxTap(e));
document.title = "Workflow - " + workflow.name;
registerKeys();
}
//})
const stopSchedule = (trigger, triggerindex) => {
fetch(
`${globalUrl}/api/v1/workflows/${props.match.params.key}/schedule/${trigger.id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for stream results :O!");
}
return response.json();
})
.then((responseJson) => {
// No matter what, it's being stopped.
if (!responseJson.success) {
if (responseJson.reason !== undefined) {
alert.error("Failed to stop schedule: " + responseJson.reason);
}
} else {
alert.success("Successfully stopped schedule");
}
workflow.triggers[triggerindex].status = "stopped";
trigger.status = "stopped";
setSelectedTrigger(trigger);
setWorkflow(workflow);
saveWorkflow(workflow);
})
.catch((error) => {
//alert.error(error.toString());
console.log("Stop schedule error: ", error.toString());
});
};
const submitSchedule = (trigger, triggerindex) => {
if (trigger.name.length <= 0) {
alert.error("Error: name can't be empty");
return;
}
var mappedStartnode = ""
const alledges = cy.edges().jsons()
if (alledges !== undefined && alledges !== null && alledges.length > 0) {
for (let edgekey in alledges) {
const tmp = alledges[edgekey]
console.log("TMP: ", tmp, tmp.data.source)
if (tmp.data.source === trigger.id) {
mappedStartnode = tmp.data.target
break
}
}
}
alert.info("Creating schedule with name " + trigger.name);
const data = {
name: trigger.name,
frequency: workflow.triggers[triggerindex].parameters[0].value,
execution_argument: workflow.triggers[triggerindex].parameters[1].value,
environment: workflow.triggers[triggerindex].environment,
id: trigger.id,
start: mappedStartnode,
}
fetch(
`${globalUrl}/api/v1/workflows/${props.match.params.key}/schedule`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(data),
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for stream results :O!");
}
return response.json();
})
.then((responseJson) => {
if (!responseJson.success) {
alert.error("Failed to set schedule: " + responseJson.reason);
} else {
alert.success("Successfully created schedule");
workflow.triggers[triggerindex].status = "running";
trigger.status = "running";
setSelectedTrigger(trigger);
setWorkflow(workflow);
console.log("Should set the status to running and save");
saveWorkflow(workflow);
}
})
.catch((error) => {
//alert.error(error.toString());
console.log("Get schedule error: ", error.toString());
});
};
const appViewStyle = {
marginLeft: 5,
marginRight: 5,
display: "flex",
flexDirection: "column",
minHeight: isMobile ? bodyHeight - appBarSize * 4 : "100%",
maxHeight: isMobile ? bodyHeight - appBarSize * 4 : "100%",
};
const paperAppStyle = {
borderRadius: theme.palette.borderRadius,
minHeight: isMobile ? 50 : 100,
maxHeight: isMobile ? 50 : 100,
minWidth: isMobile ? 50 : "100%",
maxWidth: isMobile ? 50 : "100%",
marginTop: "5px",
color: "white",
backgroundColor: surfaceColor,
cursor: "pointer",
display: "flex",
};
const paperVariableStyle = {
borderRadius: theme.palette.borderRadius,
minHeight: 50,
maxHeight: 50,
minWidth: "100%",
maxWidth: "100%",
marginTop: "5px",
color: "white",
backgroundColor: surfaceColor,
cursor: "pointer",
display: "flex",
};
const VariablesView = () => {
const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState(null);
const menuClick = (event) => {
setOpen(!open);
setAnchorEl(event.currentTarget);
};
const deleteVariable = (variableIndex) => {
//console.log("Delete:", variableName);
if (workflow.workflow_variables !== undefined && workflow.workflow_variables !== null && workflow.workflow_variables.length > variableIndex) {
workflow.workflow_variables.splice(variableIndex, 1)
}
//workflow.workflow_variables = workflow.workflow_variables.filter(
// (data) => data.name !== variableName
//);
setWorkflow(workflow);
};
const deleteExecutionVariable = (variableIndex) => {
if (workflow.execution_variables !== undefined && workflow.execution_variables !== null && workflow.execution_variables.length > variableIndex) {
workflow.execution_variables.splice(variableIndex, 1)
}
//workflow.execution_variables = workflow.execution_variables.filter(
// (data) => data.name !== variableName
//);
setWorkflow(workflow);
};
const variableScrollStyle = {
margin: 15,
overflow: "scroll",
height: isMobile ? "100%" : "66vh",
overflowX: "auto",
overflowY: "auto",
flex: "10",
};
return (
What are{" "}
WORKFLOW variables?
{workflow.workflow_variables === null
? null
: workflow.workflow_variables.map((variable, index) => {
return (
{ }}>
{
setVariableInfo({
"name": variable.name,
"description": variable.description,
"value": variable.value,
"index": index,
})
setVariablesModalOpen(true);
}}
>
Name: {variable.name}
);
})}
{
setVariablesModalOpen(true);
setLastSaved(false);
}}
>
New workflow variable
What are{" "}
EXECUTION variables?
{workflow.execution_variables === null ||
workflow.execution_variables === undefined
? null
: workflow.execution_variables.map((variable, index) => {
return (
{ }}>
{
//setNewVariableName(variable.name);
setVariableInfo({
"name": variable.name,
"description": variable.description,
"value": variable.value,
"index": index,
})
setExecutionVariablesModalOpen(true);
}}
>
Name: {variable.name}
);
})}
{
setExecutionVariablesModalOpen(true);
setLastSaved(false);
}}
>
New execution variable
);
};
const handleSetTab = (event, newValue) => {
setCurrentView(newValue);
};
const HandleLeftView = () => {
// Defaults to apps.
var thisview = (
);
if (currentView === 1) {
thisview = ;
} else if (currentView === 2) {
thisview = ;
}
const tabStyle = {
maxWidth: isMobile ? leftBarSize : leftBarSize / 3,
minWidth: isMobile ? leftBarSize : leftBarSize / 3,
flex: 1,
textTransform: "none",
};
const iconStyle = {
marginTop: 3,
marginRight: 5,
};
const parsedHeight = isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 50
return (
{thisview}
{isMobile ? null : Apps }
}
style={tabStyle}
/>
{isMobile ? null : Triggers }
}
style={tabStyle}
/>
{isMobile ? null : Variables }
}
style={tabStyle}
/>
);
};
const 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 title = trigger.trigger_type === "WEBHOOK" ? "Workflow starters" : trigger.trigger_type === "SUBFLOW" ? "Mid-Workflow" : ""
const color = trigger.is_valid ? green : yellow;
return (
{title.length > 0 ?
{title}
: null}
{
handleTriggerDrag(e, trigger);
}}
onStop={(e) => {
handleDragStop(e);
}}
dragging={false}
position={{
x: 0,
y: 0,
}}
>
{ }}>
{imageline}
{isMobile ? null :
{trigger.name}
{trigger.description}
}
);
})}
);
};
var newNodeId = "";
var parsedApp = {};
const handleTriggerDrag = (e, data) => {
const cycontainer = cy.container();
// Chrome lol
if (
e.pageX > cycontainer.offsetLeft &&
e.pageX < cycontainer.offsetLeft + cycontainer.offsetWidth &&
e.pageY > cycontainer.offsetTop &&
e.pageY < cycontainer.offsetTop + cycontainer.offsetHeight
) {
if (newNodeId.length > 0) {
var currentnode = cy.getElementById(newNodeId);
if (currentnode.length === 0) {
return;
}
currentnode[0].renderedPosition("x", e.pageX - cycontainer.offsetLeft);
currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop);
} else {
if (workflow.start === "" || workflow.start === undefined) {
alert.error("Define a starting action first.");
return;
}
const triggerLabel = getNextActionName(data.name);
newNodeId = uuidv4();
const newposition = {
x: e.pageX - cycontainer.offsetLeft,
y: e.pageY - cycontainer.offsetTop,
};
const newAppData = {
app_name: data.name,
app_version: "1.0.0",
environment: isCloud ? "cloud" : data.environment,
description: data.description,
long_description: data.long_description,
errors: [],
id_: newNodeId,
_id_: newNodeId,
id: newNodeId,
finished: false,
label: triggerLabel,
type: data.type,
is_valid: true,
trigger_type: data.trigger_type,
large_image: data.large_image,
status: "uninitialized",
name: data.name,
isStartNode: false,
position: newposition,
};
// Can all the data be in here? hmm
const nodeToBeAdded = {
group: "nodes",
data: newAppData,
renderedPosition: newposition,
};
cy.add(nodeToBeAdded);
parsedApp = nodeToBeAdded;
return;
}
}
};
const handleDragStop = (e, app) => {
var currentnode = cy.getElementById(newNodeId);
if (
currentnode === undefined ||
currentnode === null ||
currentnode.length === 0
) {
return;
}
// Using remove & replace, as this triggers the function
// onNodeAdded() with this node after it's added
currentnode.remove();
parsedApp.data.finished = true;
parsedApp.data.position = currentnode.renderedPosition();
parsedApp.position = currentnode.renderedPosition();
parsedApp.renderedPosition = currentnode.renderedPosition();
var newAppData = parsedApp.data;
if (newAppData.type === "ACTION") {
//const activateApp = (appid) => {
if (newAppData.activated === false) {
activateApp(newAppData.app_id, false)
}
// AUTHENTICATION
if (app.authentication !== undefined && app.authentication !== null && app.authentication.required === true) {
// Setup auth here :)
const authenticationOptions = [];
var findAuthId = "";
if (
newAppData.authentication_id !== null &&
newAppData.authentication_id !== undefined &&
newAppData.authentication_id.length > 0
) {
findAuthId = newAppData.authentication_id;
}
const tmpAuth = JSON.parse(JSON.stringify(appAuthentication));
for (let authkey in tmpAuth) {
if (authkey === undefined) {
continue
}
var item = tmpAuth[authkey];
const newfields = {};
for (let fieldkey in item.fields) {
if (item.fields[fieldkey] === undefined) {
console.log("Problem with filterkey in Node select", fieldkey)
continue
}
const filterkey = item.fields[fieldkey]["key"]
if (filterkey === null || filterkey === undefined) {
console.log("Problem with filterkey 2. Null or undefined 3")
continue
}
newfields[filterkey] = item.fields[fieldkey]["value"];
}
item.fields = newfields;
if (item.app.id === app.id || item.app.name === app.name) {
authenticationOptions.push(item);
if (item.id === findAuthId) {
newAppData.selectedAuthentication = item
newAppData.authentication_id = item.id
} else if (findAuthId === "") {
// Will always be set to the last one if one isn't found.
// Last = timestamp too
newAppData.selectedAuthentication = item
newAppData.authentication_id = item.id
}
}
}
if (
authenticationOptions !== undefined &&
authenticationOptions !== null &&
authenticationOptions.length > 0
) {
for (let authkey in authenticationOptions) {
const option = authenticationOptions[authkey];
if (option.active && newAppData.authentication_id === "") {
newAppData.selectedAuthentication = option;
newAppData.authentication_id = option.id;
break;
}
}
}
} else {
newAppData.authentication = [];
newAppData.authentication_id = "";
newAppData.selectedAuthentication = {};
}
parsedApp.data = newAppData;
cy.add(parsedApp);
} else if (newAppData.type === "TRIGGER") {
cy.add(parsedApp);
}
newNodeId = "";
parsedApp = {};
};
const appScrollStyle = {
overflow: "scroll",
maxHeight: isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 55 - 50,
minHeight: isMobile ? bodyHeight - appBarSize * 4 : bodyHeight - appBarSize - 55 - 50,
marginTop: 1,
overflowY: "auto",
overflowX: "hidden",
};
const handleAppDrag = (e, app) => {
const cycontainer = cy.container();
//console.log("e: ", e)
//console.log("Offset: ", cycontainer)
// Chrome lol
if (
e.pageX > cycontainer.offsetLeft &&
e.pageX < cycontainer.offsetLeft + cycontainer.offsetWidth &&
e.pageY > cycontainer.offsetTop &&
e.pageY < cycontainer.offsetTop + cycontainer.offsetHeight
) {
if (newNodeId.length > 0) {
var currentnode = cy.getElementById(newNodeId);
if (
currentnode === undefined ||
currentnode === null ||
currentnode.length === 0
) {
return;
}
currentnode[0].renderedPosition("x", e.pageX - cycontainer.offsetLeft);
currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop);
} else {
if (workflow.public) {
console.log("workflow is public - not adding");
return;
}
if (
app.actions === undefined ||
app.actions === null ||
app.actions.length === 0
) {
alert.error(
"App " +
app.name +
" currently has no actions to perform. Please go to https://shuffler.io/apps to edit it."
);
return;
}
newNodeId = uuidv4();
const actionType = "ACTION";
const actionLabel = getNextActionName(app.name);
console.log("Next action name: ", actionLabel)
var parameters = null;
var example = "";
var description = ""
if (
app.actions[0].parameters !== undefined &&
app.actions[0].parameters !== null &&
app.actions[0].parameters.length > 0
) {
parameters = app.actions[0].parameters;
}
if (
app.actions[0].returns !== undefined &&
app.actions[0].returns !== null &&
app.actions[0].returns.example !== undefined &&
app.actions[0].returns.example !== null &&
app.actions[0].returns.example.length > 0
) {
example = app.actions[0].returns.example;
}
if (
app.actions[0].description !== undefined &&
app.actions[0].description !== null &&
app.actions[0].description.length > 0
) {
description = app.actions[0].description
}
const parsedEnvironments =
environments === null || environments === []
? "cloud"
: environments[defaultEnvironmentIndex] === undefined
? "cloud"
: environments[defaultEnvironmentIndex].Name;
// activated: app.generated === true ? app.activated === false ? false : true : true,
const newAppData = {
name: app.actions[0].name,
label: actionLabel,
app_name: app.name,
app_version: app.app_version,
app_id: app.id,
sharing: app.sharing,
private_id: app.private_id,
description: description,
environment: parsedEnvironments,
errors: [],
finished: false,
id_: newNodeId,
_id_: newNodeId,
id: newNodeId,
is_valid: true,
type: actionType,
parameters: parameters,
isStartNode: false,
large_image: app.large_image,
run_magic_output: false,
authentication: [],
execution_variable: undefined,
example: example,
category:
app.categories !== null &&
app.categories !== undefined &&
app.categories.length > 0
? app.categories[0]
: "",
authentication_id: "",
finished: false,
template: app.template === true ? true : false,
};
// FIXME: overwrite category if the ACTION chosen has a different category
// const image = "url("+app.large_image+")"
// FIXME - find the cytoscape offset position
// Can this be done with zoom calculations?
const nodeToBeAdded = {
group: "nodes",
data: newAppData,
renderedPosition: {
x: e.pageX - cycontainer.offsetLeft,
y: e.pageY - cycontainer.offsetTop,
},
};
parsedApp = nodeToBeAdded;
cy.add(nodeToBeAdded);
return;
}
}
};
const AppView = (props) => {
const { allApps, prioritizedApps, filteredApps, extraApps } = props;
//extraApps,
const [visibleApps, setVisibleApps] = React.useState(
Array.prototype.concat.apply(
prioritizedApps,
filteredApps.filter((innerapp) => !internalIds.includes(innerapp.id)),
)
);
var delay = -75
var runDelay = false
const ParsedAppPaper = (props) => {
const app = props.app;
const [hover, setHover] = React.useState(false);
if (app.id === "" || app.name === "") {
return null
}
const maxlen = 24;
var newAppname = app.name;
newAppname = newAppname.charAt(0).toUpperCase() + newAppname.substring(1);
if (newAppname.length > maxlen) {
newAppname = newAppname.slice(0, maxlen) + "..";
}
newAppname = newAppname.replaceAll("_", " ");
if (app.large_image === undefined || app.large_image === null || app.large_image === "") {
app.large_image = theme.palette.defaultImage
}
const image = app.large_image !== undefined && app.large_image !== null && app.large_image !== "" ? app.large_image : theme.palette.defaultImage
const newAppStyle = JSON.parse(JSON.stringify(paperAppStyle));
const pixelSize = !hover ? "2px" : "4px";
//newAppStyle.borderLeft = app.is_valid && app.actions !== null && app.actions !== undefined && app.actions.length > 0 && !(app.activated && app.generated)
newAppStyle.borderLeft = app.is_valid && app.actions !== null && app.actions !== undefined && app.actions.length > 0
? `${pixelSize} solid ${green}`
: `${pixelSize} solid ${yellow}`;
if (!app.activated && app.generated) {
newAppStyle.borderLeft = `${pixelSize} solid ${yellow}`;
}
return (
{
handleAppDrag(e, app);
}}
onStop={(e) => {
handleDragStop(e, app);
}}
key={app.id}
dragging={false}
position={{
x: 0,
y: 0,
}}
>
{
setHover(true);
}}
onMouseOut={() => {
setHover(false);
}}
onClick={() => {
if (isMobile) {
newNodeId = uuidv4();
const actionType = "ACTION";
const actionLabel = getNextActionName(app.name);
var parameters = null;
var example = "";
var description = ""
if (
app.actions[0].parameters !== null &&
app.actions[0].parameters.length > 0
) {
parameters = app.actions[0].parameters;
}
if (
app.actions[0].returns.example !== undefined &&
app.actions[0].returns.example !== null &&
app.actions[0].returns.example.length > 0
) {
example = app.actions[0].returns.example;
}
if (
app.actions[0].description !== undefined &&
app.actions[0].description !== null &&
app.actions[0].description.length > 0
) {
description = app.actions[0].description
}
const parsedEnvironments =
environments === null || environments === []
? "cloud"
: environments[defaultEnvironmentIndex] === undefined
? "cloud"
: environments[defaultEnvironmentIndex].Name;
// activated: app.generated === true ? app.activated === false ? false : true : true,
const newAppData = {
app_name: app.name,
app_version: app.app_version,
app_id: app.id,
sharing: app.sharing,
private_id: app.private_id,
description: description,
environment: parsedEnvironments,
errors: [],
finished: false,
id_: newNodeId,
_id_: newNodeId,
id: newNodeId,
is_valid: true,
label: actionLabel,
type: actionType,
name: app.actions[0].name,
parameters: parameters,
isStartNode: false,
large_image: image,
run_magic_output: false,
authentication: [],
execution_variable: undefined,
example: example,
category:
app.categories !== null &&
app.categories !== undefined &&
app.categories.length > 0
? app.categories[0]
: "",
authentication_id: "",
finished: false,
};
const nodeToBeAdded = {
group: "nodes",
data: newAppData,
renderedPosition: {
x: 100,
y: 100,
},
};
parsedApp = nodeToBeAdded;
cy.add(nodeToBeAdded);
}
}}
>
{isMobile ? null :
{newAppname}
Version: {app.app_version}
{app.description}
}
);
};
const runSearch = (value) => {
if (value.length > 0) {
var newApps = allApps.filter(
(app) =>
app.name.toLowerCase().includes(value.trim().toLowerCase())
||
app.description.toLowerCase().includes(value.trim().toLowerCase())
)
// Extend search
if (newApps.length === 0) {
const searchvalue = value.trim().toLowerCase();
newApps = allApps.filter((app) => {
if (app.actions !== undefined && app.actions !== null) {
for (let actionkey in app.actions) {
const inneraction = app.actions[actionkey];
if (inneraction.name.toLowerCase().includes(searchvalue)) {
return true;
}
}
}
return false;
});
}
setVisibleApps(newApps);
} else {
setVisibleApps(
prioritizedApps.concat(
filteredApps.filter(
(innerapp) => !internalIds.includes(innerapp.id)
)
)
);
}
};
const SearchBox = ({ currentRefinement, refine, isSearchStalled, }) => {
useEffect(() => {
if (document !== undefined) {
const appsearchValue = document.getElementById("appsearch")
if (appsearchValue !== undefined && appsearchValue !== null) {
if (appsearchValue.value !== undefined && appsearchValue.value !== null && appsearchValue.value.length > 0) {
refine(appsearchValue.value)
}
}
//}
}
}, [])
return (
)
}
const AppHits = ({ hits }) => {
const [mouseHoverIndex, setMouseHoverIndex] = useState(0)
//var tmp = searchOpen
//if (!searchOpen) {
// return null
//}
const positionInfo = document.activeElement.getBoundingClientRect()
const outerlistitemStyle = {
width: "100%",
overflowX: "hidden",
overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)",
}
if (hits.length > 4) {
hits = hits.slice(0, 4)
}
var type = "app"
const baseImage =
return (
{hits.length === 0 ?
console.log(hits)}>
:
hits.map((hit, index) => {
const innerlistitemStyle = {
width: positionInfo.width + 35,
overflowX: "hidden",
overflowY: "hidden",
borderBottom: "1px solid rgba(255,255,255,0.4)",
backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit",
cursor: "pointer",
marginLeft: 0,
marginRight: 0,
maxHeight: 75,
minHeight: 75,
maxWidth: 420,
minWidth: "100%",
}
const name = hit.name === undefined ?
hit.filename.charAt(0).toUpperCase() + hit.filename.slice(1).replaceAll("_", " ") + " - " + hit.title :
(hit.name.charAt(0).toUpperCase() + hit.name.slice(1)).replaceAll("_", " ")
var secondaryText = hit.data !== undefined ? hit.data.slice(0, 40) + "..." : ""
const avatar = hit.image_url === undefined ?
baseImage
:
//console.log(hit)
if (hit.categories !== undefined && hit.categories !== null && hit.categories.length > 0) {
secondaryText = hit.categories.slice(0, 3).map((data, index) => {
if (index === 0) {
return data
}
return ", " + data
/*
{
//handleChipClick
}}
variant="outlined"
color="primary"
/>
*/
})
}
var parsedUrl = isCloud ? `/apps/${hit.objectID}` : `https://shuffler.io/apps/${hit.objectID}`
parsedUrl += `?queryID=${hit.__queryID}`
return (
{
//if (!isCloud) {
// alert.info("Since this is an on-prem instance. You will need to activate the app yourself. Opening link to download it in a new window.")
// setTimeout(() => {
// event.preventDefault()
// window.open(parsedUrl, '_blank')
// }, 2000)
//} else {
alert.info(`Activating ${name}`)
//}
console.log("CLICK: ", hit)
const queryID = hit.__queryID
console.log("QUERY: ", queryID)
if (queryID !== undefined && queryID !== null) {
aa('init', {
appId: "JNSS5CFDZZ",
apiKey: "db08e40265e2941b9a7d8f644b6e5240",
})
const timestamp = new Date().getTime()
aa('sendEvents', [
{
eventType: 'conversion',
eventName: 'Public App Activated',
index: 'appsearch',
objectIDs: [hit.objectID],
timestamp: timestamp,
queryID: queryID,
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
}
])
} else {
console.log("No query to handle when activating")
}
activateApp(hit.objectID, true)
}}>
{
setMouseHoverIndex(index)
}}>
{avatar}
{/*
*/}
)
})
}
)
}
const CustomSearchBox = connectSearchBox(SearchBox)
const CustomAppHits = connectHits(AppHits)
return (
),
}}
fullWidth
color="primary"
placeholder={"Search Active Apps"}
id="appsearch"
onKeyPress={(event) => {
if (event.key === "Enter") {
event.target.blur(event);
}
}}
onBlur={(event) => {
//navigate(`?q=${event.target.value}`)
runSearch(event.target.value);
}}
/>
{visibleApps.length > extraApps.length ? (
{visibleApps.map((app, index) => {
if (app.invalid) {
return null;
}
var extraMessage = ""
if (index == 2) {
extraMessage =
}
delay += 75
return (
runDelay ?
:
)
})}
) : apps.length > 0 ? (
{
console.log("Should load in extra apps?")
}}
>
Couldn't find the app you're looking for? Searching unactivated apps. Click one of the below apps to Activate it for your organization.
{
console.log("CLICKED")
}}>
) : (
Loading Apps
)}
);
};
const getNextActionName = (appName) => {
var highest = "";
const allitems = workflow.actions.concat(workflow.triggers);
if (allitems !== undefined && allitems !== null) {
for (let itemkey in allitems) {
const item = allitems[itemkey];
if (
item.app_name === appName &&
item.label !== undefined &&
item.label !== null
) {
var number = item.label.split("_");
if (
isNaN(number[-1]) &&
parseInt(number[number.length - 1]) > highest
) {
highest = number[number.length - 1];
}
}
}
}
appName = appName.replaceAll(" ", "_")
console.log("Highest:", highest)
if (highest) {
return appName + "_" + (parseInt(highest) + 1);
} else {
return appName + "_" + 1;
}
};
const setNewSelectedAction = (e) => {
if (selectedApp.actions === undefined || selectedApp.actions === null) {
return
}
const newaction = selectedApp.actions.find(
(a) => a.name === e.target.value
);
if (newaction === undefined || newaction === null) {
alert.error("Failed to find the action");
return;
}
if (workflow.actions !== undefined && workflow.actions !== null) {
const foundInfo = workflow.actions.find(ac => ac.id === selectedAction.id)
}
// Setting an old reference just to use the same memory space elsewhere
// for selectedAction
const oldaction = JSON.parse(JSON.stringify(selectedAction))
// Does this one find the wrong one?
//var newSelectedAction = JSON.parse(JSON.stringify(selectedAction))
var newSelectedAction = selectedAction
newSelectedAction.name = newaction.name;
newSelectedAction.parameters = JSON.parse(JSON.stringify(newaction.parameters))
newSelectedAction.errors = [];
newSelectedAction.isValid = true;
newSelectedAction.is_valid = true;
//console.log(newSelectedAction)
// Simmple action swap autocompleter
if (oldaction.parameters !== undefined && oldaction.parameters !== null && newSelectedAction.parameters !== undefined && oldaction.id === newSelectedAction.id) {
var fileid_found = false
for (let [paramkey,paramkeyval] in Object.entries(oldaction.parameters)) {
const param = oldaction.parameters[paramkey];
if (param.name === "file_id") {
fileid_found = true
}
if (param.value === null || param.value === undefined || param.value.length === 0) {
continue
}
if (param.name === "body") {
//console.log("Param: ", param)
continue
}
if (param.name === "headers") {
console.log("Swap header? For now, yes. File found: ", fileid_found)
if (fileid_found) {
newSelectedAction.parameters[paramkey].value = ""
newSelectedAction.parameters[paramkey].autocompleted = true
continue
}
//newSelectedAction.parameters[newParamIndex].value = param.value
}
if (newSelectedAction.parameters === undefined || newSelectedAction.parameters === null) {
continue
}
// Not doing options fields
const newParamIndex = newSelectedAction.parameters.findIndex(paramdata => paramdata.name === param.name)
if (newParamIndex < 0) {
continue
}
newSelectedAction.parameters[newParamIndex].value = param.value
newSelectedAction.parameters[newParamIndex].autocompleted = true
if (param.options !== undefined && param.options !== null && param.options.length > 0) {
newSelectedAction.parameters[newParamIndex].autocompleted = false
}
}
}
if (newSelectedAction.app_name === "Shuffle Tools") {
const iconInfo = GetIconInfo(newSelectedAction);
console.log("ICONINFO: ", iconInfo);
const svg_pin = ` `;
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
newSelectedAction.large_image = svgpin_Url;
newSelectedAction.fillGradient = iconInfo.fillGradient;
newSelectedAction.fillstyle = "solid";
if (
newSelectedAction.fillGradient !== undefined &&
newSelectedAction.fillGradient !== null &&
newSelectedAction.fillGradient.length > 0
) {
newSelectedAction.fillstyle = "linear-gradient";
} else {
newSelectedAction.iconBackground = iconInfo.iconBackgroundColor;
}
const foundnode = cy.getElementById(newSelectedAction.id);
if (foundnode !== null && foundnode !== undefined) {
foundnode.data(newSelectedAction);
}
}
// Takes an action as input, then runs through and updates the relevant parameters based on previous actions' results (parent nodes)
// Further checks if those fields are already set in a previously used action
newSelectedAction = RunAutocompleter(newSelectedAction);
if (
newaction.returns.example !== undefined &&
newaction.returns.example !== null &&
newaction.returns.example.length > 0
) {
newSelectedAction.example = newaction.returns.example;
}
if (
newaction.description !== undefined &&
newaction.description !== null &&
newaction.description.length > 0
) {
newSelectedAction.description = newaction.description
}
// FIXME - this is broken sometimes lol
//var env = environments.find(a => a.Name === newaction.environment)
//if ((!env || env === undefined) && selectedAction.environment === undefined ) {
// env = environments[defaultEnvironmentIndex]
//}
//setSelectedActionEnvironment(env)
if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
const foundActionIndex = workflow.actions.findIndex(actiondata => actiondata.id === newSelectedAction.id)
console.log("Found action on index ", foundActionIndex)
if (foundActionIndex >= 0) {
workflow.actions[foundActionIndex] = newSelectedAction
setWorkflow(workflow)
}
}
console.log("NEW ACTION: ", newSelectedAction);
setSelectedAction(newSelectedAction);
setUpdate(Math.random());
// FIXME - should change icon-node (descriptor) as well
const allNodes = cy.nodes().jsons();
if (allNodes !== undefined && allNodes !== null) {
for (let nodekey in allNodes) {
const currentNode = allNodes[nodekey];
if (
currentNode.data.attachedTo === oldaction.id &&
currentNode.data.isDescriptor
) {
const foundnode = cy.getElementById(currentNode.data.id);
if (foundnode !== null && foundnode !== undefined) {
const iconInfo = GetIconInfo(newaction);
const svg_pin = ` `;
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
foundnode.data("image", svgpin_Url);
foundnode.data("imageColor", iconInfo.iconBackgroundColor);
}
break;
}
}
}
};
// APPSELECT at top
// appname & version
// description
// ACTION select
const selectedNameChange = (event) => {
event.target.value = event.target.value.replaceAll("(", "");
event.target.value = event.target.value.replaceAll(")", "");
event.target.value = event.target.value.replaceAll("]", "");
event.target.value = event.target.value.replaceAll("[", "");
event.target.value = event.target.value.replaceAll("{", "");
event.target.value = event.target.value.replaceAll("}", "");
event.target.value = event.target.value.replaceAll("*", "");
event.target.value = event.target.value.replaceAll("!", "");
event.target.value = event.target.value.replaceAll("@", "");
event.target.value = event.target.value.replaceAll("#", "");
event.target.value = event.target.value.replaceAll("$", "");
event.target.value = event.target.value.replaceAll("%", "");
event.target.value = event.target.value.replaceAll("&", "");
event.target.value = event.target.value.replaceAll("#", "");
event.target.value = event.target.value.replaceAll(".", "");
event.target.value = event.target.value.replaceAll(",", "");
event.target.value = event.target.value.replaceAll(" ", "_");
event.target.value = event.target.value.replaceAll("^", "_");
event.target.value = event.target.value.replaceAll("'", "_");
event.target.value = event.target.value.replaceAll("\"", "_");
event.target.value = event.target.value.replaceAll("\\", "_");
event.target.value = event.target.value.replaceAll(":", "_");
event.target.value = event.target.value.replaceAll(";", "_");
event.target.value = event.target.value.replaceAll("=", "_");
event.target.value = event.target.value.replaceAll("+", "_");
selectedAction.label = event.target.value;
setSelectedAction(selectedAction);
};
const actionDelayChange = (event) => {
if (isNaN(event.target.value)) {
console.log("NAN: ", event.target.value)
return
}
const parsedNumber = parseInt(event.target.value)
if (parsedNumber > 86400) {
console.log("Max number is 1 day (86400)")
return
}
selectedAction.execution_delay = parsedNumber
setSelectedAction(selectedAction)
}
const selectedTriggerChange = (event) => {
selectedTrigger.label = event.target.value;
setSelectedTrigger(selectedTrigger);
};
// Starts on current node and climbs UP the tree to the root object.
// Sends back everything in it's path
const getParents = (action) => {
if (action === undefined || action === null) {
return []
}
var allkeys = [action.id];
var handled = [];
var results = [];
// maxiter = max amount of parent nodes to loop
// also handles breaks if there are issues
var iterations = 0;
var maxiter = 10;
while (true) {
for (let parentkey in allkeys) {
var currentnode = cy.getElementById(allkeys[parentkey]);
if (currentnode === undefined || currentnode === null) {
continue;
}
if (currentnode.data() === undefined) {
handled.push(allkeys[parentkey]);
results.push({ id: allkeys[parentkey], type: "TRIGGER" });
} else {
if (handled.includes(currentnode.data().id)) {
continue;
} else {
handled.push(currentnode.data().id);
results.push(currentnode.data());
}
}
// Get the name / label here too?
if (currentnode.length === 0) {
continue;
}
const incomingEdges = currentnode.incomers("edge");
if (incomingEdges.length === 0) {
continue;
}
for (let i = 0; i < incomingEdges.length; i++) {
var tmp = incomingEdges[i];
if (tmp.data("decorator")) {
continue
}
if (!allkeys.includes(tmp.data("source"))) {
allkeys.push(tmp.data("source"));
}
}
}
if (results.length === allkeys.length || iterations === maxiter) {
break;
}
iterations += 1;
}
// Remove on the end as we don't want to remove everything
results = results.filter((data) => data.id !== action.id);
results = results.filter((data) => data.type === "ACTION" || data.app_name === "Shuffle Workflow" || data.app_name === "User Input");
results.push({ label: "Execution Argument", type: "INTERNAL" });
return results;
};
// BOLD name: type: required?
// FORM
// Dropdown -> static, action, local env, global env
// VALUE (JSON)
// {data.name}, {data.description}, {data.required}, {data.schema.type}
//height: "100%",
const appApiViewStyle = {
display: "flex",
flexDirection: "column",
backgroundColor: "#1F2023",
color: "white",
paddingRight: 15,
paddingLeft: 15,
minHeight: "100%",
zIndex: 1000,
resize: "vertical",
overflow: "auto",
};
var rightsidebarStyle = {
position: "fixed",
top: appBarSize + 25,
right: 25,
height: "80vh",
width: isMobile ? "100%" : 365,
minWidth: 200,
maxWidth: 600,
maxHeight: "100vh",
border: "1px solid rgb(91, 96, 100)",
zIndex: 1000,
borderRadius: theme.palette.borderRadius,
resize: "both",
overflow: "auto",
};
const setTriggerFolderWrapperMulti = (event) => {
const { options } = event.target;
var value = [];
for (let i = 0, l = options.length; i < l; i += 1) {
if (options[i].selected) {
value.push(options[i].value);
}
}
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = [[]];
workflow.triggers[selectedTriggerIndex].parameters = [[]];
}
// Max 1 folder for office for some reason. MailFolders('MAILBOX_ID') in resource
// Can't parse URL with multiple folders.
if (selectedTrigger.name === "Office365" & value !== undefined && value !== null && value.length > 1) {
alert.info("Max 1 folder at a time allowed for Office365")
console.log("VALUE: ", value)
value = [value[0]]
}
// This is a dirty workaround for the static values in the go backend and datastore db
const fixedValue = value.join(splitter);
selectedTrigger.parameters[0] = {
value: fixedValue,
name: "outlookfolder",
};
workflow.triggers[selectedTriggerIndex].parameters[0] = {
value: fixedValue,
name: "outlookfolder",
};
// This resets state for some reason (:
setSelectedAction({});
setSelectedTrigger({});
setSelectedApp({});
setSelectedEdge({});
// Set value
setSelectedTrigger(selectedTrigger);
setWorkflow(workflow);
};
const setTriggerCronWrapper = (value) => {
console.log("Cron Value: ", value)
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = [];
}
workflow.triggers[selectedTriggerIndex].parameters[0] = {
value: value,
name: "cron",
};
setWorkflow(workflow);
};
const setTriggerOptionsWrapper = (value) => {
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = [];
}
const splitItems = workflow.triggers[selectedTriggerIndex].parameters[2].value.split(",");
console.log(splitItems);
if (splitItems.includes(value)) {
for (let i = 0; i < splitItems.length; i++) {
if (splitItems[i] === value) {
splitItems.splice(i, 1);
}
}
} else {
splitItems.push(value);
}
for (let i = 0; i < splitItems.length; i++) {
if (splitItems[i] === "") {
splitItems.splice(i, 1);
}
}
workflow.triggers[selectedTriggerIndex].parameters[2].value = splitItems.join(",");
setWorkflow(workflow);
setLocalFirstrequest(!localFirstrequest);
setUpdate(Math.random());
};
const setTriggerTextInformationWrapper = (value) => {
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = [];
}
workflow.triggers[selectedTriggerIndex].parameters[0] = {
value: value,
name: "alertinfo",
};
setWorkflow(workflow);
};
const setTriggerBodyWrapper = (value) => {
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = [];
workflow.triggers[selectedTriggerIndex].parameters[0] = {
value: value,
name: "cron",
};
}
workflow.triggers[selectedTriggerIndex].parameters[1] = {
value: value,
name: "execution_argument",
};
setWorkflow(workflow);
};
const AppConditionHandler = (props) => {
const { tmpdata, type } = props;
const [data] = useState(tmpdata);
const [multiline, setMultiline] = useState(false);
const [showAutocomplete, setShowAutocomplete] = React.useState(false);
const [actionlist, setActionlist] = React.useState([]);
if (tmpdata === undefined) {
return tmpdata;
}
if (data.variant === "") {
data.variant = "STATIC_VALUE";
}
// Set actions based on NEXT node, since it should be able to involve those two
if (actionlist.length === 0) {
// FIXME: Have previous execution values in here
actionlist.push({
type: "Execution Argument",
name: "Execution Argument",
value: "$exec",
highlight: "exec",
autocomplete: "exec",
example: "tmp",
})
actionlist.push({
type: "Shuffle DB",
name: "Shuffle DB",
value: "$shuffle_cache",
highlight: "shuffle_cache",
autocomplete: "shuffle_cache",
example: "tmp",
})
if (
workflow.workflow_variables !== null &&
workflow.workflow_variables !== undefined &&
workflow.workflow_variables.length > 0
) {
for (let varkey in workflow.workflow_variables) {
const item = workflow.workflow_variables[varkey];
actionlist.push({
type: "workflow_variable",
name: item.name,
value: item.value,
id: item.id,
autocomplete: `${item.name.split(" ").join("_")}`,
example: item.value,
});
}
}
// FIXME: Add values from previous executions if they exist
if (
workflow.execution_variables !== null &&
workflow.execution_variables !== undefined &&
workflow.execution_variables.length > 0
) {
for (let varkey in workflow.execution_variables) {
const item = workflow.execution_variables[varkey];
actionlist.push({
type: "execution_variable",
name: item.name,
value: item.value,
id: item.id,
autocomplete: `${item.name.split(" ").join("_")}`,
example: "",
});
}
}
const destAction = cy.getElementById(selectedEdge.target);
var parents = getParents(destAction.data());
if (parents.length > 1) {
for (let parentkey in parents) {
const item = parents[parentkey];
if (item.label === "Execution Argument") {
continue;
}
// 1. Take
const actionvalue = {
type: "action",
id: item.id,
name: item.label,
autocomplete: `${item.label.split(" ").join("_")}`,
example: item.example === undefined ? "" : item.example,
};
actionlist.push(actionvalue);
}
}
setActionlist(actionlist);
}
if (
data.multiline !== undefined &&
data.multiline !== null &&
data.multiline === true
) {
setMultiline(true);
}
var placeholder = "Static value";
if (
data.example !== undefined &&
data.example !== null &&
data.example.length > 0
) {
placeholder = data.example;
}
var datafield = (
Use "Shuffle Tools" app with "Filter List" action to handle loops
) : null
}
onBlur={(e) => {
changeActionVariable(data.action_field, e.target.value);
setUpdate(Math.random());
}}
/>
);
const changeActionVariable = (variable, value) => {
// set the name
data.value = value;
data.action_field = variable;
if (type === "source") {
setSourceValue(data);
} else if (type === "destination") {
setDestinationValue(data);
}
};
return (
{datafield}
{actionlist.length === 0 ? null : (
Autocomplete
{
setShowAutocomplete(false);
setUpdate(Math.random());
}}
onClick={() => {
setShowAutocomplete(true);
}}
open={showAutocomplete}
fullWidth
style={{
borderBottom: `1px solid #f85a3e`,
color: "white",
height: 50,
marginTop: 2,
}}
onChange={(e) => {
const autocomplete = e.target.value.autocomplete;
const newValue = autocomplete.startsWith("$")
? data.value + autocomplete
: `${data.value}$${autocomplete}`;
changeActionVariable(data.action_field, newValue);
}}
>
{actionlist.map((data) => {
const icon =
data.type === "action" ? (
) : data.type === "workflow_variable" ||
data.type === "execution_variable" ? (
) : (
);
const handleExecArgumentHover = (inside) => {
var exec_text_field = document.getElementById(
"execution_argument_input_field"
);
if (exec_text_field !== null) {
if (inside) {
exec_text_field.style.border = "2px solid #f85a3e";
} else {
exec_text_field.style.border = "";
}
}
// Also doing arguments
if (
workflow.triggers !== undefined &&
workflow.triggers !== null &&
workflow.triggers.length > 0
) {
for (let triggerkey in workflow.triggers) {
const item = workflow.triggers[triggerkey];
var node = cy.getElementById(item.id);
if (node.length > 0) {
if (inside) {
node.addClass("shuffle-hover-highlight");
} else {
node.removeClass("shuffle-hover-highlight");
}
}
}
}
};
const handleActionHover = (inside, actionId) => {
var node = cy.getElementById(actionId);
console.log("Hovering over action: " + actionId)
if (node.length > 0) {
if (inside) {
console.log("Hovering over action: " + actionId)
node.addClass("shuffle-hover-highlight");
} else {
node.removeClass("shuffle-hover-highlight");
}
}
};
return (
{
if (data.type === "Execution Argument") {
handleExecArgumentHover(true);
} else if (data.type === "action") {
handleActionHover(true, data.id);
}
}}
onMouseOut={() => {
if (data.type === "Execution Argument") {
handleExecArgumentHover(false);
} else if (data.type === "action") {
handleActionHover(false, data.id);
}
}}
>
{icon} {data.name}
);
})}
)}
);
};
const menuItemStyle = {
color: "white",
backgroundColor: inputColor,
};
const conditionsModal = (
{
}}
>
Conditions can't be used for loops [ .# ]{" "}
Learn more
Condition
{
conditionValue.configuration =
!conditionValue.configuration;
setConditionValue(conditionValue);
setUpdate(Math.random());
}}
>
{conditionValue.configuration ? "!" : "="}
{
setVariableAnchorEl(e.currentTarget);
}}
>
{conditionValue.value}
{
setConditionsModalOpen(false);
setSourceValue({});
setConditionValue({});
setDestinationValue({});
}}
color="secondary"
>
Cancel
{
setSelectedEdge({});
var data = {
condition: conditionValue,
source: sourceValue,
destination: destinationValue,
};
setConditionsModalOpen(false);
if (selectedEdge.conditions === undefined) {
selectedEdge.conditions = [data];
} else {
const curedgeindex = selectedEdge.conditions.findIndex(
(data) => data.source.id === sourceValue.id
);
if (curedgeindex < 0) {
selectedEdge.conditions.push(data);
} else {
selectedEdge.conditions[curedgeindex] = data;
}
}
var label = "";
if (selectedEdge.conditions.length === 1) {
label = selectedEdge.conditions.length + " condition";
} else if (selectedEdge.conditions.length > 1) {
label = selectedEdge.conditions.length + " conditions";
}
var currentedge = cy.getElementById(selectedEdge.id);
if (currentedge !== undefined && currentedge !== null && label !== undefined) {
currentedge.data("label", label)
//.label = label;
//oldstartnode[0].data("isStartNode", false);
}
setSelectedEdge(selectedEdge);
workflow.branches[selectedEdgeIndex] = selectedEdge;
setWorkflow(workflow);
}}
color="primary"
>
Submit
);
const EdgeSidebar = () => {
const ConditionHandler = (condition, index) => {
const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState(null);
const duplicateCondition = (conditionIndex) => {
var newEdge = JSON.parse(
JSON.stringify(selectedEdge.conditions[conditionIndex])
);
const newUuid = uuidv4();
newEdge.condition.id = newUuid;
newEdge.source.id = newUuid;
newEdge.destination.id = newUuid;
selectedEdge.conditions.push(newEdge);
setUpdate(Math.random());
};
const deleteCondition = (conditionIndex) => {
console.log(selectedEdge);
if (selectedEdge.conditions.length === 1) {
selectedEdge.conditions = [];
} else {
selectedEdge.conditions.splice(conditionIndex, 1);
}
setSelectedEdge(selectedEdge);
setOpen(false);
setUpdate(Math.random());
};
const paperVariableStyle = {
minHeight: 75,
maxHeight: 75,
minWidth: "100%",
maxWidth: "100%",
marginTop: "5px",
color: "white",
backgroundColor: surfaceColor,
cursor: "pointer",
display: "flex",
};
const menuClick = (event) => {
console.log("MENU CLICK");
setOpen(!open);
setAnchorEl(event.currentTarget);
};
return (
{ }}
>
{
setSourceValue(condition.source);
setConditionValue(condition.condition);
setDestinationValue(condition.destination);
setConditionsModalOpen(true);
}}
>
{condition.source.value}
{ }}
>
{condition.condition.value}
{condition.destination.value}
);
};
var injectedData =
;
if (selectedEdge.conditions !== undefined && selectedEdge.conditions !== null && selectedEdge.conditions.length > 0) {
injectedData = selectedEdge.conditions.map((condition, index) => {
return ConditionHandler(condition, index);
});
}
// Startnode = dest node
const conditionsDisabled = false
const conditionId = uuidv4();
return (
Conditions
{injectedData}
{
if (conditionsModalOpen) {
return
}
setSourceValue({
name: "source",
value: "",
variant: "STATIC_VALUE",
action_field: "",
id: conditionId,
});
setConditionValue({
name: "condition",
value: "equals",
id: conditionId,
});
setDestinationValue({
name: "destination",
value: "",
variant: "STATIC_VALUE",
action_field: "",
id: conditionId,
});
setConditionsModalOpen(true);
}}
fullWidth
>
New condition
{/* Check if dest is the same as start */}
{conditionsDisabled ?
Conditions are unavailable between triggers and the startnode.
: null}
);
};
// 1. GET the trigger authentication data
// 2. Parse the fields that are used (outlook & gmail)
// 3. Parse the folders that are selected
// 4. Start / stop
const EmailSidebar = () => {
if (Object.getOwnPropertyNames(selectedTrigger).length === 0) {
return null;
}
if (workflow.triggers[selectedTriggerIndex] === undefined) {
return null;
}
if (
workflow.triggers[selectedTriggerIndex].parameters === undefined ||
workflow.triggers[selectedTriggerIndex].parameters === null ||
workflow.triggers[selectedTriggerIndex].parameters.length === 0
) {
workflow.triggers[selectedTriggerIndex].parameters = [
{ value: "No folders selected yet", name: "outlookfolder" },
];
selectedTrigger.parameters = [
{ value: "No folders selected yet", name: "outlookfolder" },
];
setWorkflow(workflow);
setSelectedTrigger(selectedTrigger);
}
const setGmailFolders = () => {
console.log("In set gmail folders");
fetch(
globalUrl +
"/api/v1/triggers/gmail/getFolders?trigger_id=" +
selectedTrigger.id,
{
method: "GET",
headers: { "content-type": "application/json" },
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
throw new Error("No folders :o!");
}
return response.json();
})
.then((responseJson) => {
if (
responseJson !== undefined &&
responseJson !== null &&
responseJson.success !== false &&
responseJson.length > 0
) {
setTriggerFolders(responseJson);
}
if (
workflow.triggers[selectedTriggerIndex].parameters.length === 0 &&
responseJson.length > 0
) {
workflow.triggers[selectedTriggerIndex].parameters = [
{
value: responseJson[0].displayName,
name: "outlookfolder",
id: responseJson[0].id,
},
];
selectedTrigger.parameters = [
{
value: responseJson[0].displayName,
name: "outlookfolder",
id: responseJson[0].id,
},
];
setWorkflow(workflow);
setSelectedTrigger(selectedTrigger);
}
})
.catch((error) => {
console.log("Get gmail folder error: ", error.toString());
});
};
const setOutlookFolders = () => {
fetch(
globalUrl +
"/api/v1/triggers/outlook/getFolders?trigger_id=" +
selectedTrigger.id,
{
method: "GET",
headers: { "content-type": "application/json" },
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
throw new Error("No folders :o!");
}
return response.json();
})
.then((responseJson) => {
if (
responseJson !== null &&
responseJson.success !== false &&
responseJson.length > 0
) {
console.log("Got trigger folders: ", triggerFolders)
setTriggerFolders(responseJson);
}
if (
workflow.triggers[selectedTriggerIndex].parameters.length === 0 &&
responseJson.length > 0
) {
workflow.triggers[selectedTriggerIndex].parameters = [
{
value: responseJson[0].displayName,
name: "outlookfolder",
id: responseJson[0].id,
},
]
selectedTrigger.parameters = [
{
value: responseJson[0].displayName,
name: "outlookfolder",
id: responseJson[0].id,
},
];
setWorkflow(workflow);
setSelectedTrigger(selectedTrigger);
}
})
.catch((error) => {
console.log("Get outlook folders error: ", error.toString());
});
};
const getTriggerAuth = () => {
fetch(`${globalUrl}/api/v1/triggers/${selectedTrigger.id}`, {
method: "GET",
headers: { "content-type": "application/json" },
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
throw new Error("No trigger info :o!");
}
return response.json();
})
.then((responseJson) => {
setTriggerAuthentication(responseJson);
})
.catch((error) => {
//console.log(error.toString());
console.log("Set trigger auth error: ", error.toString());
});
};
// Getting the triggers and the folders if they exist
if (localFirstrequest) {
//console.log("Trigger: ", selectedTrigger)
//console.log("Triggername: ", selectedTrigger.name)
//if (selectedTrigger.name.toLowerCase() === "gmail") {
setGmailFolders();
setOutlookFolders();
getTriggerAuth();
setLocalFirstrequest(false);
}
const gmailButton =
selectedTrigger.name !== "Gmail" ? null : (
{
console.log("HOST: ", window.location.host);
console.log("HOST: ", window.location);
const redirectUri = isCloud
? window.location.host === "localhost:3002"
? "http%3A%2F%2Flocalhost:5002%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister"
: "https%3A%2F%2Fshuffler.io%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister"
: window.location.protocol === "http:" ?
`http%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister`
:
`https%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister`
const client_id =
"253565968129-c0a35knic7q1pdk6i6qk9gdkvr07ci49.apps.googleusercontent.com";
const username = userdata.id;
console.log(redirectUri);
console.log("USER: ", username, userdata);
const branch = workflow.branches.find(
(branch) => branch.source_id === selectedTrigger.id
);
if (branch === undefined || branch === null) {
alert.error(
"No startnode connected to node. Connect it to an action."
);
return;
}
console.log("BRANCH: ", branch);
const startnode = branch.destination_id;
const scopes = "https://www.googleapis.com/auth/gmail.readonly";
const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`;
console.log("URL: ", url);
var newwin = window.open(url, "", "width=800,height=600");
// Check whether we got a callback somewhere
var id = setInterval(function () {
fetch(
`${globalUrl}/api/v1/triggers/${selectedTrigger.id}`,
{
method: "GET",
headers: { "content-type": "application/json" },
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
throw new Error("No trigger info :o!");
}
return response.json();
})
.then((responseJson) => {
//console.log("RESPONSE: ");
setTriggerAuthentication(responseJson);
clearInterval(id);
newwin.close();
setGmailFolders();
})
.catch((error) => {
console.log("Set gmail trigg error: ", error.toString());
});
}, 2500);
saveWorkflow(workflow);
}}
>
Sign in with Google
);
const outlookButton =
selectedTrigger.name !== "Office365" ? null : (
{
console.log(window.location)
const redirectUri = isCloud
? window.location.host === "localhost:3002"
? "http%3A%2F%2Flocalhost:5002%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
: "https%3A%2F%2Fshuffler.io%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
: window.location.protocol === "http:" ?
`http%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister`
:
`https%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister`
//const client_id = "fd55c175-aa30-4fa6-b303-09a29fb3f750"
//const client_id = "bb4bff85-0d0b-4f5d-8a69-3cee8029b11a";
const client_id = "efe4c3fe-84a1-4821-a84f-23a6cfe8e72d";
const username = userdata.id;
console.log(redirectUri);
console.log("USER: ", username, userdata);
const branch = workflow.branches.find(
(branch) => branch.source_id === selectedTrigger.id
);
if (branch === undefined || branch === null) {
alert.error(
"No startnode connected to node. Connect it to an action."
);
return;
}
console.log("BRANCH: ", branch);
const startnode = branch.destination_id;
// prompt=login
const url = `https://login.microsoftonline.com/common/oauth2/authorize?access_type=offline&client_id=${client_id}&redirect_uri=${redirectUri}&resource=https%3A%2F%2Fgraph.microsoft.com&response_type=code&scope=Mail.Read+User.Read+https%3A%2F%2Foutlook.office.com%2Fmail.read&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Doutlook%26start%3d${startnode}`;
//const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`
//const scopes = "https://www.googleapis.com/auth/gmail.readonly"
//const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`
console.log("URL: ", url);
var newwin = window.open(url, "", "width=800,height=600");
// Check whether we got a callback somewhere
var id = setInterval(function () {
fetch(
`${globalUrl}/api/v1/triggers/${selectedTrigger.id}`,
{
method: "GET",
headers: { "content-type": "application/json" },
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
throw new Error("No trigger info :o!");
}
return response.json();
})
.then((responseJson) => {
setTriggerAuthentication(responseJson);
clearInterval(id);
newwin.close();
setOutlookFolders();
})
.catch((error) => {
console.log("Set outlook trigger error: ", error.toString());
});
}, 2500);
saveWorkflow(workflow);
}}
>
Sign in with Microsoft
);
// FIXME - set everything in here to multifolder etc
var triggerInfo = "SET UP BUT NO TYPE :)";
if (Object.getOwnPropertyNames(triggerAuthentication).length > 0) {
// Should get the folders if they don't already exist
if (
triggerAuthentication.type === "outlook" ||
triggerAuthentication.type === "gmail"
) {
triggerInfo = (
{selectedTrigger.status === "running" ? null : (
{outlookButton}
{gmailButton}
If you have trouble using this trigger, please contact us to get access
)}
{triggerFolders === undefined || triggerFolders === null ? null : (
Select {triggerAuthentication.type === "gmail" ? "labels" : "a folder"}
{triggerFolders.length === 0 ?
No folders found. Please authenticate and make sure the user has access folders available. If this persists, contact us .
:
{
setTriggerFolderWrapperMulti(e)
}}
fullWidth
input={ }
key={selectedTrigger}
>
{triggerFolders.map((folder) => {
var folderItem = (
{folder.displayName}
);
if (folder.childFolderCount > 0) {
// Here to handle subfolders sometime later
folderItem = (
{folder.displayName}
);
}
return folderItem;
})}
}
)}
);
} else if (triggerAuthentication.type === "gmail") {
console.log("AUTH: ", triggerAuthentication);
triggerInfo = "SPECIAL GMAIL";
}
}
// Check
const argumentView =
Object.getOwnPropertyNames(triggerAuthentication).length > 0 ? (
{triggerInfo}
) : (
{outlookButton}
{gmailButton}
If you have trouble using this trigger, please {
if (window.drift !== undefined) {
window.drift.api.startInteraction({ interactionId: 340043 })
} else {
console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift)
}
}}>contact us to get access
);
return (
Name
Environment:
{argumentView}
{
startMailSub(selectedTrigger, selectedTriggerIndex);
}}
color="primary"
>
Start
{
stopMailSub(selectedTrigger, selectedTriggerIndex);
}}
color="primary"
>
Stop
);
};
const handleWorkflowSelectionUpdate = (e, isUserinput) => {
setUpdate(Math.random());
if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) {
console.log("Returning as there's no id")
return null
}
const paramIndex = isUserinput === true ? 5 : 0
workflow.triggers[selectedTriggerIndex].parameters[paramIndex].value = e.target.value.id;
setSubworkflow(e.target.value);
// Sets the startnode
if (e.target.value.id !== workflow.id) {
console.log("WORKFLOW: ", e.target.value);
const startnode = e.target.value.actions.find((action) => action.id === e.target.value.start);
if (startnode !== undefined && startnode !== null) {
setSubworkflowStartnode(startnode);
if (paramIndex === 0) {
try {
workflow.triggers[selectedTriggerIndex].parameters[3].value = startnode.id;
} catch {
workflow.triggers[selectedTriggerIndex].parameters[3] = {
name: "startnode",
value: startnode.id,
};
}
}
//setWorkflow(workflow);
}
console.log("STARTNODE: ", startnode);
} else {
console.log("WORKFLOW: ", workflow);
}
setWorkflow(workflow);
}
const SubflowSidebar = () => {
const [menuPosition, setMenuPosition] = useState(null);
const [showDropdown, setShowDropdown] = React.useState(false);
const [actionlist, setActionlist] = React.useState([]);
if (actionlist.length === 0) {
// FIXME: Have previous execution values in here
actionlist.push({
type: "Execution Argument",
name: "Execution Argument",
value: "$exec",
highlight: "exec",
autocomplete: "exec",
example: "hello",
})
actionlist.push({
type: "Shuffle Database",
name: "Shuffle Database",
value: "$shuffle_cache",
highlight: "shuffle_db",
autocomplete: "shuffle_cache",
example: "hello",
})
if (
workflow.workflow_variables !== null &&
workflow.workflow_variables !== undefined &&
workflow.workflow_variables.length > 0
) {
for (let varkey in workflow.workflow_variables) {
const item = workflow.workflow_variables[varkey];
actionlist.push({
type: "workflow_variable",
name: item.name,
value: item.value,
id: item.id,
autocomplete: `${item.name.split(" ").join("_")}`,
example: item.value,
});
}
}
// FIXME: Add values from previous executions if they exist
if (
workflow.execution_variables !== null &&
workflow.execution_variables !== undefined &&
workflow.execution_variables.length > 0
) {
for (let varkey in workflow.execution_variables) {
const item = workflow.execution_variables[varkey];
actionlist.push({
type: "execution_variable",
name: item.name,
value: item.value,
id: item.id,
autocomplete: `${item.name.split(" ").join("_")}`,
example: "",
});
}
}
var parents = getParents(selectedTrigger);
if (parents.length > 1) {
for (let parentkey in parents) {
const item = parents[parentkey];
if (item.label === "Execution Argument") {
continue;
}
var exampledata = item.example === undefined ? "" : item.example;
// Find previous execution and their variables
if (workflowExecutions.length > 0) {
// Look for the ID
for (let execkey in workflowExecutions) {
if (
workflowExecutions[execkey].results === undefined ||
workflowExecutions[execkey].results === null
) {
continue;
}
var foundResult = workflowExecutions[execkey].results.find(
(result) => result.action.id === item.id
);
if (foundResult === undefined) {
continue;
}
const validated = validateJson(foundResult.result)
if (validated.valid) {
exampledata = validateJson.result
break
}
}
}
// 1. Take
const actionvalue = {
type: "action",
id: item.id,
name: item.label,
autocomplete: `${item.label.split(" ").join("_")}`,
example: exampledata,
}
actionlist.push(actionvalue);
}
}
setActionlist(actionlist);
}
// Shows nested list of nodes > their JSON lists
const ActionlistWrapper = (props) => {
const { data } = props;
const handleMenuClose = () => {
setUpdate(Math.random());
setMenuPosition(null);
};
const handleItemClick = (values) => {
console.log("VALUES: ", values)
if (values === undefined || values === null || values.length === 0) {
return;
}
/*
workflow.triggers[selectedTriggerIndex].parameters[1].value
.trim()
.endsWith("$")
? values[0].autocomplete
: "$" + values[0].autocomplete;
for (var key in values) {
if (key === 0 || values[key].autocomplete.length === 0) {
continue;
}
toComplete += values[key].autocomplete
}
*/
console.log("SELECTED TRIGGER: ", selectedTrigger)
if (selectedTrigger.name === "Shuffle Workflow") {
const toComplete = selectedTrigger.parameters[1].value + "$" + values[0].autocomplete
selectedTrigger.parameters[1].value = toComplete
setSelectedTrigger(selectedTrigger)
}
setUpdate(Math.random());
setShowDropdown(false);
setMenuPosition(null);
};
const iconStyle = {
marginRight: 15,
};
return (
{
handleMenuClose();
}}
open={!!menuPosition}
style={{
border: `2px solid #f85a3e`,
color: "white",
marginTop: 2,
}}
>
{actionlist.map((innerdata) => {
const icon =
innerdata.type === "action" ? (
) : innerdata.type === "workflow_variable" ||
innerdata.type === "execution_variable" ? (
) : (
);
const handleExecArgumentHover = (inside) => {
var exec_text_field = document.getElementById(
"execution_argument_input_field"
);
if (exec_text_field !== null) {
if (inside) {
exec_text_field.style.border = "2px solid #f85a3e";
} else {
exec_text_field.style.border = "";
}
}
// Also doing arguments
if (
workflow.triggers !== undefined &&
workflow.triggers !== null &&
workflow.triggers.length > 0
) {
for (let triggerkey in workflow.triggers) {
const item = workflow.triggers[triggerkey];
if (cy !== undefined) {
var node = cy.getElementById(item.id);
if (node.length > 0) {
if (inside) {
node.addClass("shuffle-hover-highlight");
} else {
node.removeClass("shuffle-hover-highlight");
}
}
}
}
}
}
const handleActionHover = (inside, actionId) => {
if (cy !== undefined) {
var node = cy.getElementById(actionId);
if (node.length > 0) {
if (inside) {
node.addClass("shuffle-hover-highlight");
} else {
node.removeClass("shuffle-hover-highlight");
}
}
}
};
const handleMouseover = () => {
if (innerdata.type === "Execution Argument") {
handleExecArgumentHover(true);
} else if (innerdata.type === "action") {
handleActionHover(true, innerdata.id);
}
};
const handleMouseOut = () => {
if (innerdata.type === "Execution Argument") {
handleExecArgumentHover(false);
} else if (innerdata.type === "action") {
handleActionHover(false, innerdata.id);
}
};
var parsedPaths = [];
console.log("Found example data: ", innerdata.example)
if (typeof innerdata.example === "object") {
parsedPaths = GetParsedPaths(innerdata.example, "");
}
const coverColor = "#82ccc3"
return parsedPaths.length > 0 ? (
{/*
{icon} {innerdata.name}
}
parentMenuOpen={!!menuPosition}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
minWidth: 250,
}}
onClick={() => {
handleItemClick([innerdata]);
}}
>
{parsedPaths.map((pathdata, index) => {
// FIXME: Should be recursive in here
const icon =
pathdata.type === "value" ? (
) : pathdata.type === "list" ? (
) : (
)
return (
{ }}
onClick={() => {
handleItemClick([innerdata, pathdata]);
}}
>
{icon} {pathdata.name}
);
})}
*/}
{icon} {innerdata.name}
}
parentMenuOpen={!!menuPosition}
style={{
color: "white",
minWidth: 250,
maxWidth: 250,
maxHeight: 50,
overflow: "hidden",
}}
onClick={() => {
console.log("CLICKED: ", innerdata);
console.log(innerdata.example)
handleItemClick([innerdata]);
}}
>
{
//console.log("HOVER: ", pathdata);
}}
onClick={() => {
handleItemClick([innerdata]);
}}
>
{innerdata.name}
{parsedPaths.map((pathdata, index) => {
// FIXME: Should be recursive in here
//
const icon =
pathdata.type === "value" ? (
) : pathdata.type === "list" ? (
) : (
);
//
const indentation_count = (pathdata.name.match(/\./g) || []).length+1
const baseIndent =
//const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0
const boxPadding = 0
const namesplit = pathdata.name.split(".")
const newname = namesplit[namesplit.length-1]
return (
{
//console.log("HOVER: ", pathdata);
}}
onClick={() => {
handleItemClick([innerdata, pathdata]);
}}
>
{Array(indentation_count).fill().map((subdata, subindex) => {
return (
baseIndent
)
})}
{icon} {newname}
{pathdata.type === "list" ? {
}} /> : null}
);
})}
) : (
handleMouseover()}
onMouseOut={() => {
handleMouseOut();
}}
onClick={() => {
handleItemClick([innerdata]);
}}
>
{icon} {innerdata.name}
);
})}
);
};
if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
if (workflow.triggers[selectedTriggerIndex] === undefined) {
return null;
}
if (
workflow.triggers[selectedTriggerIndex].parameters === undefined ||
workflow.triggers[selectedTriggerIndex].parameters === null ||
workflow.triggers[selectedTriggerIndex].parameters.length === 0
) {
workflow.triggers[selectedTriggerIndex].parameters = [];
workflow.triggers[selectedTriggerIndex].parameters[0] = {
name: "workflow",
value: "",
};
workflow.triggers[selectedTriggerIndex].parameters[1] = {
name: "argument",
value: "",
};
workflow.triggers[selectedTriggerIndex].parameters[2] = {
name: "user_apikey",
value: "",
};
workflow.triggers[selectedTriggerIndex].parameters[3] = {
name: "startnode",
value: "",
};
workflow.triggers[selectedTriggerIndex].parameters[4] = {
name: "check_result",
value: "false",
};
/*
// API-key has been replaced by auth key for the execution.
// Parents can now automatically execute children without auth from a user, as long as the subflow in question is owned by the same org and the subflow is actually referencing it during checkin.
console.log("SETTINGS: ", userSettings);
if (
userSettings !== undefined &&
userSettings !== null &&
userSettings.apikey !== null &&
userSettings.apikey !== undefined &&
userSettings.apikey.length > 0
) {
workflow.triggers[selectedTriggerIndex].parameters[2] = {
name: "user_apikey",
value: userSettings.apikey,
};
}
*/
}
const handleSubflowStartnodeSelection = (e) => {
setSubworkflowStartnode(e.target.value);
if (e.target.value === null || e.target.value === undefined) {
return
}
const branchId = uuidv4();
const newbranch = {
source_id: workflow.triggers[selectedTriggerIndex].id,
destination_id: e.target.value.id,
source: workflow.triggers[selectedTriggerIndex].id,
target: e.target.value.id,
has_errors: false,
id: branchId,
_id: branchId,
label: "Subflow",
decorator: true,
};
if (workflow.visual_branches !== undefined) {
if (workflow.visual_branches === null) {
workflow.visual_branches = [newbranch];
} else if (workflow.visual_branches.length === 0) {
workflow.visual_branches.push(newbranch);
} else {
const foundIndex = workflow.visual_branches.findIndex(
(branch) => branch.source_id === newbranch.source_id
);
if (foundIndex !== -1) {
const currentEdge = cy.getElementById(
workflow.visual_branches[foundIndex].id
);
if (
currentEdge !== undefined &&
currentEdge !== null
) {
currentEdge.remove();
}
}
workflow.visual_branches.splice(foundIndex, 1);
workflow.visual_branches.push(newbranch);
}
}
if (workflow.id === subworkflow.id) {
const cybranch = {
group: "edges",
source: newbranch.source_id,
target: newbranch.destination_id,
id: branchId,
data: newbranch,
};
cy.add(cybranch);
}
console.log("Value to be set: ", e.target.value);
try {
workflow.triggers[
selectedTriggerIndex
].parameters[3].value = e.target.value.id;
} catch {
workflow.triggers[selectedTriggerIndex].parameters[3] =
{
name: "startnode",
value: e.target.value.id,
};
}
setWorkflow(workflow);
}
return (
Name
Delay
{
if (isNaN(event.target.value)) {
console.log("NAN: ", event.target.value)
return
}
const parsedNumber = parseInt(event.target.value)
if (parsedNumber > 86400) {
console.log("Max number is 1 day (86400)")
return
}
selectedTrigger.execution_delay = parseInt(event.target.value)
setSelectedTrigger(selectedTrigger)
}}
/>
{
const newvalue =
workflow.triggers[selectedTriggerIndex].parameters[4] ===
undefined ||
workflow.triggers[selectedTriggerIndex].parameters[4]
.value === "false"
? "true"
: "false";
workflow.triggers[selectedTriggerIndex].parameters[4] = {
name: "check_result",
value: newvalue,
};
setWorkflow(workflow);
setUpdate(Math.random());
}}
color="primary"
value="Wait for results"
/>
}
style={{ marginTop: 10 }}
label={Wait for results
}
/>
Select a workflow to execute
{workflow.triggers[selectedTriggerIndex].parameters[0].value
.length === 0 ? null : workflow.triggers[selectedTriggerIndex]
.parameters[0].value === props.match.params.key ? null : (
)}
{workflows === undefined ||
workflows === null ||
workflows.length === 0 ? null : (
option.id === value.id}
getOptionLabel={(option) => {
if (
option === undefined ||
option === null ||
option.name === undefined ||
option.name === null
) {
return "No Workflow Selected";
}
const newname = (
option.name.charAt(0).toUpperCase() + option.name.substring(1)
).replaceAll("_", " ");
return newname;
}}
options={workflows}
fullWidth
style={{
backgroundColor: theme.palette.inputColor,
height: 50,
borderRadius: theme.palette.borderRadius,
}}
onChange={(event, newValue) => {
console.log("Found value: ", newValue)
var parsedinput = { target: { value: newValue } }
// For variables
if (typeof newValue === 'string' && newValue.startsWith("$")) {
parsedinput = {
target: {
value: {
"name": newValue,
"id": newValue,
"actions": [],
"triggers": [],
}
}
}
}
handleWorkflowSelectionUpdate(parsedinput)
}}
renderOption={(data, index) => {
if (data.id === workflow.id) {
data = workflow;
}
//key={index}
return (
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
: null}
Choose {data.name}
} placement="bottom">
{data.name}
)
}}
renderInput={(params) => {
return (
);
}}
/>
)}
{subworkflow === undefined ||
subworkflow === null ||
subworkflow.id === undefined ||
subworkflow.actions === null ||
subworkflow.actions === undefined ||
subworkflow.actions.length === 0 ? null : (
option.id === value.id}
getOptionLabel={(option) => {
if (option === undefined || option === null || option.label === undefined || option.label === null) {
if (option.length === 36) {
}
return "TMP";
}
const newname = (
option.label.charAt(0).toUpperCase() + option.label.substring(1)
).replaceAll("_", " ");
return newname;
}}
options={subworkflow.actions}
fullWidth
style={{
backgroundColor: theme.palette.inputColor,
height: 50,
borderRadius: theme.palette.borderRadius,
}}
onChange={(event, newValue) => {
handleSubflowStartnodeSelection({ target: { value: newValue } })
}}
renderOption={(action) => {
const isParent = getParents(selectedTrigger).find(
(parent) => parent.id === action.id
)
return (
{
if (subworkflow.id === workflow.id) {
handleActionHover(true, action.id)
}
}}
onMouseOut={() => {
if (subworkflow.id === workflow.id) {
handleActionHover(false, action.id)
}
}}
disabled={isCloud && isParent}
style={{
backgroundColor: theme.palette.inputColor,
color: isParent ? "red" : "white",
}}
value={action}
>
{action.label}
);
}}
renderInput={(params) => {
return (
);
}}
/>
)}
{
setMenuPosition({
top: event.pageY + 10,
left: event.pageX + 10,
});
//setShowDropdownNumber(3)
setShowDropdown(true);
}}
/>
),
}}
rows="6"
multiline
fullWidth
color="primary"
placeholder="Some execution data"
defaultValue={
workflow.triggers[selectedTriggerIndex].parameters[1].value
}
onBlur={(e) => {
workflow.triggers[selectedTriggerIndex].parameters[1].value =
e.target.value;
setWorkflow(workflow);
}}
/>
{!showDropdown ? null :
}
{/*
{
workflow.triggers[selectedTriggerIndex].parameters[2].value =
e.target.value;
setWorkflow(workflow);
}}
/>
*/}
);
}
return null;
};
const CommentSidebar = () => {
if (Object.getOwnPropertyNames(selectedComment).length > 0) {
/*
if (workflow.triggers[selectedTriggerIndex] === undefined) {
return null
}
if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) {
workflow.triggers[selectedTriggerIndex].parameters = []
workflow.triggers[selectedTriggerIndex].parameters[0] = {"name": "url", "value": referenceUrl+"webhook_"+selectedTrigger.id}
workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "tmp", "value": "webhook_"+selectedTrigger.id}
workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "auth_headers", "value": ""}
setWorkflow(workflow)
} else {
if (selectedTrigger.environment !== "cloud") {
const newUrl = referenceUrl+"webhook_"+selectedTrigger.id
if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) {
console.log("Url is wrong - should update. This functionality is temporarily disabled.")
//workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl
//setWorkflow(workflow)
}
}
}
const trigger_header_auth = workflow.triggers[selectedTriggerIndex].parameters.length > 2 ? workflow.triggers[selectedTriggerIndex].parameters[2].value : ""
*/
return (
Name
{
selectedComment.label = event.target.value;
setSelectedComment(selectedComment);
}}
/>
Height
{
selectedComment.height = event.target.value;
setSelectedComment(selectedComment);
}}
/>
Width
{
selectedComment.width = event.target.value;
setSelectedComment(selectedComment);
}}
/>
Background
{
selectedComment.backgroundcolor = event.target.value;
setSelectedComment(selectedComment);
}}
/>
Text Color
{
selectedComment.color = event.target.value;
setSelectedComment(selectedComment);
}}
/>
Background-Image
{
selectedComment.backgroundimage = event.target.value;
console.log("Comment: ", selectedComment)
setSelectedComment(selectedComment);
}}
/>
);
}
return null;
};
const WebhookSidebar = () => {
if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
if (workflow.triggers[selectedTriggerIndex] === undefined) {
return null;
}
if (
workflow.triggers[selectedTriggerIndex].parameters === undefined ||
workflow.triggers[selectedTriggerIndex].parameters === null ||
workflow.triggers[selectedTriggerIndex].parameters.length === 0
) {
workflow.triggers[selectedTriggerIndex].parameters = [];
workflow.triggers[selectedTriggerIndex].parameters[0] = {
name: "url",
value: referenceUrl + "webhook_" + selectedTrigger.id,
};
workflow.triggers[selectedTriggerIndex].parameters[1] = {
name: "tmp",
value: "webhook_" + selectedTrigger.id,
};
workflow.triggers[selectedTriggerIndex].parameters[2] = {
name: "auth_headers",
value: "",
};
workflow.triggers[selectedTriggerIndex].parameters[3] = {
name: "custom_response_body",
value: "",
};
workflow.triggers[selectedTriggerIndex].parameters[4] = {
name: "await_response",
value: "v1",
};
setWorkflow(workflow);
} else {
// Always update
const newUrl = referenceUrl + "webhook_" + selectedTrigger.id;
//console.log("Validating webhook url: ", newUrl);
if (selectedTrigger.environment !== "cloud") {
if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) {
console.log("Url is wrong. NOT updating because of hybrid.");
//workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl;
//setWorkflow(workflow);
}
}
}
const trigger_header_auth =
workflow.triggers[selectedTriggerIndex].parameters.length > 2
? workflow.triggers[selectedTriggerIndex].parameters[2].value
: "";
return (
Name
{apps !== undefined && apps !== null && apps.length > 0 ?
{
//console.log("Option contains?: ", inputValue, options)
const lowercaseValue = inputValue.toLowerCase()
options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue))
return options
}}
getOptionLabel={(option) => {
if (
option === undefined ||
option === null ||
option.name === undefined ||
option.name === null
) {
return null;
}
const newname = (
option.name.charAt(0).toUpperCase() + option.name.substring(1)
).replaceAll("_", " ");
return newname;
}}
options={sortByKey(apps, "name")}
fullWidth
style={{
backgroundColor: theme.palette.inputColor,
height: 50,
borderRadius: theme.palette.borderRadius,
}}
onChange={(event, newValue) => {
// Workaround with event lol
console.log(event, newValue)
if (newValue !== undefined && newValue !== null) {
var parsedvalue = JSON.parse(JSON.stringify(newValue))
parsedvalue.actions = []
parsedvalue.authentication = {}
selectedTrigger.app_association = parsedvalue
setUpdate(Math.random());
}
// setNewSelectedAction({
// target: {
// value: newValue.name
// }
// });
//}
}}
renderOption={(app) => {
var appname = app.name.replaceAll("_", " ")
appname = appname.charAt(0).toUpperCase() + appname.substring(1)
return (
{appname}
)
}}
renderInput={(params) => {
return (
);
}}
/>
: null}
{selectedTrigger.status === "running" ? null :
Environment
{
selectedTrigger.environment = e.target.value;
if (e.target.value === "cloud") {
const tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/");
const urlpath = tmpvalue.slice(3, tmpvalue.length);
const newurl = "https://shuffler.io/" + urlpath.join("/");
workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl;
} else {
const tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/");
const urlpath = tmpvalue.slice(3, tmpvalue.length);
const newurl = window.location.origin + "/" + urlpath.join("/");
workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl;
}
console.log("New value: ", workflow.triggers[selectedTriggerIndex].parameters[0])
selectedTrigger.parameters[0] = workflow.triggers[selectedTriggerIndex].parameters[0]
setSelectedTrigger(selectedTrigger);
setWorkflow(workflow);
setUpdate(Math.random());
}}
style={{
backgroundColor: inputColor,
color: "white",
height: 50,
}}
>
{triggerEnvironments.map((data) => {
if (data.archived) {
return null;
}
return (
{data}
);
})}
}
Parameters
{
}}
helperText={
workflow.triggers[selectedTriggerIndex].parameters[0].value !== undefined &&
workflow.triggers[selectedTriggerIndex].parameters[0].value !== null &&
(workflow.triggers[
selectedTriggerIndex
].parameters[0].value.includes("localhost") ||
workflow.triggers[
selectedTriggerIndex
].parameters[0].value.includes("127.0.0.1")) ? (
PS: This does NOT work with localhost. Use your local IP
instead.
) : null
}
InputProps={{
style: {
color: "white",
height: 50,
marginLeft: "5px",
maxWidth: "95%",
fontSize: "1em",
},
endAdornment:
{
var copyText = document.getElementById("webhook_uri_field");
if (copyText !== undefined && copyText !== null) {
console.log("NAVIGATOR: ", navigator);
const clipboard = navigator.clipboard;
if (clipboard === undefined) {
alert.error("Can only copy over HTTPS (port 3443)");
return;
}
navigator.clipboard.writeText(copyText.value);
copyText.select();
copyText.setSelectionRange(
0,
99999
); /* For mobile devices */
/* Copy the text inside the text field */
document.execCommand("copy");
alert.success("Copied Webhook URL");
} else {
console.log("Couldn't find webhook URI field: ", copyText);
}
}}
edge="end"
>
}}
fullWidth
disabled
value={
workflow.triggers[selectedTriggerIndex].parameters[0].value
}
color="primary"
placeholder="defaultValue"
onBlur={(e) => {
setTriggerCronWrapper(e.target.value);
}}
/>
{
newWebhook(workflow.triggers[selectedTriggerIndex]);
}}
color="primary"
>
Start
{
deleteWebhook(selectedTrigger, selectedTriggerIndex);
}}
color="primary"
>
Stop
{isCloud && workflow.triggers[selectedTriggerIndex].parameters.length > 4 ?
{
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = [];
}
// Sets the webhook to run as version 2.. kinda
var value = "v2"
if (workflow.triggers[selectedTriggerIndex].parameters[4].value.includes("v2")) {
value = "v1"
}
workflow.triggers[selectedTriggerIndex].parameters[4] = {
name: "await_response",
value: value
}
setWorkflow(workflow)
setUpdate(Math.random())
}}
color="primary"
value="await_response"
/>
}
label={Wait For Response
}
/>
: null}
);
}
return null;
};
const stopMailSub = (trigger, triggerindex) => {
// DELETE
if (trigger.id === undefined) {
return;
}
alert.info("Stopping mail trigger");
const requesttype = triggerAuthentication.type;
fetch(
`${globalUrl}/api/v1/workflows/${props.match.params.key}/${requesttype}/${trigger.id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
throw new Error("Status not 200 for stream results :O!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success) {
alert.success("Successfully stopped trigger");
// Set the status
workflow.triggers[triggerindex].status = "stopped";
trigger.status = "stopped";
setWorkflow(workflow);
setSelectedTrigger(trigger);
saveWorkflow(workflow);
} else {
alert.error("Failed stopping trigger: " + responseJson.reason);
}
})
.catch((error) => {
//alert.error(error.toString());
console.log("Stop mailsub error: ", error.toString());
});
};
const startMailSub = (trigger, triggerindex) => {
var folders = [];
if (triggerFolders === null || triggerFolders === undefined) {
return null;
}
const splitItem =
workflow.triggers[selectedTriggerIndex].parameters[0].value.split(
splitter
)
console.log("Starting mail sub: ", workflow.triggers[selectedTriggerIndex].parameters[0].value, splitItem);
for (let splitkey in splitItem) {
const item = splitItem[splitkey];
const curfolder = triggerFolders.find((a) => a.displayName === item);
if (curfolder === undefined) {
alert.error("Something went wrong with folder selection: " + item);
return;
}
folders.push(curfolder.id);
}
const data = {
name: trigger.name,
folders: folders,
id: trigger.id,
};
const requesttype = triggerAuthentication.type;
alert.info(
"Creating " + requesttype + " subscription with name " + trigger.name
);
fetch(
globalUrl +
"/api/v1/workflows/" +
props.match.params.key +
"/" +
requesttype,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(data),
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for stream results :O!");
}
return response.json();
})
.then((responseJson) => {
if (!responseJson.success) {
alert.error("Failed to start trigger: " + responseJson.reason);
} else {
alert.success(
"Successfully started folder subscription trigger. Test it by sending yoursend an email"
);
workflow.triggers[triggerindex].status = "running";
trigger.status = "running";
setWorkflow(workflow);
setSelectedTrigger(trigger);
saveWorkflow(workflow);
}
})
.catch((error) => {
//alert.error(error.toString());
console.log("Start mailsub error: ", error.toString());
});
};
const newWebhook = (trigger) => {
const hookname = trigger.label;
if (hookname.length === 0) {
alert.error("Missing name");
return;
}
if (trigger.id.length !== 36) {
alert.error("Missing id");
return;
}
// Check the node it's connected to
var startNode = workflow.start;
const branch = workflow.branches.find(
(branch) => branch.source_id === trigger.id
);
if (branch === undefined && (workflow.start === undefined || workflow.start === null || workflow.start.length === 0)) {
alert.error("No webhook node defined");
}
alert.info("Starting webhook");
if (branch !== undefined) {
startNode = branch.destination_id;
}
const param = trigger.parameters.find((param) => param.name === "auth_headers");
var auth = "";
if (param !== undefined && param !== null) {
auth = param.value;
}
// Version: v2 = await response for 30 sec
const await_resp = trigger.parameters.find((param) => param.name === "await_response");
var version = "";
if (await_resp !== undefined && await_resp !== null) {
version = await_resp.value;
}
const customRespParam = trigger.parameters.find(
(param) => param.name === "custom_response_body"
)
var custom_response = "";
if (customRespParam !== undefined && customRespParam !== null) {
custom_response = customRespParam.value;
}
const data = {
name: hookname,
type: "webhook",
id: trigger.id,
workflow: workflow.id,
start: startNode,
environment: trigger.environment,
auth: auth,
custom_response: custom_response,
version: version,
version_timeout: 15,
};
console.log("Trigger data: ", data)
fetch(globalUrl + "/api/v1/hooks/new", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(data),
credentials: "include",
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson.success) {
// Set the status
alert.success("Successfully started webhook");
trigger.status = "running";
setSelectedTrigger(trigger);
workflow.triggers[selectedTriggerIndex].status = "running";
setWorkflow(workflow);
saveWorkflow(workflow);
} else {
alert.error("Failed starting webhook: " + responseJson.reason);
}
})
.catch((error) => {
//console.log(error.toString());
console.log("New webhook error: ", error.toString());
});
};
const deleteWebhook = (trigger, triggerindex) => {
if (trigger.id === undefined) {
return;
}
fetch(globalUrl + "/api/v1/hooks/" + trigger.id + "/delete", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for stream results :O!");
}
return response.json();
})
.then((responseJson) => {
if (workflow.triggers[triggerindex] !== undefined) {
workflow.triggers[triggerindex].status = "stopped";
}
if (responseJson.success) {
// Set the status
saveWorkflow(workflow);
} else {
if (responseJson.reason !== undefined) {
alert.error("Failed stopping webhook: " + responseJson.reason);
}
}
trigger.status = "stopped";
setWorkflow(workflow);
setSelectedTrigger(trigger);
})
.catch((error) => {
//alert.error(error.toString());
alert.error("Delete webhook error. Contact support or check logs if this persists.")
});
};
const UserinputSidebar = () => {
if (
Object.getOwnPropertyNames(selectedTrigger).length > 0 &&
workflow.triggers[selectedTriggerIndex] !== undefined
) {
if (
workflow.triggers[selectedTriggerIndex].parameters === undefined ||
workflow.triggers[selectedTriggerIndex].parameters === null ||
workflow.triggers[selectedTriggerIndex].parameters.length === 0
) {
workflow.triggers[selectedTriggerIndex].parameters = [];
workflow.triggers[selectedTriggerIndex].parameters[0] = {
name: "alertinfo",
value: "Do you want to continue the workflow? Start parameters: $exec",
};
// boolean,
workflow.triggers[selectedTriggerIndex].parameters[1] = {
name: "options",
value: "boolean",
};
// email,sms,app ...
workflow.triggers[selectedTriggerIndex].parameters[2] = {
name: "type",
value: "email",
};
workflow.triggers[selectedTriggerIndex].parameters[3] = {
name: "email",
value: "test@test.com",
};
workflow.triggers[selectedTriggerIndex].parameters[4] = {
name: "sms",
value: "0000000",
};
workflow.triggers[selectedTriggerIndex].parameters[5] = {
name: "subflow",
value: "",
};
setWorkflow(workflow);
}
return (
Name
Environment:
Parameters
{
setTriggerTextInformationWrapper(e.target.value);
}}
/>
{
setTriggerOptionsWrapper("email");
}}
color="primary"
value="email"
/>
}
label={Email
}
/>
{
setTriggerOptionsWrapper("sms");
}}
color="primary"
value="sms"
disabled={true}
/>
}
label={SMS
}
/>
{
setTriggerOptionsWrapper("subflow");
}}
color="primary"
value="subflow"
/>
}
label={Subflow
}
/>
{workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow") ? (
{workflows === undefined ||
workflows === null ||
workflows.length === 0 ? null : (
option.id === value.id}
getOptionLabel={(option) => {
if (option === undefined || option === null || option.name === undefined || option.name === null) {
return "No Workflow Selected";
}
const newname = (option.name.charAt(0).toUpperCase() + option.name.substring(1)).replaceAll("_", " ");
return newname;
}}
options={workflows}
fullWidth
style={{
backgroundColor: theme.palette.inputColor,
height: 50,
borderRadius: theme.palette.borderRadius,
marginTop: 15,
marginBottom: 15,
}}
onChange={(event, newValue) => {
console.log("Changed autocomplete!")
handleWorkflowSelectionUpdate({ target: { value: newValue } }, true)
}}
renderOption={(data, index) => {
if (data.id === workflow.id) {
data = workflow;
}
//key={index}
return (
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
: null}
Choose {data.name}
} placement="bottom">
{data.name}
)
}}
renderInput={(params) => {
return (
);
}}
/>
)}
) : null}
{workflow.triggers[selectedTriggerIndex].parameters[2] !==
undefined &&
workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("email") ? (
{
workflow.triggers[selectedTriggerIndex].parameters[3].value =
event.target.value;
setWorkflow(workflow);
setUpdate(Math.random());
}}
/>
) : null}
{workflow.triggers[selectedTriggerIndex].parameters[2] !==
undefined &&
workflow.triggers[
selectedTriggerIndex
].parameters[2].value.includes("sms") ? (
{
workflow.triggers[selectedTriggerIndex].parameters[4].value =
event.target.value;
setWorkflow(workflow);
setUpdate(Math.random());
}}
/>
) : null}
);
}
return null;
};
const ScheduleSidebar = () => {
if (
Object.getOwnPropertyNames(selectedTrigger).length > 0 &&
workflow.triggers[selectedTriggerIndex] !== undefined
) {
if (
workflow.triggers[selectedTriggerIndex].parameters === undefined ||
workflow.triggers[selectedTriggerIndex].parameters === null ||
workflow.triggers[selectedTriggerIndex].parameters.length === 0
) {
workflow.triggers[selectedTriggerIndex].parameters = [];
workflow.triggers[selectedTriggerIndex].parameters[0] = {
name: "cron",
value: isCloud ? "*/25 * * * *" : "60",
};
workflow.triggers[selectedTriggerIndex].parameters[1] = {
name: "execution_argument",
value: '{"example": {"json": "is cool"}}',
};
setWorkflow(workflow);
}
return (
Name
Environment
{
selectedTrigger.environment = e.target.value;
setSelectedTrigger(selectedTrigger);
if (e.target.value === "cloud") {
console.log("Set cloud config");
workflow.triggers[selectedTriggerIndex].parameters[0].value =
"*/25 * * * *";
} else {
console.log("Set cloud config");
workflow.triggers[selectedTriggerIndex].parameters[0].value =
"60";
}
setWorkflow(workflow);
setUpdate(Math.random());
}}
style={{
backgroundColor: inputColor,
color: "white",
height: 50,
}}
>
{triggerEnvironments.map((data) => {
if (data.archived) {
return null;
}
return (
{data}
);
})}
Parameters
{
setTriggerCronWrapper(e.target.value);
}}
/>
{/*selectedTrigger.environment === "cloud" ?
:
null
*/}
{
setTriggerBodyWrapper(e.target.value);
}}
/>
{
submitSchedule(selectedTrigger, selectedTriggerIndex);
}}
color="primary"
>
Start
{
stopSchedule(selectedTrigger, selectedTriggerIndex);
}}
color="primary"
>
Stop
);
}
return null;
};
const cytoscapeViewWidths = isMobile ? 50 : 850;
const bottomBarStyle = {
position: "fixed",
right: isMobile ? 20 : 20,
bottom: isMobile ? undefined : 0,
top: isMobile ? appBarSize + 55 : undefined,
left: isMobile ? undefined : leftBarSize,
minWidth: cytoscapeViewWidths,
maxWidth: cytoscapeViewWidths,
marginLeft: 20,
marginBottom: 20,
zIndex: 10,
};
const topBarStyle = {
position: "fixed",
right: 0,
left: isMobile ? 20 : leftBarSize + 20,
top: isMobile ? 30 : appBarSize + 20,
};
const TopCytoscapeBar = (props) => {
if (workflow.public === true) {
return null
}
return (
Workflows
{workflow.name}
);
};
const WorkflowMenu = () => {
const [newAnchor, setNewAnchor] = React.useState(null);
const [showShuffleMenu, setShowShuffleMenu] = React.useState(false);
return (
}
control={
{
workflow.configuration.exit_on_error =
!workflow.configuration.exit_on_error;
setWorkflow(workflow);
setUpdate(
"exit_on_error_" + workflow.configuration.exit_on_error
? "true"
: "false"
);
}}
/>
}
/>
Start from top}
control={
{
workflow.configuration.start_from_top =
!workflow.configuration.start_from_top;
setWorkflow(workflow);
setUpdate(
"start_from_top_" + workflow.configuration.start_from_top
? "true"
: "false"
);
}}
/>
}
/>
{/*
{
setShowShuffleMenu(!showShuffleMenu);
setNewAnchor(event.currentTarget);
}}
>
*/}
{isMobile ?
{
console.log("Show apps!")
setLeftBarSize(leftViewOpen ? 0 : 60)
setLeftViewOpen(!leftViewOpen)
}}
>
: null}
);
};
const handleActionHover = (inside, actionId) => {
if (cy !== undefined) {
var node = cy.getElementById(actionId);
if (node.length > 0) {
if (inside) {
node.addClass("shuffle-hover-highlight");
} else {
node.removeClass("shuffle-hover-highlight");
}
}
}
}
const handleHistoryUndo = () => {
//console.log("history: ", history, "index: ", historyIndex);
var item = history[historyIndex - 1];
if (historyIndex === 0) {
item = history[historyIndex];
}
if (item === undefined) {
console.log("Couldn't find the action you're looking for");
return;
}
//console.log("HANDLE: ", item);
if (item.type === "node" && item.action === "removed") {
// Re-add the node
console.log("Item: ", item.data)
const edge = cy.getElementById(item.data.id).json()
if (edge !== null && edge !== undefined) {
console.log("Couldn't add node as it exists")
return
}
cy.add({
group: "nodes",
data: item.data,
position: item.data.position,
});
} else if (item.action === "added") {
//console.log("Should remove item!");
const currentitem = cy.getElementById(item.data.id);
if (currentitem !== undefined && currentitem !== null) {
currentitem.remove();
}
} else if (item.type === "edge" && item.action === "removed") {
const sourcenode = cy.getElementById(item.data.source)
const targetnode = cy.getElementById(item.data.target)
if (sourcenode === undefined || sourcenode === null || targetnode === undefined || targetnode === null) {
console.log("Can't readd bad edge!")
return
}
const edge = cy.getElementById(item.data.id).json()
if (edge !== null && edge !== undefined) {
console.log("Couldn't add edge as it exists")
return
}
cy.add({
group: "edges",
data: item.data,
});
} else {
console.log("UNHANDLED: ", item)
}
if (historyIndex > 0) {
setHistoryIndex(historyIndex - 1);
}
};
const BottomCytoscapeBar = () => {
if (workflow.id === undefined || workflow.id === null || (!workflow.public && apps.length === 0)) {
return null;
}
const boxSize = isMobile ? 50 : 100;
const executionButton = executionRunning ? (
{
abortExecution();
}}
>
) : (
{
executeWorkflow(executionText, workflow.start, lastSaved);
}}
>
);
return (
{executionButton}
{isMobile || workflow.public ? null : (
{
setExecutionText(e.target.value);
}}
/>
)}
{/*userdata.avatar === creatorProfile.github_avatar ? null :*/}
{
saveWorkflow()
if (workflow.public === true) {
console.log("Public!")
const tmpurl = new URL(window.location.href)
const searchParams = tmpurl.searchParams
const queryID = searchParams.get('queryID')
if (queryID !== undefined && queryID !== null) {
aa('init', {
appId: "JNSS5CFDZZ",
apiKey: "db08e40265e2941b9a7d8f644b6e5240",
})
const timestamp = new Date().getTime()
aa('sendEvents', [
{
eventType: 'conversion',
eventName: 'Public Workflow Saved',
index: 'workflows',
objectIDs: [workflow.id],
timestamp: timestamp,
queryID: queryID,
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
}
])
} else {
console.log("No query to handle")
}
}
}}
>
{savingState === 2 ? (
) : savingState === 1 ? (
) : (
)}
{workflow.public ? (
{
const data = workflow;
let exportFileDefaultName = data.name + ".json";
let dataStr = JSON.stringify(data);
let dataUri =
"data:application/json;charset=utf-8," +
encodeURIComponent(dataStr);
let linkElement = document.createElement("a");
linkElement.setAttribute("href", dataUri);
linkElement.setAttribute("download", exportFileDefaultName);
linkElement.click();
}}
>
) : null}
cy.fit(null, 50)}
>
{
handleHistoryUndo(history);
}}
>
{
const selectedNode = cy.$(":selected");
if (selectedNode.data() === undefined) {
return
}
removeNode(selectedNode.data("id"))
}}
>
{
setExecutionModalOpen(true);
getWorkflowExecution(props.match.params.key, "");
}}
>
{/* */}
{/* */}
{
addCommentNode();
}}
>
{workflow.configuration !== null &&
workflow.configuration !== undefined &&
workflow.configuration.exit_on_error !== undefined ? (
) : null}
{
console.log("SHOW EDIT VIEW!")
setEditWorkflowModalOpen(true)
}}
>
);
};
const addCommentNode = () => {
const newId = uuidv4();
const position = {
x: 300,
y: 300,
};
cy.add({
group: "nodes",
data: {
id: newId,
label: "Click to write a comment",
type: "COMMENT",
is_valid: true,
decorator: true,
width: 250,
height: 150,
position: position,
backgroundcolor: "#1f2023",
color: "#ffffff",
},
position: position,
});
};
const RightSideBar = (props) => {
const {
workflow,
setWorkflow,
setSelectedAction,
setUpdate,
selectedApp,
workflowExecutions,
setSelectedResult,
selectedAction,
setSelectedApp,
setSelectedTrigger,
setSelectedEdge,
setCurrentView,
cy,
setAuthenticationModalOpen,
setVariablesModalOpen,
setCodeModalOpen,
selectedNameChange,
rightsidebarStyle,
showEnvironment,
selectedActionEnvironment,
environments,
setNewSelectedAction,
appApiViewStyle,
globalUrl,
setSelectedActionEnvironment,
requiresAuthentication,
scrollConfig,
setScrollConfig,
} = props;
if (!rightSideBarOpen) {
return null;
}
var defaultReturn = null
if (Object.getOwnPropertyNames(selectedAction).length > 0) {
if (Object.getOwnPropertyNames(selectedAction).length === 0) {
return null;
}
defaultReturn =
} else if (Object.getOwnPropertyNames(selectedComment).length > 0) {
defaultReturn =
} else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
if (selectedTrigger.trigger_type === "SCHEDULE") {
defaultReturn =
} else if (selectedTrigger.trigger_type === "WEBHOOK") {
defaultReturn =
} else if (selectedTrigger.trigger_type === "SUBFLOW") {
defaultReturn =
} else if (selectedTrigger.trigger_type === "EMAIL") {
defaultReturn =
} else if (selectedTrigger.trigger_type === "USERINPUT") {
defaultReturn =
} else if (selectedTrigger.trigger_type === undefined) {
//defaultReturn =
return null;
} else {
console.log(
"Unable to handle invalid trigger type " +
selectedTrigger.trigger_type
);
return null;
}
} else if (Object.getOwnPropertyNames(selectedEdge).length > 0) {
defaultReturn =
}
const iOS = typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent);
const drawerBleeding = 56;
if (defaultReturn === undefined || defaultReturn === null) {
return null
}
return (
isMobile ?
{
console.log("Close!")
//setRightSideBarOpen(false)
cy.elements().unselect()
}}
disableSwipeToOpen={false}
ModalProps={{
keepMounted: true,
}}
PaperProps={{
style: {
maxHeight: "70%",
overflow: "auto",
}
}}
>
{defaultReturn}
:
{defaultReturn}
);
//return null;
};
// This can execute a workflow with firestore. Used for test, as datastore is old and stuff
// Too much work to move everything over alone, so won't touch it for now
// {
// executeWorkflowWebsocket()
//}}>Execute websocket
//
// A list used for FRONTEND handling of whether a public workflow
// should be change-able
const allowList = ["frikky", "m1nk-code", "DavidtheGoliath"]
// console.log(allowList, userdata.public_username)
const leftView = workflow.public === true ?
{workflow.name}
This workflow is public and {
saveWorkflow()
}}>must be saved or exported before use.
{Object.getOwnPropertyNames(creatorProfile).length !== 0 && creatorProfile.github_avatar !== undefined && creatorProfile.github_avatar !== null ?
{
}}>
Shared by {creatorProfile.github_username}
: null}
{workflow.tags !== undefined && workflow.tags !== null && workflow.tags.length > 0 ?
Tags
{workflow.tags.map((tag, index) => {
if (index >= 3) {
return null;
}
return (
);
})}
: null}
{workflow.blogpost !== undefined && workflow.blogpost !== null && workflow.blogpost.length > 0 ?
: null
}
{appGroup.length > 0 ?
Apps
{appGroup.map((data, index) => {
return (
)
})}
: null}
{triggerGroup.length > 0 ?
Triggers
{triggerGroup.map((data, index) => {
return (
)
})}
: null}
{/*
Mitre Att&ck:
TBD
*/}
{/*
Related Workflows:
TBD
*/}
{workflow.video !== undefined && workflow.video !== null && workflow.video.length > 0 ?
Video
{
workflow.video.includes("loom.com/share") && workflow.video.split("/").length > 4 ?
:
workflow.video.includes("youtube.com") && workflow.video.split("/").length > 3 && workflow.video.includes("v=")
?
VIDEO
:
{workflow.video}
}
: null}
{workflow.description !== undefined && workflow.description !== null && workflow.description.length > 0 ?
Description
{workflow.description}
: null}
{/*
{
setSelectionOpen(true)
}}
>
Statistics
*/}
{userdata.avatar !== undefined && (userdata.avatar === creatorProfile.github_avatar || allowList.includes(userdata.public_username)) ?
You can see these buttons because you may have the correct access rights as a creator to help modify this workflow.
{
// Further checks are being done on the backend
// Even if the user can "edit" a workflow on the frontend,
// that doesn't necessarily mean anything
//setEditWorkflowDetails(true)
workflow.public = false
setUserediting(true)
setWorkflow(workflow)
getAppAuthentication();
getEnvironments();
getWorkflowExecution(props.match.params.key, "");
getAvailableWorkflows(-1);
getSettings();
getFiles()
setUpdate(Math.random());
}}
>
Edit Workflow
{
// setEditWorkflowDetails(true)
// workflow.public = false
// setWorkflow(workflow)
// setUpdate(Math.random());
}}
>
Unpublish Workflow
: null}
: isMobile && leftViewOpen ?
: leftViewOpen ? (
) : (
{
setLeftViewOpen(true);
setLeftBarSize(350);
}}
>
);
const executionPaperStyle = {
minWidth: "95%",
maxWidth: "95%",
marginTop: "5px",
color: "white",
marginBottom: 10,
padding: 5,
backgroundColor: surfaceColor,
cursor: "pointer",
display: "flex",
minHeight: 40,
maxHeight: 40,
};
const parsedExecutionArgument = () => {
var showResult = executionData.execution_argument.trim();
const validate = validateJson(showResult);
if (validate.valid) {
if (typeof validate.result === "string") {
try {
validate.result = JSON.parse(validate.result);
} catch (e) {
console.log("Error: ", e);
validate.valid = false;
}
}
return (
{
if (validate.valid) {
//console.log("Find and change result: ",
//const oldstartnode = cy.getElementById(selectedResult.action.id);
//if (oldstartnode !== undefined && oldstartnode !== null) {
// const foundname = oldstartnode.data("label")
// if (foundname !== undefined && foundname !== null) {
// result.action.label = foundname
// }
//}
}
setSelectedResult({
"action": {
"label": "Execution Argument",
"name": "Execution Argument",
"large_image": theme.palette.defaultImage,
"image": theme.palette.defaultImage,
},
"result": validate.valid ? JSON.stringify(validate.result) : validate.result,
"status": "SUCCESS"
})
setCodeModalOpen(true);
}}
>
{
handleReactJsonClipboard(copy);
}}
displayDataTypes={false}
onSelect={(select) => {
HandleJsonCopy(validate.result, select, "exec");
}}
name={"Execution Argument"}
/>
)
}
return (
Execution Argument
{executionData.execution_argument}
);
};
const getExecutionSourceImage = (execution) => {
// This is the playbutton at 150x150
const defaultImage =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACOCAMAAADkWgEmAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAWlBMVEX4Wj69TDgmKCvkVTwlJyskJiokJikkJSkjJSn4Ykf+6+f5h3L////8xLr5alH/9fT7nYz4Wz/919H5cVn/+vr8qpv4XUL94d35e2X//v38t6v4YUbkVDy8SzcVIzHLAAAAAWJLR0QMgbNRYwAAAAlwSFlzAAARsAAAEbAByCf1VAAAAAd0SU1FB+QGGgsvBZ/GkmwAAAFKSURBVHja7dlrTgMxDEXhFgpTiukL2vLc/zbZQH5N7MmReu4KPmlGN4m9WgGzfhgtaOZxM1rQztNoQDvPowHtTKMB7WxHA2TJkiVLlixIZMmSRYgsWbIIkSVLFiGyZMkiRNZirBcma/eKZEW87ZGsOBxPRFbE+R3Jio/LlciKuH0iWfH1/UNkRSR3RRYruSvyWKldkcjK7IpUVl5X5LLSuiKbldQV6aycrihgZXRFCau/K2pY3V1RxersijJWX1cUsnq6opLV0RW1rNldUc2a2RXlrHldsQBrTlfcLwv5EZm/PLIgkHXKPHyQRzXzYoO8BjIvzcgnBvJBxny+Ih/7zNEIcpDEHLshh5TIkS5zAI5cFzCXK8hVFHNxh1xzQpfC0BV6XWTJkkWILFmyCJElSxYhsmTJIkSWLFmEyJIlixBZsmQB8stk/U3/Yb49pVcDMg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDYtMjZUMTE6NDc6MDUrMDI6MDD8QCPmAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA2LTI2VDExOjQ3OjA1KzAyOjAwjR2bWgAAAABJRU5ErkJggg==";
const size = 40;
if (
execution.execution_source === undefined ||
execution.execution_source === null ||
execution.execution_source.length === 0
) {
return (
);
}
if (execution.execution_source === "webhook") {
return (
trigger.trigger_type === "WEBHOOK")
.large_image
}
style={{ width: size, height: size }}
/>
);
} else if (execution.execution_source === "outlook") {
return (
trigger.trigger_type === "EMAIL")
.large_image
}
style={{ width: size, height: size }}
/>
);
} else if (execution.execution_source === "schedule") {
return (
trigger.trigger_type === "SCHEDULE")
.large_image
}
style={{ width: size, height: size }}
/>
);
} else if (execution.execution_source === "EMAIL") {
return (
trigger.trigger_type === "EMAIL")
.large_image
}
style={{ width: size, height: size }}
/>
);
}
if (
execution.execution_parent !== null &&
execution.execution_parent !== undefined &&
execution.execution_parent.length > 0
) {
return (
trigger.trigger_type === "SUBFLOW")
.large_image
}
style={{ width: size, height: size }}
/>
);
}
return (
);
};
const handleReactJsonClipboard = (copy) => {
console.log("COPY: ", copy);
const elementName = "copy_element_shuffle";
var copyText = document.getElementById(elementName);
if (copyText !== null && copyText !== undefined) {
if (
copy.namespace !== undefined &&
copy.name !== undefined &&
copy.src !== undefined
) {
copy = copy.src;
}
const clipboard = navigator.clipboard;
if (clipboard === undefined) {
alert.error("Can only copy over HTTPS (port 3443)");
return;
}
var stringified = JSON.stringify(copy);
if (stringified.startsWith('"') && stringified.endsWith('"')) {
stringified = stringified.substring(1, stringified.length - 1);
}
navigator.clipboard.writeText(stringified);
copyText.select();
copyText.setSelectionRange(0, 99999); /* For mobile devices */
/* Copy the text inside the text field */
document.execCommand("copy");
console.log("COPYING!");
alert.info("Copied value to clipboard, NOT json path.")
} else {
console.log("Failed to copy from " + elementName + ": ", copyText);
}
};
const HandleJsonCopy = (base, copy, base_node_name) => {
if (typeof copy.name === "string") {
copy.name = copy.name.replaceAll(" ", "_");
}
//lol
if (typeof base === 'object' || typeof base === 'dict') {
base = JSON.stringify(base)
}
if (base_node_name === "execution_argument" || base_node_name === "Execution Argument") {
base_node_name = "exec"
}
console.log("COPY: ", base_node_name, copy);
//var newitem = JSON.parse(base);
var newitem = validateJson(base).result
// Check if base_node_name has changed
if (cy !== undefined && cy !== null) {
console.log("Change name?")
//const allNodes = cy.nodes().jsons();
//for (var key in allNodes) {
//const currentNode = allNodes[key];
//if (currentNode.
//}
//const nodedata = cy.getElementById(data.action.id).data();
//base_node_name =
}
to_be_copied = "$" + base_node_name.toLowerCase().replaceAll(" ", "_");
for (let copykey in copy.namespace) {
if (copy.namespace[copykey].includes("Results for")) {
continue;
}
if (newitem !== undefined && newitem !== null) {
newitem = newitem[copy.namespace[copykey]];
if (!isNaN(copy.namespace[copykey])) {
to_be_copied += ".#";
} else {
to_be_copied += "." + copy.namespace[copykey];
}
}
}
if (newitem !== undefined && newitem !== null) {
newitem = newitem[copy.name];
if (!isNaN(copy.name)) {
to_be_copied += ".#";
} else {
to_be_copied += "." + copy.name;
}
}
to_be_copied.replaceAll(" ", "_");
const elementName = "copy_element_shuffle";
var copyText = document.getElementById(elementName);
if (copyText !== null && copyText !== undefined) {
console.log("NAVIGATOR: ", navigator);
const clipboard = navigator.clipboard;
if (clipboard === undefined) {
alert.error("Can only copy over HTTPS (port 3443)");
return;
}
navigator.clipboard.writeText(to_be_copied);
copyText.select();
copyText.setSelectionRange(0, 99999); /* For mobile devices */
/* Copy the text inside the text field */
document.execCommand("copy");
console.log("COPYING!");
alert.info("Copied JSON path to clipboard.")
} else {
console.log("Couldn't find element ", elementName);
}
}
// Not used because of issue with state updates.
const ShowReactJsonField = (props) => {
const { validate, jsonValue, collapsed, label, autocomplete } = props
const [parsedCollapse, setParsedCollapse] = React.useState(collapsed)
const [open, setOpen] = React.useState(false);
const [anchorPosition, setAnchorPosition] = React.useState({
top: 750,
left: 16,
});
const isFirstRender = React.useRef(true)
useEffect(() => {
console.log("IN useeffectt " + autocomplete)
if (isFirstRender.current) {
isFirstRender.current = false;
console.log("IN useeffectt (2)" + collapsed)
return;
}
})
/*
componentWillUpdate = (nextProps, nextState) => {
console.log(nextProps, nextState)
//nextState.value = nextProps.a + nextProps.b;
}
*/
const jsonRef = React.useRef()
return (
{
console.log("FIELD: ", field)
}}
enableClipboard={(copy) => {
handleReactJsonClipboard(copy);
}}
displayDataTypes={false}
onClick={(event) => {
const pos = {
top: event.screenX,
left: event.screenY,
}
console.log("POS CLICK: ", pos)
setAnchorPosition(pos)
}}
onSelect={(select) => {
setOpen(true)
setTimeout(() => {
setOpen(false)
}, 2500)
//setAnchorPosition({
// top: 300,
// right: 300,
//})
//setAnchorEl(jsonRef.current)
HandleJsonCopy(jsonValue, select, autocomplete);
console.log("SELECTED!: ", select);
}}
name={label}
/>
{anchorPosition !== null ?
{
setAnchorPosition({
top: 750,
left: 16,
})
}}
disableRestoreFocus
>
Copying
: null}
)
}
const ShowCopyingTooltip = () => {
const [showCopying, setShowCopying] = React.useState(true)
if (!showCopying) {
return false
}
return (
)
}
var nonskippedResults = []
if (executionData.results !== undefined) {
const newSkipped = executionData.results.find((result) => result.status !== "SKIPPED")
if (newSkipped !== undefined) {
nonskippedResults = newSkipped
}
}
var executionDelay = -75
const executionModal = (
{
setExecutionModalOpen(false)
}}
style={{ resize: "both", overflow: "auto", zIndex: 10005 }}
hideBackdrop={false}
variant="temporary"
BackdropProps={{
style: {
//backgroundColor: "transparent",
}
}}
PaperProps={{
style: {
resize: "both",
overflow: "auto",
minWidth: isMobile ? "100%" : 420,
maxWidth: isMobile ? "100%" : 420,
backgroundColor: "#1F2023",
color: "white",
fontSize: 18,
zIndex: 10005,
borderLeft: theme.palette.defaultBorder,
},
}}
>
{isMobile ?
{
e.preventDefault();
setExecutionModalOpen(false)
}}
>
: null}
{executionModalView === 0 ? (
All Executions
{
getWorkflowExecution(props.match.params.key, "");
}}
color="primary"
>
Refresh executions
{workflowExecutions.length > 0 ? (
{workflowExecutions.map((data, index) => {
executionDelay += 50
const statusColor =
data.status === "FINISHED"
? green : data.status === "ABORTED" || data.status === "FAILED"
? "red"
: yellow;
const resultsLength =
data.results !== undefined && data.results !== null
? data.results.length
: 0;
const timestamp = new Date(data.started_at * 1000)
.toLocaleString("en-GB")
.split(".")[0]
.split("T")
.join(" ");
var calculatedResult =
data.workflow.actions !== undefined &&
data.workflow.actions !== null
? data.workflow.actions.length
: 0;
if (data.workflow.triggers !== undefined && data.workflow.triggers !== null) {
for (let triggerkey in data.workflow.triggers) {
const trigger = data.workflow.triggers[triggerkey];
if (
(trigger.app_name === "User Input" &&
trigger.trigger_type === "USERINPUT") ||
(trigger.app_name === "Shuffle Workflow" &&
trigger.trigger_type === "SUBFLOW")
) {
calculatedResult += 1;
}
}
}
return (
{ }}
onMouseOut={() => { }}
onClick={() => {
if (
(data.result === undefined ||
data.result === null ||
data.result.length === 0) &&
data.status !== "FINISHED" &&
data.status !== "ABORTED"
) {
start();
setExecutionRunning(true);
setExecutionRequestStarted(false);
}
// Ensuring we have the latest version of the result.
// Especially important IF the result is > 1 Mb in cloud
var checkStarted = false
if (data.results !== undefined && data.results !== null && data.results.length > 0) {
if (data.execution_argument !== undefined && data.execution_argument !== null && data.execution_argument.includes("too large")) {
setExecutionData({});
checkStarted = true
start();
setExecutionRunning(true);
setExecutionRequestStarted(false);
} else {
if (data.results !== undefined && data.results !== null) {
for (let resultkey in data.results) {
if (data.results[resultkey].status !== "SUCCESS") {
continue
}
if (data.results[resultkey].result.includes("too large")) {
setExecutionData({});
checkStarted = true
start();
setExecutionRunning(true);
setExecutionRequestStarted(false);
break
}
}
}
}
}
const cur_execution = {
execution_id: data.execution_id,
authorization: data.authorization,
};
setExecutionRequest(cur_execution);
setExecutionModalView(1);
if (!checkStarted) {
handleUpdateResults(data, cur_execution);
console.log("Clearing colors during click for: !", data)
if (cy !== undefined && cy !== null) {
cy.elements().removeClass("success-highlight failure-highlight executing-highlight");
for (let actionKey in data.workflow.actions) {
var actionitem = data.workflow.actions[actionKey];
handleColoring(actionitem.id, "", actionitem.label)
}
for (let resultKey in data.results) {
var item = data.results[resultKey];
handleColoring(item.action.id, item.status, item.action.label)
}
}
setExecutionData(data);
}
}}
>
{getExecutionSourceImage(data)}
{timestamp}
{data.workflow.actions !== null ? (
{resultsLength}/{calculatedResult}
) : null}
{lastExecution === data.execution_id ? (
) : (
)}
);
})}
) : (
There are no executions yet, or they are not loaded.
)}
) : (
{
setExecutionRunning(false);
stop();
getWorkflowExecution(props.match.params.key, "");
setExecutionModalView(0);
setLastExecution(executionData.execution_id);
}}
>
{ }}
>
{ }}
>
See other Executions
Execution info
{
//console.log("DATA: ", executionData);
executeWorkflow(
executionData.execution_argument,
executionData.start,
lastSaved
)
if (executionText === undefined || executionText === null || executionText.length === 0) {
setExecutionText(executionData.execution_argument)
}
setExecutionModalOpen(false);
}}
>
{executionData.status === "EXECUTING" ? (
{
abortExecution();
}}
>
) : null}
{executionData.status !== undefined &&
executionData.status.length > 0 ? (
Status
{executionData.status}
) : null}
{executionData.execution_source !== undefined &&
executionData.execution_source !== null &&
executionData.execution_source.length > 0 &&
executionData.execution_source !== "default" ? (
Source
{executionData.execution_parent !== null &&
executionData.execution_parent !== undefined &&
executionData.execution_parent.length > 0 ? (
executionData.execution_source === props.match.params.key ? (
{
getWorkflowExecution(
props.match.params.key,
executionData.execution_parent
);
}}
>
Parent Execution
) : (
Parent Workflow
)
) : (
executionData.execution_source
)}
) : null}
{executionData.started_at !== undefined ? (
Started
{new Date(executionData.started_at * 1000).toLocaleString("en-GB")}
) : null}
{executionData.completed_at !== undefined &&
executionData.completed_at !== null &&
executionData.completed_at > 0 ? (
{
console.log(executionData)
}}>
Finished
{new Date(executionData.completed_at * 1000).toLocaleString("en-GB")}
) : null}
{executionData.execution_argument !== undefined &&
executionData.execution_argument.length > 0
? parsedExecutionArgument()
: null}
{executionData.results !== undefined &&
executionData.results !== null &&
executionData.results.length > 1 &&
executionData.results.find(
(result) =>
result.status === "SKIPPED"
) ? (
Show skipped actions
}
control={
{
setShowSkippedActions(!showSkippedActions);
}}
/>
}
/>
) : null}
{executionData.status !== undefined &&
executionData.status !== "ABORTED" &&
executionData.status !== "FINISHED" &&
executionData.status !== "FAILURE" &&
executionData.status !== "WAITING" &&
!(executionData.results === undefined || executionData.results === null || (executionData.results.length === 0 && executionData.status === "EXECUTING")) ? (
{
console.log(environments, defaultEnvironmentIndex, nonskippedResults)
}} />
{environments.length > 0 && defaultEnvironmentIndex < environments.length && nonskippedResults.length === 0 && environments[defaultEnvironmentIndex].Name !== "Cloud" ?
No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? Learn more . If the Workflow doesn't start within 30 seconds with Orborus running, contact support: support@shuffler.io
: null}
) : null}
{
executionData.results === undefined ||
executionData.results === null ||
(executionData.results.length === 0 && executionData.status === "EXECUTING") ? (
{environments.length > 0 && defaultEnvironmentIndex < environments.length && nonskippedResults.length === 0 && environments[defaultEnvironmentIndex].Name !== "Cloud" ?
No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? Learn more . If the Workflow doesn't start within 30 seconds with Orborus running, contact support: support@shuffler.io
:
null}
) : (
executionData.results.map((data, index) => {
if (executionData.results.length !== 1 && !showSkippedActions && (data.status === "SKIPPED")) {
return null;
}
// FIXME: The latter replace doens't really work if ' is used in a string
var showResult = data.result.trim();
const validate = validateJson(showResult);
const curapp = apps.find(
(a) =>
a.name === data.action.app_name &&
a.app_version === data.action.app_version
);
const imgsize = 50;
const statusColor =
data.status === "FINISHED" || data.status === "SUCCESS"
? green
: data.status === "ABORTED" || data.status === "FAILURE"
? "red"
: yellow;
var imgSrc = curapp === undefined ? "" : curapp.large_image;
if (
imgSrc.length === 0 &&
workflow.actions !== undefined &&
workflow.actions !== null
) {
// Look for the node in the workflow
const action = workflow.actions.find(
(action) => action.id === data.action.id
);
if (action !== undefined && action !== null) {
imgSrc = action.large_image;
}
}
var actionimg =
curapp === null ? null : (
);
if (triggers.length > 2) {
if (data.action.app_name === "shuffle-subflow") {
const parsedImage = triggers[4].large_image;
actionimg = (
);
}
if (data.action.app_name === "User Input") {
actionimg = (
);
}
}
if (
data.action.app_name === "Shuffle Tools" &&
data.action.id !== undefined &&
cy !== undefined
) {
const nodedata = cy.getElementById(data.action.id).data();
if (
nodedata !== undefined &&
nodedata !== null &&
nodedata.fillstyle === "linear-gradient"
) {
var imgStyle = {
marginRight: 20,
width: imgsize,
height: imgsize,
border: `2px solid ${statusColor}`,
borderRadius:
executionData.start === data.action.id ? 25 : 5,
background: `linear-gradient(to right, ${nodedata.fillGradient})`,
};
actionimg = (
);
}
}
if (validate.valid && typeof validate.result === "string") {
validate.result = JSON.parse(validate.result);
}
if (validate.valid && typeof validate.result === "object") {
if (
validate.result.result !== undefined &&
validate.result.result !== null
) {
try {
validate.result.result = JSON.parse(validate.result.result);
} catch (e) {
//console.log("ERROR PARSING: ", e)
}
}
}
var similarActionsView = null
if (data.similar_actions !== undefined && data.similar_actions !== null) {
var minimumMatch = 85
var matching_executions = []
if (data.similar_actions !== undefined && data.similar_actions !== null) {
for (let [k,kval] in Object.entries(data.similar_actions)){
if (data.similar_actions.hasOwnProperty(k)) {
if (data.similar_actions[k].similarity > minimumMatch) {
matching_executions.push(data.similar_actions[k].execution_id)
}
}
}
}
if (matching_executions.length !== 0) {
var parsed_url = matching_executions.join(",")
similarActionsView =
{
navigate(`?execution_highlight=${parsed_url}`)
}}
>
}
}
return (
{
var currentnode = cy.getElementById(data.action.id);
if (currentnode !== undefined && currentnode !== null && currentnode.length !== 0) {
currentnode.addClass("shuffle-hover-highlight");
}
// Add a hover highlight
//var copyText = document.getElementById(
// "copy_element_shuffle"
//)
}}
onMouseOut={() => {
var currentnode = cy.getElementById(data.action.id);
if (currentnode.length !== 0) {
currentnode.removeClass("shuffle-hover-highlight");
}
}}
>
{
const oldstartnode = cy.getElementById(data.action.id);
console.log("FOUND NODe: ", oldstartnode)
if (oldstartnode !== undefined && oldstartnode !== null) {
const foundname = oldstartnode.data("label")
if (foundname !== undefined && foundname !== null) {
data.action.label = foundname
}
}
console.log("Click data: ", data)
//data.action.label = ""
setSelectedResult(data);
setCodeModalOpen(true);
}}
>
{actionimg}
{data.action.label}
{data.action.name}
{data.action.app_name === "shuffle-subflow" &&
validate.result.success !== undefined &&
validate.result.success === true ? (
{validate.valid &&
data.action.parameters !== undefined &&
data.action.parameters !== null &&
data.action.parameters.length > 0 ? (
data.action.parameters[0].value ===
props.match.params.key ? (
{
getWorkflowExecution(
props.match.params.key,
validate.result.execution_id
);
}}
>
) : (
{ }}
>
)
) : (
""
)}
) : null}
Status
{data.status}
{similarActionsView}
{validate.valid ? (
{
handleReactJsonClipboard(copy);
}}
displayDataTypes={false}
onSelect={(select) => {
HandleJsonCopy(showResult, select, data.action.label);
console.log("SELECTED!: ", select);
}}
name={"Results for " + data.action.label}
/>
) : (
Result
{data.result}
)}
);
})
)}
)}
);
// This sucks :)
const curapp = !codeModalOpen
? {}
: selectedResult.action.app_name === "shuffle-subflow"
? triggers[2]
: selectedResult.action.app_name === "User Input"
? triggers[3]
: apps.find(
(a) =>
a.name === selectedResult.action.app_name &&
a.app_version === selectedResult.action.app_version
);
const imgsize = 50;
const statusColor = !codeModalOpen
? "red"
: selectedResult.status === "FINISHED" ||
selectedResult.status === "SUCCESS"
? green
: selectedResult.status === "ABORTED" || selectedResult.status === "FAILURE"
? "red"
: yellow;
const validate = !codeModalOpen
? ""
: validateJson(selectedResult.result.trim());
if (validate.valid && typeof validate.result === "string") {
validate.result = JSON.parse(validate.result);
}
const AppResultVariable = ({ data }) => {
const [open, setOpen] = React.useState(false)
const showVariable = data.value.length < 60
return (
{data.value.length > 60 ?
{
if (!showVariable) {
setOpen(!open)
}
}}
>
{data.name} {showVariable ? data.value : null}
:
{data.name} : {showVariable ? data.value : null}
}
{open ?
{data.value}
: null}
)
}
var draggingDisabled = false;
const codePopoutModal = !codeModalOpen ? null : (
{
e.preventDefault();
if (workflowExecutions !== null) {
for (let execkey in workflowExecutions) {
const execution = workflowExecutions[execkey];
if (execution.execution_argument.includes("too large")) {
continue
}
const result = execution.results.find((data) => data.status === "SUCCESS" && data.action.id === selectedResult.action.id)
if (result !== undefined) {
const oldstartnode = cy.getElementById(selectedResult.action.id);
if (oldstartnode !== undefined && oldstartnode !== null) {
const foundname = oldstartnode.data("label")
if (foundname !== undefined && foundname !== null) {
result.action.label = foundname
}
}
setSelectedResult(result);
setUpdate(Math.random());
break;
}
}
}
}}
>
{
e.preventDefault();
for (let execkey in workflowExecutions) {
const execution = workflowExecutions[execkey];
const result = execution.results.find(
(data) =>
data.action.id === selectedResult.action.id &&
data.status !== "SUCCESS" &&
data.status !== "SKIPPED" &&
data.status !== "WAITING"
);
if (result !== undefined) {
const oldstartnode = cy.getElementById(selectedResult.action.id);
if (oldstartnode !== undefined && oldstartnode !== null) {
const foundname = oldstartnode.data("label")
if (foundname !== undefined && foundname !== null) {
result.action.label = foundname
}
}
setSelectedResult(result);
setUpdate(Math.random());
break;
}
}
}}
>
{
e.preventDefault();
const executionIndex = workflowExecutions.findIndex((data) => data.execution_id === selectedResult.execution_id);
if (executionIndex !== -1) {
setExecutionModalOpen(true);
setExecutionModalView(1);
if (workflowExecutions[executionIndex] !== undefined && workflowExecutions[executionIndex] !== null && workflowExecutions[executionIndex].execution_argument.includes("too large")) {
//checkStarted = true
setExecutionData({});
start();
setExecutionRunning(true);
setExecutionRequestStarted(false);
} else {
setExecutionData(workflowExecutions[executionIndex]);
}
}
}}
>
{
e.preventDefault();
setCodeModalOpen(false);
}}
>
{curapp === null ? null : (
)}
{selectedResult.action.label}
{selectedResult.action.name}
Status {selectedResult.status}
{validate.valid ? (
{
handleReactJsonClipboard(copy);
}}
displayDataTypes={false}
onSelect={(select) => {
HandleJsonCopy(validate.result, select, selectedResult.action.label);
}}
name={"Results for " + selectedResult.action.label}
/>
) : (
Result
{
console.log("IN HERE TO CLICK");
to_be_copied = selectedResult.result;
var copyText = document.getElementById(
"copy_element_shuffle"
);
console.log("PRECOPY: ", to_be_copied);
if (copyText !== null && copyText !== undefined) {
console.log("COPY: ", copyText);
console.log("NAVIGATOR: ", navigator);
const clipboard = navigator.clipboard;
if (clipboard === undefined) {
alert.error("Can only copy over HTTPS (port 3443)");
return;
}
navigator.clipboard.writeText(to_be_copied);
copyText.select();
copyText.setSelectionRange(
0,
99999
); /* For mobile devices */
/* Copy the text inside the text field */
document.execCommand("copy");
} else {
console.log(
"Failed to copy. copy_element_shuffle is undefined"
);
}
}}
>
{selectedResult.result}
)}
{selectedResult.action.parameters !== null &&
selectedResult.action.parameters !== undefined ? (
Variables (click to expand)
{selectedResult.action.parameters.map((data, index) => {
if (data.value.length === 0) {
return null;
}
if (
data.example !== undefined &&
data.example !== null &&
data.example.includes("***")
) {
return null;
}
return (
);
})}
) : null}
);
const newView = (
{/*isMobile ? null : leftView*/}
{leftView}
{workflow.id === undefined || workflow.id === null || appsLoaded === false ? (
Loading Workflow
) : (
{
// FIXME: There's something specific loading when
// you do the first hover of a node. Why is this different?
//console.log("CY: ", incy)
setCy(incy);
}}
/>
)}
{executionModal}
);
const editWorkflowModal =
{
setEditWorkflowDetails(false)
}}
PaperProps={{
style: {
pointerEvents: "auto",
backgroundColor: surfaceColor,
color: "white",
border: theme.palette.defaultBorder,
maxWidth: "100%",
padding: 50,
},
}}
>
Edit workflow!
const ExecutionVariableModal = (props) => {
const { variableInfo } = props
const [newVariableName, setNewVariableName] = React.useState(variableInfo.name !== undefined ? variableInfo.name : "");
const [newVariableDescription, setNewVariableDescription] = React.useState(variableInfo.description !== undefined ? variableInfo.description : "");
const [newVariableValue, setNewVariableValue] = React.useState(variableInfo.value !== undefined ? variableInfo.value : "");
if (!executionVariablesModalOpen) {
return null
}
return (
{
setNewVariableName("");
setExecutionVariablesModalOpen(false);
}}
PaperProps={{
style: {
pointerEvents: "auto",
backgroundColor: surfaceColor,
color: "white",
border: theme.palette.defaultBorder,
maxWidth: "100%",
},
}}
>
Execution Variable
Execution Variables are TEMPORARY variables that you can only be set
and used during execution. Learn more{" "}
here
setNewVariableName(event.target.value)}
color="primary"
placeholder="Name"
style={{ marginTop: 25 }}
InputProps={{
style: {
color: "white",
},
}}
margin="dense"
fullWidth
defaultValue={newVariableName}
/>
{
setNewVariableName("");
setExecutionVariablesModalOpen(false);
}}
color="primary"
>
Cancel
{
console.log("VARIABLES! ", newVariableName);
if (
workflow.execution_variables === undefined ||
workflow.execution_variables === null
) {
workflow.execution_variables = [];
}
// try to find one with the same name
const found = workflow.execution_variables.findIndex(
(data) => data.name === newVariableName
);
//console.log(found)
if (found !== -1) {
if (newVariableName.length > 0) {
workflow.execution_variables[found].name = newVariableName;
}
} else {
workflow.execution_variables.push({
name: newVariableName,
description: "An execution variable",
value: "",
id: uuidv4(),
});
}
setExecutionVariablesModalOpen(false);
setNewVariableName("");
setWorkflow(workflow);
}}
color="primary"
>
Submit
{workflowExecutions.length > 0 ? (
Values from last 3 executions
{workflowExecutions.slice(0, 3).map((execution, index) => {
if (
execution.execution_variables === undefined ||
execution.execution_variables === null ||
execution.execution_variables === 0
) {
return null;
}
const variable = execution.execution_variables.find(
(data) => data.name === newVariableName
)
if (variable === undefined || variable.value === undefined) {
return null;
}
return (
{index + 1}: {variable.value}
);
})}
) : null}
)
}
const VariablesModal = (props) => {
const { setVariableInfo, variableInfo } = props
const [newVariableName, setNewVariableName] = React.useState(variableInfo.name !== undefined ? variableInfo.name : "");
const [newVariableDescription, setNewVariableDescription] = React.useState(variableInfo.description !== undefined ? variableInfo.description : "");
const [newVariableValue, setNewVariableValue] = React.useState(variableInfo.value !== undefined ? variableInfo.value : "");
if (!variablesModalOpen) {
return null
}
return (
{
setNewVariableName("");
setNewVariableDescription("");
setNewVariableValue("");
setVariablesModalOpen(false);
}}
PaperProps={{
style: {
pointerEvents: "auto",
backgroundColor: surfaceColor,
color: "white",
border: theme.palette.defaultBorder,
maxWidth: isMobile ? bodyWidth - 100 : "100%",
},
}}
>
Workflow Variable
setNewVariableName(event.target.value)}
color="primary"
placeholder="Name"
InputProps={{
style: {
color: "white",
},
}}
margin="dense"
fullWidth
defaultValue={newVariableName}
/>
setNewVariableDescription(event.target.value)}
color="primary"
placeholder="Description"
margin="dense"
fullWidth
InputProps={{
style: {
color: "white",
},
}}
defaultValue={newVariableDescription}
/>
setNewVariableValue(event.target.value)}
rows="6"
multiline
color="primary"
placeholder="Value"
margin="dense"
InputProps={{
style: {
color: "white",
},
}}
fullWidth
defaultValue={newVariableValue}
/>
{
setNewVariableName("");
setNewVariableDescription("");
setNewVariableValue("");
setVariablesModalOpen(false);
}}
color="primary"
>
Cancel
{
var handled = false
if (
workflow.workflow_variables === undefined ||
workflow.workflow_variables === null
) {
workflow.workflow_variables = [];
} else {
if (variableInfo.index !== undefined && variableInfo.index !== null && variableInfo.index >= 0) {
if (newVariableName.length > 0) {
workflow.workflow_variables[variableInfo.index].name = newVariableName;
}
if (newVariableDescription.length > 0) {
workflow.workflow_variables[variableInfo.index].description =
newVariableDescription;
}
if (newVariableValue.length > 0) {
workflow.workflow_variables[variableInfo.index].value = newVariableValue;
}
handled = true
}
}
if (!handled) {
// try to find one with the same name
const found = workflow.workflow_variables.findIndex(
(data) => data.name === newVariableName
);
if (found !== -1) {
if (newVariableName.length > 0) {
workflow.workflow_variables[found].name = newVariableName;
}
if (newVariableDescription.length > 0) {
workflow.workflow_variables[found].description =
newVariableDescription;
}
if (newVariableValue.length > 0) {
workflow.workflow_variables[found].value = newVariableValue;
}
} else {
workflow.workflow_variables.push({
name: newVariableName,
description: newVariableDescription,
value: newVariableValue,
id: uuidv4(),
});
}
}
setVariableInfo({})
setWorkflow(workflow);
setVariablesModalOpen(false);
setNewVariableName("");
setNewVariableDescription("");
setNewVariableValue("");
}}
color="primary"
>
Submit
)
}
const AuthenticationData = (props) => {
const selectedApp = props.app;
const [authenticationOption, setAuthenticationOptions] = React.useState({
app: JSON.parse(JSON.stringify(selectedApp)),
fields: {},
label: "",
usage: [
{
workflow_id: workflow.id,
},
],
id: uuidv4(),
active: true,
});
if (
selectedApp.authentication === undefined ||
selectedApp.authentication.parameters === null ||
selectedApp.authentication.parameters === undefined ||
selectedApp.authentication.parameters.length === 0
) {
return (
{selectedApp.name} does not require authentication
);
}
authenticationOption.app.actions = [];
for (let paramkey in selectedApp.authentication.parameters) {
if (
authenticationOption.fields[
selectedApp.authentication.parameters[paramkey].name
] === undefined
) {
authenticationOption.fields[
selectedApp.authentication.parameters[paramkey].name
] = "";
}
}
const handleSubmitCheck = () => {
if (authenticationOption.label.length === 0) {
authenticationOption.label = `Auth for ${selectedApp.name}`;
}
// Automatically mapping fields that already exist (predefined).
// Warning if fields are NOT filled
for (let paramkey in selectedApp.authentication.parameters) {
if (
authenticationOption.fields[
selectedApp.authentication.parameters[paramkey].name
].length === 0
) {
if (
selectedApp.authentication.parameters[paramkey].value !== undefined &&
selectedApp.authentication.parameters[paramkey].value !== null &&
selectedApp.authentication.parameters[paramkey].value.length > 0
) {
authenticationOption.fields[
selectedApp.authentication.parameters[paramkey].name
] = selectedApp.authentication.parameters[paramkey].value;
} else {
if (
selectedApp.authentication.parameters[paramkey].schema.type === "bool"
) {
authenticationOption.fields[
selectedApp.authentication.parameters[paramkey].name
] = "false";
} else {
alert.info(
"Field " +
selectedApp.authentication.parameters[paramkey].name +
" can't be empty"
);
return;
}
}
}
}
console.log("Action: ", selectedAction);
selectedAction.authentication_id = authenticationOption.id;
selectedAction.selectedAuthentication = authenticationOption;
if (
selectedAction.authentication === undefined ||
selectedAction.authentication === null
) {
selectedAction.authentication = [authenticationOption];
} else {
selectedAction.authentication.push(authenticationOption);
}
setSelectedAction(selectedAction);
var newAuthOption = JSON.parse(JSON.stringify(authenticationOption));
var newFields = [];
console.log("Fields: ", newAuthOption.fields)
for (let authkey in newAuthOption.fields) {
const value = newAuthOption.fields[authkey];
newFields.push({
"key": authkey,
"value": value,
});
}
newAuthOption.fields = newFields;
setNewAppAuth(newAuthOption);
if (configureWorkflowModalOpen) {
setSelectedAction({});
}
setUpdate(authenticationOption.id);
};
if (
authenticationOption.label === null ||
authenticationOption.label === undefined
) {
authenticationOption.label = selectedApp.name + " authentication";
}
return (
Authentication for {selectedApp.name}
What is app authentication?
These are required fields for authenticating with {selectedApp.name}
Name - what is this used for?
{
authenticationOption.label = event.target.value;
}}
/>
{selectedApp.authentication.parameters.map((data, index) => {
return (
{data.name}
{data.schema !== undefined &&
data.schema !== null &&
data.schema.type === "bool" ? (
{
console.log("Value: ", e.target.value);
authenticationOption.fields[data.name] = e.target.value;
}}
style={{
backgroundColor: theme.palette.surfaceColor,
color: "white",
height: 50,
}}
>
false
true
) : (
{
authenticationOption.fields[data.name] =
event.target.value;
}}
/>
)}
);
})}
{
setAuthenticationModalOpen(false);
}}
color="primary"
>
Cancel
{
setAuthenticationOptions(authenticationOption);
handleSubmitCheck();
}}
color="primary"
>
Submit
);
};
const configureWorkflowModal =
configureWorkflowModalOpen && apps.length !== 0 ? (
{
setConfigureWorkflowModalOpen(false);
}}
>
) : null;
// This whole part is redundant. Made it part of Arguments instead.
const authenticationModal = authenticationModalOpen ? (
{
//if (configureWorkflowModalOpen) {
// setSelectedAction({});
//}
}}
PaperProps={{
style: {
pointerEvents: "auto",
backgroundColor: surfaceColor,
color: "white",
minWidth: 1100,
minHeight: 700,
maxHeight: 700,
padding: 15,
overflow: "hidden",
zIndex: 10012,
border: theme.palette.defaultBorder,
},
}}
>
{selectedApp.reference_info === undefined ||
selectedApp.reference_info === null ||
selectedApp.reference_info.github_url === undefined ||
selectedApp.reference_info.github_url === null ||
selectedApp.reference_info.github_url.length === 0 ? (
) : (
)}
{
setAuthenticationModalOpen(false);
if (configureWorkflowModalOpen) {
setSelectedAction({});
}
}}
>
{authenticationType.type === "oauth2" ? (
) : (
)}
{selectedApp.documentation === undefined ||
selectedApp.documentation === null ||
selectedApp.documentation.length === 0 ? (
{selectedApp.description}
There is currently no extended documentation available for this
app.
Want help help making or using this app?{" "}
Join the community on Discord!
Want to help change this app directly?
{selectedApp.reference_info === undefined ||
selectedApp.reference_info === null ||
selectedApp.reference_info.github_url === undefined ||
selectedApp.reference_info.github_url === null ||
selectedApp.reference_info.github_url.length === 0 ? (
Check it out on Github!
) : (
Check it out on Github!
)}
) : (
)}
) : null;
const loadedCheck =
isLoaded && workflowDone ? (
{newView}
{conditionsModal}
{authenticationModal}
{codePopoutModal}
{configureWorkflowModal}
{editWorkflowModal}
{editWorkflowModalOpen === true ?
: null}
{/*selectionOpen === true ?
{
setSelectionOpen(false)
}}
>
: null*/}
{showVideo !== undefined && showVideo.length > 0 ?
{
setShowVideo("")
}}
>
: null}
) : (
);
// Awful way of handling scroll
if (
scrollConfig !== undefined &&
setScrollConfig !== undefined &&
Object.getOwnPropertyNames(selectedAction).length !== 0
) {
const rightSideActionView = document.getElementById("rightside_actions");
if (rightSideActionView !== undefined && rightSideActionView !== null) {
if (
scrollConfig.top !== 0 &&
scrollConfig.top !== undefined &&
scrollConfig.top !== 0
) {
setTimeout(() => {
if (
scrollConfig.selected !== undefined &&
scrollConfig.selected !== null
) {
const selectedField = document.getElementById(
scrollConfig.selected
);
if (selectedField !== undefined && selectedField !== null) {
selectedField.focus();
}
}
}, 5);
} else {
if (rightSideActionView.scrollTop !== scrollConfig.top) {
setScrollConfig({
top: rightSideActionView.scrollTop,
left: 0,
selected: "",
});
}
}
}
}
return (
{/* Removed due to missing react router features
*/}
{loadedCheck}
);
};
export default AngularWorkflow;