import React, {useRef, useState, useEffect, useLayoutEffect} from 'react';
import { useInterval } from 'react-powerhooks';
import { useTheme } from '@material-ui/core/styles';
import { v4 as uuidv4 } from 'uuid';
import {Link} from 'react-router-dom';
import { Prompt } from 'react-router'
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 {Slide, TextField, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade} from '@material-ui/core';
import {OpenInNew as OpenInNewIcon,Undo as UndoIcon, FileCopy as FileCopyIcon, GetApp as GetAppIcon, Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, 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, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
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 { w3cwebsocket as W3CWebSocket } from "websocket";
import { useAlert } from "react-alert";
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 Scroll from 'react-scroll'
//import { Element as ScrollElement, animateScroll as scroll, scrollSpy, scroller } from 'react-scroll'
const surfaceColor = "#27292D"
const inputColor = "#383B40"
const appSorter = "favorite_apps"
// 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))
})
}
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 (var 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 splitter = "|~|"
const svgSize = 24
//const referenceUrl = "https://shuffler.io/functions/webhooks/"
//const referenceUrl = window.location.origin+"/api/v1/hooks/"
function useTraceUpdate(props) {
const prev = useRef(props)
useEffect(() => {
const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
if (prev.current[k] !== v) {
ps[k] = [prev.current[k], v];
}
return ps;
}, {});
if (Object.keys(changedProps).length > 0) {
console.log('Changed props:', changedProps);
}
prev.current = props;
});
}
const AngularWorkflow = (props) => {
const { globalUrl, isLoggedIn, isLoaded, userdata } = props;
const referenceUrl = globalUrl+"/api/v1/hooks/"
const alert = useAlert()
const theme = useTheme();
const green = "#86c142"
const yellow = "#FECC00"
const [bodyWidth, bodyHeight] = useWindowSize();
var to_be_copied = ""
const [cystyle, ] = useState(cytoscapestyle)
const [cy, setCy] = React.useState()
const [currentView, setCurrentView] = React.useState(0)
const [triggerAuthentication, setTriggerAuthentication] = React.useState({})
const [triggerFolders, setTriggerFolders] = React.useState([])
const [workflows, setWorkflows] = React.useState([])
const [showEnvironment, setShowEnvironment] = React.useState(false)
const [workflow, setWorkflow] = React.useState({});
const [userSettings, setUserSettings] = React.useState({});
const [subworkflow, setSubworkflow] = React.useState({});
const [subworkflowStartnode, setSubworkflowStartnode] = React.useState("");
const [leftViewOpen, setLeftViewOpen] = React.useState(true);
const [leftBarSize, setLeftBarSize] = React.useState(350)
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 [appAuthentication, setAppAuthentication] = React.useState([]);
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 [newVariableName, setNewVariableName] = React.useState("");
const [authenticationType, setAuthenticationType] = React.useState("");
const [newVariableDescription, setNewVariableDescription] = React.useState("");
const [newVariableValue, setNewVariableValue] = React.useState("");
const [workflowDone, setWorkflowDone] = React.useState(false)
const [authLoaded, setAuthLoaded] = 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, setCurpath] = useState(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 [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 [workflow, setWorkflow] = React.useState(workflowdata);
const [apps, setApps] = React.useState([]);
const [filteredApps, setFilteredApps] = React.useState([]);
const [prioritizedApps, setPrioritizedApps] = React.useState([]);
const [firstrequest, setFirstrequest] = React.useState(true)
//const [apps, setApps] = React.useState(appdata);
//const [filteredApps, setFilteredApps] = React.useState();
const [environments, setEnvironments] = React.useState([]);
const [established, setEstablished] = 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 [, setExecutingNodes] = 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 [lastSaved, setLastSaved] = React.useState(true)
const [appAdded, setAppAdded] = useState(false)
const [update, setUpdate] = useState("");
const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0)
// This should all be set once, not on every iteration
// Use states and don't update lol
const cloudSyncEnabled = props.userdata !== undefined && props.userdata.active_org !== null && props.userdata.active_org !== undefined ? props.userdata.active_org.cloud_sync === true : false
//const triggerEnvironments = cloudSyncEnabled ? ["cloud", "onprem"] : environments
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)?'
useBeforeunload(() => {
if (!lastSaved) {
return unloadText
}
})
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 getAvailableWorkflows = (trigger_index) => {
fetch(globalUrl+"/api/v1/workflows", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflows :O!")
return
}
return response.json()
})
.then((responseJson) => {
if (responseJson !== undefined) {
setWorkflows(responseJson)
// Sets up subflow trigger with the right info
if (trigger_index > -1) {
var outersub = {}
const trigger = workflow.triggers[trigger_index]
if (trigger.parameters.length >= 3) {
for (var key in trigger.parameters) {
const param = trigger.parameters[key]
if (param.name === "workflow") {
if (param.value === workflow.id) {
setSubworkflow(workflow)
outersub = workflow
} else {
const sub = responseJson.find(data => data.id === param.value)
if (sub !== undefined && subworkflow.id !== sub.id) {
setSubworkflow(sub)
outersub = sub
}
}
}
console.log("PARAM: ", param)
//console.log("PARAMVAL: ", param.value)
if (param.name === "startnode" && param.value !== undefined && param.value !== null) {
//console.log("SHOULD SET STARTNODE IN SUBFLOW SELECTION: ", outersub)
//const innernode = outersub.actions.find(action => action.id === param.value)
//console.log("FOUND NODE: ", innernode)
//if (innernode !== undefined && subworkflowStartnode.id !== innernode.id) {
//setSubworkflowStartnode(JSON.parse(JSON.stringify(param.value)))
setSubworkflowStartnode(param.value)
//}
/*
const sub = responseJson.find(data => data.id === param.value)
setSubworkflow(sub)
}
*/
}
}
}
}
}
})
.catch(error => {
alert.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(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.apikey === undefined || responseJson.apikey.length === 0 || responseJson.apikey === null) {
generateApikey()
}
setUserSettings(responseJson)
})
.catch(error => {
console.log(error)
});
}
const setNewAppAuth = (appAuthData) => {
console.log("DAta: ", 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())
})
}
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)
//console.log("NEWKEYS: ", newkeys)
//setWorkflowExecutions(responseJson)
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) {
setExecutionData(execution)
setExecutionModalView(1)
start()
setExecutionRequest({
"execution_id": execution.execution_id,
"authorization": execution.authorization,
})
const newitem = removeParam("execution_id", cursearch)
props.history.push(curpath+newitem)
}
}
}
//alert.info("Loaded executions")
//setWorkflowExecutions(responseJson)
})
.catch(error => {
alert.error(error.toString())
});
}
const debugView = workflowExecutions.length > 0 ?
{workflowExecutions.slice(0,15).map((data, index) => {
return (
{new Date(data.started_at*1000).toISOString()}
, {data.status}
{data.result.length > 0 ? ", "+data.result : ", "}
{data.execution_argument.length > 0 ? ", "+data.execution_argument : ", "}
)
return
})}
: null
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) => {
handleUpdateResults(responseJson, executionRequest)
})
.catch(error => {
console.log("Error: ", error)
//alert.error(error.toString())
stop()
})
}
const abortExecution = () => {
setExecutionRunning(false)
//alert.info("Aborting execution")
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!")
} else {
//alert.success("Execution aborted")
}
return response.json()
})
.catch(error => {
alert.error(error.toString())
});
}
// Controls the colors and direction of execution results.
// Style is in defaultCytoscapeStyle.js
const handleUpdateResults = (responseJson, executionRequest) => {
//console.log(responseJson)
// Loop nodes and find results
// Update on every interval? idk
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.length !== responseJson.results.length) {
//console.log("Updated state with this data:")
//console.log(responseJson)
//console.log(executionData)
setExecutionData(responseJson)
} else {
console.log("NOT updating state.")
}
}
} else {
//console.log("JSON is same")
}
//console.log("PRE LOOPING RESULTS: !", responseJson.execution_id, executionRequest.execution_id)
if (responseJson.execution_id !== executionRequest.execution_id) {
cy.elements().removeClass('success-highlight failure-highlight executing-highlight')
return
}
//console.log("LOOPING RESULTS!")
if (responseJson.results !== null && responseJson.results.length > 0) {
for (var key in responseJson.results) {
var item = responseJson.results[key]
var currentnode = cy.getElementById(item.action.id)
if (currentnode.length === 0) {
continue
}
currentnode = currentnode[0]
const outgoingEdges = currentnode.outgoers('edge')
const incomingEdges = currentnode.incomers('edge')
//currentnode.removeClass('success-highlight failure-highlight executing-highlight')
switch (item.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(item.action.label)) {
if (executionRunning) {
//alert.show("WAITING FOR "+item.action.label+" with result "+item.result)
visited.push(item.action.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(item.action.label)) {
if (executionRunning) {
//alert.show("Success in node "+item.action.label)
//+" with result "+item.result)
visited.push(item.action.label)
setVisited(visited)
}
}
// FIXME - add outgoing nodes to executing
//const outgoingNodes = outgoingEdges.find().data().target
if (outgoingEdges.length > 0) {
for (var 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')
}
}
// const outgoingEdges = currentnode.outgoers('edge')
}
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(item.action.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(item.action.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:
console.log("DEFAULT?")
break
}
}
}
if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status == "WAITING") {
stop()
if (executionRunning) {
setExecutionRunning(false)
}
var curelements = cy.elements()
for (var i = 0; i < curelements.length; i++) {
if (curelements[i].classes().includes("executing-highlight")) {
curelements[i].removeClass("executing-highlight")
curelements[i].addClass("failure-highlight")
} else {
//curelements[i].removeClass('not-executing-highlight')
//curelements[i].removeClass('executing-highlight')
//curelements[i].removeClass('success-highlight')
//curelements[i].removeClass('awaiting-data-highlight')
//curelements[i].removeClass('failure-highlight')
}
}
getWorkflowExecution(props.match.params.key, "")
} else if (responseJson.status === "FINISHED") {
//console.log("STOPPING BECAUSE ITS OVAH!")
setExecutionRunning(false)
stop()
getWorkflowExecution(props.match.params.key, "")
setUpdate(Math.random())
} else {
//console.log("Nothing to update")
}
}
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
} else {
//alert.info("Saving workflow")
}
var cyelements = cy.elements()
var newActions = []
var newTriggers = []
var newBranches = []
var newVBranches = []
for (var key in cyelements) {
if (cyelements[key].data === undefined) {
continue
}
var type = cyelements[key].data()["type"]
if (type === undefined) {
if (cyelements[key].data().source === undefined ||
cyelements[key].data().target === undefined) {
continue
}
var parsedElement = {
id: cyelements[key].data().id,
source_id: cyelements[key].data().source,
destination_id: cyelements[key].data().target,
conditions: cyelements[key].data().conditions,
decorator: cyelements[key].data().decorator,
}
if (parsedElement.decorator) {
newVBranches.push(parsedElement)
} else {
newBranches.push(parsedElement)
}
} else {
if (type === "ACTION") {
const cyelement = cyelements[key].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[key].data()
}
curworkflowAction.position = cyelements[key].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[key].data().example !== undefined) {
curworkflowAction.example = cyelements[key].data().example
}
}
// Override just in this place
curworkflowAction.errors = []
curworkflowAction.isValid = true
// Cleans up OpenAPI items
var newparams = []
for (var key in curworkflowAction.parameters) {
const thisitem = curworkflowAction.parameters[key]
if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) {
continue
}
newparams.push(thisitem)
}
curworkflowAction.parameters = newparams
newActions.push(curworkflowAction)
} else if (type === "TRIGGER") {
//console.log("TRIGGER")
var curworkflowTrigger = useworkflow.triggers.find(a => a.id === cyelements[key].data()["id"])
if (curworkflowTrigger === undefined) {
curworkflowTrigger = cyelements[key].data()
}
curworkflowTrigger.position = cyelements[key].position()
//console.log(curworkflowTrigger)
newTriggers.push(curworkflowTrigger)
} else {
alert.info("No handler for type: "+type)
}
}
}
useworkflow.actions = newActions
useworkflow.triggers = newTriggers
useworkflow.branches = newBranches
useworkflow.visual_branches = newVBranches
// Errors are backend defined
useworkflow.errors = []
useworkflow.previously_saved = true
setLastSaved(true)
fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(useworkflow),
credentials: "include",
})
.then((response) => {
setSavingState(0)
if (response.status !== 200) {
console.log("Status not 200 for setting workflows :O!")
}
return response.json()
})
.then((responseJson) => {
if (executionArgument !== undefined && startNode !== undefined) {
console.log("Running execution AFTER saving")
executeWorkflow(executionArgument, startNode, true)
return
}
if (!responseJson.success) {
console.log(responseJson)
alert.error("Failed to save: "+responseJson.reason)
} else {
if (responseJson.new_id !== undefined && responseJson.new_id !== null) {
window.location.pathname = "/workflows/"+responseJson.new_id
}
success = true
if (responseJson.errors !== undefined) {
//console.log(responseJson)
workflow.errors = responseJson.errors
if (responseJson.errors.length === 0) {
workflow.isValid = true
workflow.is_valid = true
//console.log("ELEMENTS: ", cy.elements())
//const setupGraph = () => {
const cyelements = cy.elements()
for (var i = 0; i < cyelements.length; i++) {
cyelements[i].removeStyle()
cyelements[i].data().is_valid = true
cyelements[i].data().errors = []
}
for (var key in workflow.actions) {
workflow.actions[key].is_valid = true
workflow.actions[key].errors = []
}
}
for (var key in workflow.errors) {
//console.log("Error: ", workflow.errors[key])
alert.info(workflow.errors[key])
}
setWorkflow(workflow)
}
//alert.success("Successfully saved workflow")
setSavingState(1)
setTimeout(() => {
setSavingState(0)
}, 1500);
}
})
.catch(error => {
setSavingState(0)
alert.error(error.toString())
});
return success
}
const monitorUpdates = () => {
var firstnode = cy.getElementById(workflow.start)
if (firstnode.length === 0) {
var found = false
for (var key in workflow.actions) {
if (workflow.actions[key].isStartNode) {
console.log("Updating startnode")
workflow.start = workflow.actions[key].id
firstnode = cy.getElementById(workflow.actions[key].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) => {
if (hasSaved === false) {
//alert.error("You might have forgotten to save before executing.")
//const saveWorkflow = (curworkflow) => {
//setExecutionRunning(true)
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 (var i = 0; i < curelements.length; i++) {
curelements[i].addClass("not-executing-highlight")
}
if (executionArgument !== undefined && executionArgument !== null && executionArgument.length > 0) {
//alert.success("Starting execution WITH an execution argument")
} else {
//alert.success("Starting execution")
}
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) => {
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 (var 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 (var i = 0; i < curelements.length; i++) {
curelements[i].removeClass("not-executing-highlight")
}
return
}
setExecutionRequest({
"execution_id": responseJson.execution_id,
"authorization": responseJson.authorization,
})
setExecutingNodes([workflow.start])
setExecutionData({})
setExecutionModalOpen(true)
setExecutionModalView(1)
start()
})
.catch(error => {
alert.error(error.toString())
});
}
//const handleAppVersioning = (apps) => {
// var newapps = []
// for (var key in apps) {
// var item = apps[key]
// const previtem = newapps.findIndex(data => data.name === item.name)
// if (previtem === -1) {
// item["versions"] = [item.app_version]
// newapps.push(item)
// continue
// }
// // THere might be duplicates for some reason..
// if (!newapps[previtem]["versions"].includes(item.app_version)) {
// newapps[previtem]["versions"].push(item.app_version)
// }
// }
//
// // FIXME - handle this, as we can't have more than one of each :)
// //setVersionedApps(newapps)
//}
// Builtin actions that should ran in Worker and not apps
const getExtraApps = () => {
const data = [{
name: "Filter",
is_valid: true,
id: "0ca8887e-b4af-4e3e-887c-87e9d3bc3d3e",
link: "https://shuffler.io",
app_version: "1.0.0",
generated: true,
downloaded: false,
sharing: false,
verified: false,
tested: false,
owner: "",
private_id: "",
description: "Filter",
environment: "Shuffle",
small_image: "",
large_image: "",
contact_info: {name: "", url: ""},
authentication: {required: false, parameters: [],},
actions: [{
description: "Filter cases",
id: "",
name: "filter_cases",
node_type: "action",
environment: "Shuffle",
sharing: false,
private_id: "",
app_id: "",
authentication: null,
tested: true,
parameters: [{
description: "",
id: "",
name: "Field to look for",
example: "$testing_1.#.id",
value: "",
multiline: true,
action_field: "",
variant: "",
required: true,
schema: {type: "string"},
}]
}],
}]
return data
}
// This can be used to only show prioritzed ones later
// Right now, it can prioritize authenticated ones
//"Testing",
const internalIds = [
"Shuffle Tools",
"http",
]
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 apps :O!")
}
return response.json()
})
.then((responseJson) => {
if (responseJson.success) {
var newauth = []
for (var key in responseJson.data) {
if (responseJson.data[key].defined === false) {
continue
}
newauth.push(responseJson.data[key])
}
//{selectedAction.authentication.map(data => {
if (cy !== undefined) {
console.log("NEW AUTH = reset cy's onnodeselect")
// 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))
}
setAppAuthentication(newauth)
setAuthLoaded(true)
if (updateAction === true) {
//console.log("Should update authentication for selectedAction!!")
//console.log(responseJson)
if (selectedApp.authentication.required) {
//console.log("App requires auth!!")
// Setup auth here :)
var appUpdates = false
const authenticationOptions = []
var findAuthId = ""
if (selectedAction.authentication_id !== null && selectedAction.authentication_id !== undefined && selectedAction.authentication_id.length > 0) {
findAuthId = selectedAction.authentication_id
}
var tmpAuth = JSON.parse(JSON.stringify(responseJson.data))
//console.log("FOUND AUTH: ", tmpAuth)
//console.log("Checking authentication: ", tmpAuth)
var latest = 0
for (var key in tmpAuth) {
var item = tmpAuth[key]
const newfields = {}
for (var 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 (var key in workflow.actions) {
//console.log(workflow.actions[key].app_name)
if (workflow.actions[key].app_name == selectedApp.name) {
//console.log("Setting auth at: ", workflow.actions[key], item.id)
workflow.actions[key].selectedAuthentication = item
workflow.actions[key].authentication_id = item.id
appUpdates = true
//if (workflow.actions[key].selectedAuthentication === undefined || workflow.actions[key].selectedAuthentication === null || workflow.actions[key].selectedAuthentication.length === 0) {
// console.log("Setting inner auth: ", workflow.actions[key])
//}
}
}
}
}
}
//if (item.id === findAuthId) {
// selectedAction.selectedAuthentication = item
//}
//console.log("ACTION: ", selectedAction)
//console.log("OPTIONS: ", authenticationOptions)
selectedAction.authentication = authenticationOptions
//console.log("Authentication: ", authenticationOptions)
if (selectedAction.selectedAuthentication === null || selectedAction.selectedAuthentication === undefined || selectedAction.selectedAuthentication.length === "") {
selectedAction.selectedAuthentication = {}
}
if (appUpdates === true) {
setAuthenticationModalOpen(false)
setSelectedAction(selectedAction)
setWorkflow(workflow)
saveWorkflow(workflow)
alert.info("Added and updated authentication!")
} else {
alert.error("Failed to find new authentication - did it work?")
}
} else {
alert.info("No authentication to update")
}
}
} else {
setAuthLoaded(true)
//alert.error("Failed getting authentications")
}
})
.catch(error => {
setAuthLoaded(true)
alert.error("Auth loading 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 :O!")
}
return response.json()
})
.then((responseJson) => {
// FIXME - handle versions on left bar
//handleAppVersioning(responseJson)
//var tmpapps = []
//tmpapps = tmpapps.concat(getExtraApps())
//tmpapps = tmpapps.concat(responseJson)
console.log("APPS: ", responseJson)
setApps(responseJson)
if (isCloud) {
setFilteredApps(responseJson.filter(app => !internalIds.includes(app.name)))
setPrioritizedApps(responseJson.filter(app => internalIds.includes(app.name)))
} else {
setFilteredApps(responseJson.filter(app => !internalIds.includes(app.name) && !(!app.activated && app.generated)))
setPrioritizedApps(responseJson.filter(app => internalIds.includes(app.name)))
}
})
.catch(error => {
alert.error(error.toString())
});
}
const getWorkflow = (workflow_id, sourcenode) => {
console.log(`Getting workflow ${workflow_id} with append value ${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.public) {
alert.info("This workflow is public. You will have to save it to make it your own!")
//setLastSaved(false)
}
// Appends SUBFLOWS. Does NOT run during normal grabbing of workflows.
if (sourcenode.id !== undefined) {
console.log("WORKFLOW: ", responseJson)
var nodefound = false
const target = sourcenode.parameters.find(item => item.name === "startnode")
console.log(sourcenode.parameters)
console.log(target)
const target_id = target === undefined ? "" : target.value
const actions = responseJson.actions.map(action => {
const node = {
group: "nodes",
}
// Set it dynamically?
node.position = {
x: sourcenode.position.x+action.position.x,
y: sourcenode.position.y+action.position.y,
}
node.data = action
node.data._id = action["id"]
node.data.type = "ACTION"
node.data.source_workflow = responseJson.id
if (action.id === target_id) {
nodefound = true
}
var example = ""
if (action.example !== undefined && action.example !== null && action.example.length > 0) {
example = action.example
}
node.data.example = example
return node;
})
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 = actions.findIndex(action => action.data.id === branch.source_id)
if (sourceFound < 0) {
return null
}
const destinationFound = actions.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,
}
return edge;
})
//console.log("Adding node: ", node)
//cy.on('add', 'node', (e) => onNodeAdded(e))
edges = edges.filter(edge => edge !== null)
cy.removeListener('add')
cy.add(actions)
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.zoom(2.0)
cy.on('add', 'node', (e) => onNodeAdded(e))
cy.on('add', 'edge', (e) => onEdgeAdded(e))
//for (var key in
} else {
setWorkflow(responseJson)
setWorkflowDone(true)
//console.log(responseJson)
// 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 !== responseJson.errors.length > 0)))) {
setConfigureWorkflowModalOpen(true)
}
}
}
})
.catch(error => {
alert.error(error.toString())
});
}
const onUnselect = (event) => {
console.time("UNSELECT")
const nodedata = event.target.data()
if (nodedata.app_name === undefined && nodedata.source === undefined) {
return
}
event.target.removeClass("selected")
// 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)
setSelectedAction({})
setSelectedApp({})
setSelectedTrigger({})
//setSelectedEdge({})
// setSelectedTriggerIndex(-1)
//setSelectedActionEnvironment({})
setSelectedEdge({})
//setTriggerAuthentication({})
//setSelectedTriggerIndex(-1)
//setTriggerFolders([])
// Can be used for right side view
setRightSideBarOpen(false)
setScrollConfig({
top: 0,
left: 0,
selected: "",
})
console.timeEnd("UNSELECT")
}
const onEdgeSelect = (event) => {
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) {
*/
//console.log(event.target.data())
if (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())
}
/*
} else {
//alert.info("Can't edit branches from triggers")
console.log("IN HERE: !", triggercheck)
}
*/
setSelectedAction({})
setSelectedTrigger({})
}
// Comparing locations between nodes and setting views
var styledElements = []
var originalLocation = {
x: 0,
y: 0,
}
const onCtxTap = (event) => {
const nodedata = event.target.data()
console.log(nodedata)
if (nodedata.type === "TRIGGER" && nodedata.app_name === "Shuffle Workflow") {
if (nodedata.parameters === null) {
alert.error("Set a workflow first")
return
}
const workflow_id = nodedata.parameters.find(param => param.name === "workflow")
if (workflow.id === workflow_id.valu) {
return
}
cy.animation({
zoom: 0,
center: {
eles: event.target,
},
})
.play()
.promise()
.then( () => {
console.log("DONE: ", workflow_id)
//cy.elements().remove()
getWorkflow(workflow_id.value, nodedata)
cy.fit(null, 50)
//props.match.params.key
//cy.animation({
// zoom: 500,
// center: {
// eles: cy.nodes(),
// },
//})
//.play()
//.promise()
})
//const animationDuration = 150
//style: {
//maxZoom={2.00}
//cy.animate({
// fit: 100,
//}, {
// duration: 500,
//})
}
}
//cy.on('cxttap', "node", (e) => onCtxTap(e))
var hiddenNodes = []
const onNodeDragStop = (event, selectedAction) => {
const nodedata = event.target.data()
//console.log("IN NODE DRAG STOP: ", nodedata)
if (nodedata.id === selectedAction.id) {
return
}
if (nodedata.finished === false) {
return
}
//console.log("Drag: ", nodedata)
//return
//console.log("DRAGGED NODE: ", nodedata)
//console.log("TARGET NODE: ", selectedAction)
if (styledElements.length === 1) {
console.log("Should reset location and autofill: ", styledElements, selectedAction)
//event.target.position = originalLocation
//curworkflowTrigger.position = cyelements[key].position()
if (originalLocation.x !== 0 || originalLocation.y !== 0) {
const currentnode = cy.getElementById(nodedata.id)
if (currentnode !== null && currentnode !== undefined) {
//currentnode.position = originalLocation
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) {
//console.log("ELE: ", curElement)
curElement.style.border = curElement.style.original_border
var newValue = "$"+nodedata.label.toLowerCase().replaceAll(" ", "_")
var paramname = ""
var idnumber = -1
if (curElement.id.startsWith("rightside_field_")) {
console.log("FOUND FIELD WITH NUMBER: ", curElement.id)
const idsplit = curElement.id.split("_")
console.log(idsplit)
if (idsplit.length === 3 && !isNaN(idsplit[2])) {
console.log("ADDING TO PARAM ", idsplit[2])
console.log("PARAM: ", selectedAction)
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
}
}
const skipnames = []
if (nodedata.app_name !== undefined && ((
nodedata.app_name !== "Shuffle Tools" &&
nodedata.app_name !== "Testing" &&
nodedata.app_name !== "Shuffle Workflow" &&
nodedata.app_name !== "User Input" &&
nodedata.app_name !== "Webhook" &&
nodedata.app_name !== "Schedule" &&
nodedata.app_name !== "Email") || nodedata.isStartNode)
) {
const allNodes = cy.nodes().jsons()
var found = false
for (var key in allNodes) {
const currentNode = allNodes[key]
if (currentNode.data.attachedTo === nodedata.id && currentNode.data.isDescriptor) {
found = true
console.log("FOUND THE NODE!")
break
}
}
// Readding the icon after moving the node
if (!found) {
//console.log("Node wasn't 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
//console.log(event.target.position())
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")
}
} else {
//console.log("Shouldnt re-add info? ")
}
originalLocation = {
x: 0,
y: 0,
}
}
const onNodeDrag = (event, selectedAction) => {
//if (Object.getOwnPropertyNames(selectedAction).length === 0) {
// return
//}
const nodedata = event.target.data()
//console.log("Dragging: ", nodedata)
if (nodedata.finished === false) {
return
}
//console.log("Dragging node!!")
if (nodedata.app_name == "Shuffle Tools" || nodedata.app_name == "Testing") {
//console.log("NODE: ",
//selector: `node[app_name="Shuffle Tools"]`,
//console.log(event.target)
// 1. Find location of node
// 2. Check if it's within view of another node (inside)
// 3. If it is, then hide text
}
if (nodedata.app_name !== undefined) {
//console.log("Trying to remove friendly nodes")
const allNodes = cy.nodes().jsons()
for (var key in allNodes) {
const currentNode = allNodes[key]
if (currentNode.data.attachedTo === nodedata.id) {
cy.getElementById(currentNode.data.id).remove()
}
}
} else {
console.log("No appid? ", nodedata)
}
if (nodedata.id === selectedAction.id) {
return
}
if (originalLocation.x === 0 && originalLocation.y === 0 && nodedata.position !== undefined) {
//console.log("Updating location!: ", nodedata)
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")) {
//console.log(elementMouseIsOver.style.border)
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 key in styledElements) {
const curElement = document.getElementById(styledElements[key])
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 key in styledElements) {
const curElement = document.getElementById(styledElements[key])
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);
/*
event.target.animate({
style: {
"border-width": "12px",
"border-opacity": ".7",
}
}, {
duration: animationDuration,
})
event.target.animate({
style: {
"border-width": "12px",
"border-opacity": ".7",
}
}, {
duration: animationDuration,
})
*/
}
// Nodeselectbatching:
// https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once
const onNodeSelect = (event, newAppAuth) => {
const data = event.target.data()
//console.log("NEW NODE ID: ", data.id)
if (data.isButton) {
//console.log("BUTTON CLICKED: ", data)
if (data.buttonType === "delete") {
//console.log("DELETE!")
const parentNode = cy.getElementById(data.attachedTo)
if (parentNode !== null && parentNode !== undefined) {
parentNode.remove()
}
//for (var key in allNodes) {
// const currentNode = allNodes[key]
// if (currentNode.data.attachedTo === data.attachedTo) {
// cy.getElementById(currentNode.data.id).remove()
// }
//}
} 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')
parentNode.data('isStartNode', true)
}
} 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) {
//parentNode.data()
var newNodeData = JSON.parse(JSON.stringify(parentNode.data()))
newNodeData.id = uuidv4.v4()
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
//console.log("Node wasn't found")
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
//console.log(event.target.position())
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 key in sourcebranches) {
// var newbranch = JSON.parse(JSON.stringify(sourcebranches[key]))
// newbranch.id = uuidv4()
// newbranch.source_id = newNodeData.id
// cy.add({
// group: "edges",
// data: newbranch,
// })
//}
for (var key in sourcebranches) {
var newbranch = JSON.parse(JSON.stringify(sourcebranches[key]))
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 key in destinationbranches) {
var newbranch = JSON.parse(JSON.stringify(destinationbranches[key]))
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
} else if (data.isDescriptor) {
console.log("Can't select descriptor")
event.target.unselect()
return
}
//const node = cy.getElementById(data.id)
//if (node.length > 0) {
// node.addClass('shuffle-hover-highlight')
//}
//const branch = workflow.branches.filter(branch => branch.source_id === data.id || branch.destination_id === data.id)
//console.log("APPAUTH: ", newAppAuth)
//console.log("BRANCHES: ", branch)
//console.log("CLICK: ", data)
if (data.type === "ACTION") {
//setSelectedApp({})
//setSelectedAction({})
var curaction = workflow.actions.find(a => a.id === data.id)
//console.log("PARAMS: ", curaction.parameters[0])
//console.log("INSIDE CURACTION: ", curaction)
if (!curaction || curaction === undefined) {
//event.target.unselect()
alert.error("Action not found. Please remake it.")
return
}
const curapp = apps.find(a => a.name === curaction.app_name && ((a.app_version === curaction.app_version || (a.loop_versions !== null && a.loop_versions.includes(curaction.app_version)))))
//console.log("APP: ", curapp)
if (!curapp || curapp === undefined) {
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],
}
//console.log(tmpapp)
//console.log(curaction)
setSelectedApp(tmpapp)
//setSelectedAction(JSON.parse(JSON.stringify(curaction)))
setSelectedAction(curaction)
//return
} else {
//console.log("AUTHENTICATION: ", curapp.authentication)
//console.log(curapp.authentication)
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))
//console.log("AUTHCHECK: ", requiresAuth)
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
}
var tmpAuth = JSON.parse(JSON.stringify(newAppAuth))
//console.log("FOUND AUTH: ", tmpAuth)
//console.log("Checking authentication: ", tmpAuth)
for (var key in tmpAuth) {
var item = tmpAuth[key]
const newfields = {}
for (var filterkey in item.fields) {
newfields[item.fields[filterkey].key] = item.fields[filterkey].value
}
item.fields = newfields
if (item.app.name === curapp.name) {
authenticationOptions.push(item)
if (item.id === findAuthId) {
curaction.selectedAuthentication = item
}
}
}
//console.log("OPTIONS: ", authenticationOptions)
curaction.authentication = authenticationOptions
//console.log("Authentication: ", authenticationOptions)
if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") {
curaction.selectedAuthentication = {}
}
} else {
curaction.authentication = []
curaction.authentication_id = ""
curaction.selectedAuthentication = {}
}
//setSelectedAction(JSON.parse(JSON.stringify(curaction)))
//console.log("CURAPP: ", curapp, selectedApp)
//console.log("ACTION: ", curaction)
if (curaction.parameters !== undefined && curaction.parameters !== null && curaction.parameters.length > 0) {
//console.log("params: ", curaction.parameters)
for (var key in curaction.parameters) {
if (curaction.parameters[key].options !== undefined && curaction.parameters[key].options !== null && curaction.parameters[key].options.length > 0 && curaction.parameters[key].value == "") {
curaction.parameters[key].value = curaction.parameters[key].options[0]
}
}
}
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) {
var env = environments.find(a => a.Name === curaction.environment)
if (!env || env === undefined) {
env = environments[defaultEnvironmentIndex]
}
setSelectedActionEnvironment(env)
}
} else if (data.type === "TRIGGER") {
//console.log("Should handle trigger "+data.triggertype)
//console.log(data)
const trigger_index = workflow.triggers.findIndex(a => a.id === data.id)
if (data.app_name === "Shuffle Workflow") {
getAvailableWorkflows(trigger_index)
getSettings()
} else if (data.app_name === "Webhook") {
if (workflow.triggers[trigger_index].parameters !== undefined) {
workflow.triggers[trigger_index].parameters[0] = {"name": "url", "value": referenceUrl+"webhook_"+selectedTrigger.id}
}
}
setSelectedTriggerIndex(trigger_index)
setSelectedTrigger(data)
setSelectedActionEnvironment(data.env)
} else {
alert.error("Can't handle "+data.type)
}
//console.log("BAR: ", rightSideBarOpen, "SAVE: ", lastSaved)
setRightSideBarOpen(true)
setLastSaved(false)
// Refresh listeners
//cy.removeListener('select')
//cy.on('select', 'node', (e) => onNodeSelect(e, newAppAuth, curapp, rightSideBarOpen, lastSaved))
//cy.on('select', 'edge', (e) => onEdgeSelect(e))
setScrollConfig({
top: 0,
left: 0,
selected: "",
})
}
const GetExampleResult = (item) => {
var exampledata = item.example === undefined ? "" : item.example
if (workflowExecutions.length > 0) {
// Look for the ID
const found = false
for (var key in workflowExecutions) {
if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) {
continue
}
var foundResult = {"result": ""}
if (item.id === "exec") {
//console.log("EXEC: ", workflowExecutions[key].execution_argument)
if (workflowExecutions[key].execution_argument !== undefined && workflowExecutions[key].execution_argument !== null && workflowExecutions[key].execution_argument.length > 0) {
foundResult.result = workflowExecutions[key].execution_argument
} else {
continue
}
} else {
foundResult = workflowExecutions[key].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 {
const tmp = String(JSON.parse(foundResult.result))
if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) {
jsonvalid = false
}
} catch (e) {
try {
foundResult.result = foundResult.result.split("\'").join("\"")
const tmp = String(JSON.parse(foundResult.result))
if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) {
jsonvalid = false
}
} catch (e) {
jsonvalid = false
}
}
// Finds the FIRST json only
if (jsonvalid) {
//console.log("VALID!")
exampledata = JSON.parse(foundResult.result)
break
} else {
//console.log("INVALID: ", foundResult.result)
}
}
}
//console.log("EXAMPLE: ", exampledata)
return exampledata
}
const GetParamMatch = (paramname, exampledata, basekey) => {
const splitkey = "."
//console.log(typeof(exampledata))
//console.log("MATCHING WITH: ", exampledata)
if (typeof(exampledata) !== "object") {
return ""
}
if (exampledata === null) {
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"],
"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",],
}
// 1. Find the right synonym
// 2.
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
}
}
//console.log("SELECTED: ", selectedsynonyms)
//console.log("SYNONYMS FOR ", paramname, selectedsynonyms)
var toreturn = ""
for (const [key, value] of Object.entries(exampledata)) {
// Check if loop or JSON
const extra = basekey.length > 0 ? splitkey : ""
const basekeyname = `${basekey.slice(1, basekey.length).split(".").join(splitkey)}${extra}${key}`
// Handle direct loop!
//if (!isNaN(key) && basekey === "") {
// //console.log("Handling direct loop: ", key, value)
// //parsedValues.push({"type": "object", "name": "Node", "autocomplete": `${basekey}`})
// //parsedValues.push({"type": "list", "name": `${splitkey}list`, "autocomplete": `${basekey}.#`})
// //for (var subkey in returnValues) {
// // parsedValues.push(returnValues[subkey])
// //}
// toreturn = GetParsedPaths(paramname, value, `${basekey}.#`)
// console.log("LIST, TORETURN: ", value, toreturn)
// if (toreturn.length > 0) {
// break
// }
//}
//console.log("KEY: ", key, "VALUE: ", value, "BASEKEY: ", basekeyname)
if (typeof(value) === 'object') {
if (Array.isArray(value)) {
//console.log("LIST!!: ", value, key)
var selectedkey = ""
if (isNaN(key)) {
selectedkey = `.${key}`
}
for (var subitem in 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}`)
//console.log("OBJECT: ", value, toreturn, key)
if (toreturn.length > 0) {
break
}
}
//console.log("VALUE IS OBJECT: ", key, value)
} else {
//console.log("SINGLE ITEM: ", key)
if (selectedsynonyms.includes(key.toLowerCase())) {
//parsedValues.push({"type": "value", "name": basekeyname, "autocomplete": `${basekey}.${key}`, "value": value,})
//console.log("STRING: ", key, value)
toreturn = `${basekey}.${key}`
//toreturn = basekeyname
break
}
}
}
return toreturn
}
// Takes an action as input, then runs through and updates the relevant fields
// based on previous actions'
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",})
//console.log("EXAMPLE RETURN: ", exampledata)
var parentlabel = "exec"
for (var paramkey in dstdata.parameters) {
const param = dstdata.parameters[paramkey]
// Skip authentication params
if (param.configuration) {
continue
}
const paramname = param.name.toLowerCase().trim().replaceAll("_", " ")
//console.log("PARAM: ", param)
//console.log("PARAMNAME: ", paramname)
const foundresult = GetParamMatch(paramname, exampledata, "")
if (foundresult.length > 0) {
//console.log("FOUND: ", paramname, foundresult)
if (dstdata.parameters[paramkey].value.length === 0) {
dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`
} else {
//console.log("Skipping ", dstdata.parameters[paramkey], " because it already has a value")
}
}
}
var parents = getParents(dstdata)
//console.log("PARENTS: ", parents)
if (parents.length > 1) {
for (var key in parents) {
const item = parents[key]
if (item.label === "Execution Argument") {
continue
}
parentlabel = item.label === undefined ? "" : item.label.toLowerCase().trim().replaceAll(" ", "_")
exampledata = GetExampleResult(item)
for (var paramkey in dstdata.parameters) {
const param = dstdata.parameters[paramkey]
// Skip authentication params
if (param.configuration) {
continue
}
const paramname = param.name.toLowerCase().trim().replaceAll("_", " ")
//console.log("PARAM: ", param)
//console.log("PARAMNAME: ", paramname)
const foundresult = GetParamMatch(paramname, exampledata, "")
if (foundresult.length > 0) {
//console.log("FOUND: ", paramname, foundresult)
if (dstdata.parameters[paramkey].value.length === 0) {
dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`
} else {
dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`
//console.log("Skipping ", dstdata.parameters[paramkey], " because it already has a value")
}
}
}
// Check agains every param
}
}
return dstdata
}
//const FixNameUpdater = (sourcenode) => {
//}
// Checks for errors in edges when they're added
const onEdgeAdded = (event) => {
const edge = event.target.data()
//console.log("EDGE ADDED: ", edge)
//setLastSaved(false)
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()
}
}
//console.log("TARGET: ", event.target.target().data())
if (event.target.target().data("isButton") === true || event.target.target().data("isDescriptor") === true) {
event.target.remove()
return
}
targetnode = -1
var sourcenode = workflow.triggers.findIndex(data => data.id === edge.source)
//console.log("SOURCENODE: ", sourcenode)
if (sourcenode !== -1) {
if (workflow.triggers[sourcenode].app_name === "User Input" || workflow.triggers[sourcenode].app_name === "Shuffle Workflow") {
//console.log("NORMAL TRIGGER")
} else {
//var currentnode = cy.getElementById(workflow.triggers[sourcenode].id)
//console.log("NODE: ", currentnode)
//if (currentnode !== null && currentnode !== undefined) {
// console.log("SHOULD CHECK IF TRIGGER HAS MULTIPLE EDGES: ", currentnode)
//if (workflow.branches !== undefined && workflow.branches !== null) {
// const found_branches = workflow.branches.filter(branch => branch.source == workflow.triggers[sourcenode].id)
// console.log("FOUND BRANCHES: ", found_branches)
// if (found_branches.length > 0) {
// alert.error("Can't have multiple branches from this trigger")
// event.target.remove()
// }
//}
//if (cy.edges().size() === 1) {
// https://js.cytoscape.org/#edges.connectedNodes
//console.log("CURRENTNODE: ", currentnode)
//console.log("EDGES: ", currentnode.connectedEdges(`node[id=${workflow.triggers[sourcenode].id}]`))
//console.log("EDGES2: ", currentnode.connectedEdges())
//currentnode.connectedEdges().animate({style: {lineColor: "red"}})
//console.log("OUTGOERS: ", currentnode.outgoers())
//console.log("LEN2: ", currentnode.edges().length)
//if (currentnode.connectedNodes().length > 0) {
// alert.error("Can't have multiple branches from this trigger")
// event.target.remove()
//}
}
}
//console.log(workflow.branches)
// Check if:
// dest == source && source == dest
// dest == dest && source == source
// backend: check all children? to stop recursion
var found = false
for (var key in workflow.branches) {
if (workflow.branches[key].destination_id === edge.source && workflow.branches[key].source_id === edge.target) {
alert.error("A branch in the opposite direction already exists")
event.target.remove()
found = true
break
} else if (workflow.branches[key].destination_id === edge.target && workflow.branches[key].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) {
alert.error("Can't make arrow to starting node")
event.target.remove()
found = true
break
}
} else if (edge.source === workflow.branches[key].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 srcdata = newsource.data()
//console.log("EDGE: ", edge)
const dstdata = RunAutocompleter(newdst.data())
console.log("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 (nodedata.finished === false || (nodedata.id !== undefined && nodedata.is_valid === undefined)) {
//console.log("RETURNING (NOT ADDING) NODE ADD FOR: ", nodedata)
return
}
//console.log("IS IT ADDED TO THE WORKFLOW?: ", nodedata)
if (node.isNode() && cy.nodes().size() === 1) {
//setStartNode(node.data('id'))
workflow.start = node.data('id')
nodedata.isStartNode = true
} else {
if (workflow.actions === null) {
return
}
// Remove bad startnode
const startnode_exists = false
for (var key in workflow.actions) {
const action = workflow.actions[key]
if (action.isStartNode && workflow.start !== action.id) {
action.isStartNode = false
}
}
}
if (nodedata.type === "ACTION") {
/*
const tmpView = localStorage.getItem(appSorter)
const nodekey = nodedata.id
if (tmpView === null || tmpView === undefined) {
const basedata = {}
basedata[nodekey] = 1
localStorage.setItem(appSorter, JSON.stringify(basedata))
} else {
try {
var parsed = JSON.parse(tmpView)
try {
parsed[nodekey] += 1
} catch {
parsed[nodekey] = 1
}
localStorage.setItem(appSorter, JSON.stringify(parsed))
} catch {
console.log("Bad data in tmpView: ", tmpView)
}
}
console.log("FAVORITeS: ", tmpView)
*/
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 (var subkey in nodedata.parameters) {
var newparam = JSON.parse(JSON.stringify(nodedata.parameters[subkey]))
newparam.id = uuidv4()
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") {
//workflow.branches.push(newbranch)
if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
cy.add(edgeToBeAdded)
}
}
//if (data.trigger_type === "WEBHOOK") {
// newWebhook(newAppData)
// saveWorkflow(workflow)
//}
setWorkflow(workflow)
}
if (nodedata.app_name !== undefined) {
history.push({
"type": "node",
"action": "added",
"data": nodedata,
})
setHistory(history)
setHistoryIndex(history.length)
}
//if (nodedata.app_name !== undefined && ((
// nodedata.app_name !== "Shuffle Tools" &&
// nodedata.app_name !== "Testing" &&
// nodedata.app_name !== "Shuffle Workflow" &&
// nodedata.app_name !== "User Input" &&
// nodedata.app_name !== "Webhook" &&
// nodedata.app_name !== "Schedule" &&
// nodedata.app_name !== "Email") || nodedata.isStartNode)
//) {
// const allNodes = cy.nodes().jsons()
// var found = false
// for (var key in allNodes) {
// const currentNode = allNodes[key]
// if (currentNode.data.attachedTo === nodedata.id && currentNode.data.isDescriptor) {
// found = true
// console.log("FOUND THE NODE!")
// break
// }
// }
// // Readding the icon after moving the node
// if (!found) {
// console.log("Node wasn't 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)
// } else {
// console.log("Node already exists - don't add descriptor node")
// }
//} else {
// //console.log("Shouldnt re-add info? ")
//}
}
const onEdgeRemoved = (event) => {
setLastSaved(false)
const edge = event.target
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) {
//alert.error("Can't remove edge from a trigger")
console.log("Shouldnt remove edge from trigger")
//const edgeToBeAdded = {
// group: "edges",
// data: newcybranch,
//}
}
if (edge.data().source !== undefined) {
history.push({
"type": "edge",
"action": "removed",
"data": edge.data().source,
})
setHistory(history)
setHistoryIndex(history.length)
}
}
const onNodeRemoved = (event) => {
const node = event.target
const data = node.data()
if (data.finished === false) {
return
}
//setLastSaved(false)
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()
}
}
}
//cy.nodes().some(function( ele ) {
// if (ele.id() !== workflow.start && ele.data()["label"] !== undefined) {
// //alert.success("Changed startnode to "+ele.data()["label"])
// ele.data("isStartNode", true)
// workflow.start = ele.id()
// throw BreakException
// return false
// }
//})
}
if (data.app_name !== undefined) {
//console.log("Trying to remove friendly nodes")
const allNodes = cy.nodes().jsons()
//console.log("NOT UNDEFINED IN HOVEROUT!", allNodes)
for (var key in allNodes) {
const currentNode = allNodes[key]
if (currentNode.data.attachedTo === data.id) {
cy.getElementById(currentNode.data.id).remove()
}
}
history.push({
"type": "node",
"action": "removed",
"data": data,
})
setHistory(history)
setHistoryIndex(history.length)
}
setWorkflow(workflow)
if (data.type === "TRIGGER") {
saveWorkflow(workflow)
}
}
var previouskey = 0
const handleKeyDown = (event) => {
// SHIFT = 16
// CTRL = 17
//console.log(event.keyCode)
switch( event.keyCode ) {
case 27:
//console.log("ESCAPE")
if (configureWorkflowModalOpen === true) {
setConfigureWorkflowModalOpen(false)
}
break;
case 46:
//removeNode()
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 (previouskey === 17) {
console.log("CTRL+Z")
//handleHistoryUndo()
}
break;
case 67:
if (previouskey === 17) {
console.log("CTRL+C")
//const filteredelements = cy.filter(function(element, i){
// return element.hasClass('selected')
//})
//for (var key in filteredelements) {
//}
//var copyText = document.getElementById("copy_element_shuffle")
//if (copyText !== undefined && copyText !== null) {
// const clipboard = navigator.clipboard
// if (clipboard === undefined) {
// alert.error("Can only copy over HTTPS (port 3443)")
// return
// }
//}
//console.log("FILTERED: ", filteredelements)
}
break;
case 86:
if (previouskey === 17) {
console.log("CTRL+V")
}
break;
case 88:
if (previouskey === 17) {
console.log("CTRL+V")
}
break;
case 83:
//if (previouskey === 17) {
// event.preventDefault()
// saveWorkflow()
//}
break;
case 70:
//if (previouskey === 17) {
// event.preventDefault()
// cy.fit(null, 50)
//}
//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
//
//if (previouskey === 17) {
// event.preventDefault()
// if (executionRunning || executionRequestStarted) {
// abortExecution()
// } else {
// executeWorkflow()
// }
// cy.fit(null, 50)
//}
break;
default:
//console.log(event.keyCode)
break;
}
previouskey = event.keyCode
}
const registerKeys = () => {
document.addEventListener("keydown", handleKeyDown);
}
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 apps :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 (var key in responseJson) {
if (responseJson[key].default) {
setDefaultEnvironmentIndex(key)
found = true
}
if (responseJson[key].archived === false) {
showEnvCnt += 1
}
}
if (showEnvCnt > 1) {
setShowEnvironment(true)
}
if (!found) {
for (var key in responseJson) {
if (!responseJson[key].archived) {
setDefaultEnvironmentIndex(key)
break
}
}
}
setEnvironments(responseJson)
})
.catch(error => {
alert.error(error.toString())
});
}
if (!firstrequest && graphSetup && established && props.match.params.key !== workflow.id && workflow.id !== undefined && workflow.id !== null && workflow.id.length > 0) {
//console.log(props.match.params.key, workflow.id)
//getWorkflow()
//setCy()
//getWorkflowExecution(props.match.params.key, "")
//setEstablished(false)
//setGraphSetup(false)
window.location.pathname = "/workflows/"+props.match.params.key
}
useEffect(() => {
if (firstrequest) {
setFirstrequest(false)
getWorkflow(props.match.params.key, {})
getApps()
getAppAuthentication()
getEnvironments()
getWorkflowExecution(props.match.params.key, "")
getAvailableWorkflows(-1)
getSettings()
const cursearch = typeof window === 'undefined' || window.location === undefined ? "" : window.location.search
const tmpView = new URLSearchParams(cursearch).get("view")
if (tmpView !== undefined && tmpView !== null && tmpView === "executions") {
setExecutionModalOpen(true)
const newitem = removeParam("view", cursearch)
props.history.push(curpath+newitem)
}
return
}
// App length necessary cus of cy initialization
//console.log("PRE ELEMENTS: !", workflow.actions, graphSetup, apps, authLoaded, cy)
if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
setGraphSetup(true)
setupGraph()
//console.log("IN ELEMENT CHECK!")
} else if (!established && cy !== undefined && apps !== null && apps !== undefined && apps.length > 0 && Object.getOwnPropertyNames(workflow).length > 0 && authLoaded){
//This part has to load LAST, as it's kind of not async.
//This means we need everything else to happen first.
//
//console.log("IN THIS PART AGAIN")
//console.log("IN ESTABLISHED!")
setEstablished(true)
// Validate if the node is just a node lol
//console.log("CY: ", cy)
//console.log("CY: ", cy.edgehandles())
//try {
cy.edgehandles({
handleNodes: (el) => el.isNode() && !el.data("isButton") && !el.data("isDescriptor"),
preview: false,
toggleOffOnLeave: false,
loopAllowed: function( node ){
return false;
},
})
//} catch (e) {
// console.log("Error in edgehandles: ", e)
//}
cy.fit(null, 200)
cy.on('boxselect', 'node', (e) => {
if (e.target.data("isButton") || e.target.data("isDescriptor")) {
e.target.unselect()
}
e.target.addClass("selected")
})
cy.on('boxstart', (e) => {
console.log("START")
cy.removeListener('select')
//onNodeSelect(e, appAuthentication)
})
cy.on('boxend', (e) => {
console.log("END")
cy.removeListener('select')
//onNodeSelect(e, appAuthentication)
})
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))
//let popper2 = cy.popper({
// content: () => {
// let div = document.createElement('div');
//
// div.innerHTML = 'Popper content'
//
// document.body.appendChild(div)
//
// return div
// },
// renderedPosition: () => ({ x: 100, y: 200 }),
// popper: {} // my popper options here
//});
//let popper1 = cy.nodes()[0].popper({
// content: () => {
// let div = document.createElement('div')
//
// div.innerHTML = 'Popper content'
// document.body.appendChild(div)
//
// return div
// },
// popper: {} // my popper options here
//})
//let update = () => {
// popper.update()
//}
//let node = cy.nodes().first()
//node.on('position', update)
//cy.on('mouseover', 'node', () => $(targetElement).addClass('mouseover'));
//cy.on('cxttapstart', 'node', (e) => edgeHandler.start(e.target))
//cy.on('cxttapend', 'node', (e) => edgeHandler.stop())
//cy.on('cxtdragover', 'node', (e) => edgeHandler.preview(e.target))
//cy.on('cxtdragout', 'node', (e) => edgeHandler.unpreview(e.target))
document.title = "Workflow - "+workflow.name
registerKeys()
//setStartNode(workflow.start)
} else if (established) {
//console.log("established - should fix colors of things")
//console.log(cy.elements())
}
})
//}, [selectedAction])
var previousnodecolor = ""
//var previousedgecolor = ""
const animationDuration = 150
const onNodeHoverOut = (event) => {
const nodedata = event.target.data()
if (nodedata.app_name !== undefined) {
const allNodes = cy.nodes().jsons()
for (var key in allNodes) {
const currentNode = allNodes[key]
if (currentNode.data.isButton && currentNode.data.attachedTo !== nodedata.id) {
cy.getElementById(currentNode.data.id).remove()
}
}
}
var parsedStyle = {
"border-width": "1px",
'font-size': '18px',
}
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
}
//parentNode.lock()
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
})
//.unselectify()
}
const addCopyButton = (event) => {
var parentNode = cy.$('#' + event.target.data("id"));
if (parentNode.data('isButton') || parentNode.data('buttonId'))
return
//parentNode.lock()
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
})
//.unselectify()
}
const addDeleteButton = (event) => {
var parentNode = cy.$('#' + event.target.data("id"));
if (parentNode.data('isButton') || parentNode.data('buttonId'))
return
//parentNode.lock()
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
})
//.unselectify()
}
const onNodeHover = (event) => {
//console.log("TAR: ", event.target)
const nodedata = event.target.data()
if (nodedata.finished === false) {
return
}
var parentNode = cy.$('#' + event.target.data("id"));
if (parentNode.data('isButton') || parentNode.data('buttonId'))
return
if (nodedata.app_name !== undefined) {
const allNodes = cy.nodes().jsons()
var found = false
for (var key in allNodes) {
const currentNode = allNodes[key]
if (currentNode.data.isButton && 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)
addCopyButton(event)
addStartnodeButton(event)
}
}
const parsedStyle = {
"border-width": "7px",
"border-opacity": ".7",
'font-size': '25px',
'color': 'white',
}
event.target.animate({
style: parsedStyle
}, {
duration: animationDuration,
})
previousnodecolor = event.target.style("border-color")
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
}
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 sourcecolor = cy.getElementById(event.target.data("source")).style("border-color")
const targetcolor = cy.getElementById(event.target.data("target")).style("border-color")
if (sourcecolor !== null && sourcecolor !== undefined && targetcolor !== null && targetcolor !== undefined) {
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: 0,
})
}
}
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'
//console.log("GRADIENT!: ", action)
//action.fillstyle =
//'background-fill': 'data(fillstyle)',
} else {
action.iconBackground = iconInfo.iconBackgroundColor
}
//console.log("FOUND NODE INFO: ", action)
}
node.position = action.position
node.data = action
node.data._id = action["id"]
node.data.type = "ACTION"
node.isStartNode = action["id"] === workflow.start
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
return null
})
const triggers = workflow.triggers.map(trigger => {
const node = {}
node.position = trigger.position
node.data = trigger
node.data._id = trigger["id"]
node.data.type = "TRIGGER"
return node;
})
// FIXME - tmp branch update
var insertedNodes = [].concat(actions, triggers, decoratorNodes)
insertedNodes = insertedNodes.filter(node => node !== null)
var edges = workflow.branches.map((branch, index) => {
//workflow.branches[index].conditions = [{
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.
/*
//http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html
const sourcenode = actions.find(node => node.data._id === branch.source_id)
const destinationnode = actions.find(node => node.data._id === branch.destination_id)
if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) {
//node.data._id = action["id"]
console.log("SOURCE: ", sourcenode.position)
console.log("DESTINATIONNODE: ", destinationnode.position)
var opposite = true
if (sourcenode.position.x > destinationnode.position.x) {
opposite = false
} else {
opposite = true
}
edge.style = {
'control-point-distance': opposite ? ["25%", "-75%"] : ["-10%", "90%"],
'control-point-weight': ['0.3', '0.7'],
}
}
*/
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 key in edges) {
var item = edges[key]
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 = () => {
setSelectedApp({})
setSelectedAction({})
const selectedNode = cy.$(':selected')
if (selectedNode.data() === undefined) {
return
}
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().decorator === true) {
alert.info("This edge can't be deleted.")
} else {
selectedNode.remove()
}
setSelectedTrigger({})
setSelectedTriggerIndex({})
}
const stopSchedule = (trigger, triggerindex) => {
//alert.info("Stopping schedule")
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())
});
}
//const submitSchedule = (id, name, frequency, executionArg) => {
const submitSchedule = (trigger, triggerindex) => {
//const cronSplit = workflow.triggers[triggerindex].parameters[0].value.split("*")
//if (cronSplit.length <= 5 || cronSplit.length > 6) {
// alert.error("Error: Bad cron, example run every 1 minute: */1 * * * *")
// return
//}
if (trigger.name.length <= 0) {
alert.error("Error: name can't be empty")
return
}
alert.info("Attempting to create 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,
}
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())
});
}
const appViewStyle = {
marginLeft: 5,
marginRight: 5,
display: "flex",
flexDirection: "column",
height: "100%",
}
const paperAppStyle = {
borderRadius: theme.palette.borderRadius,
minHeight: 100,
maxHeight: 100,
minWidth: "100%",
maxWidth: "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 = (variableName) => {
console.log("Delete:" ,variableName)
workflow.workflow_variables = workflow.workflow_variables.filter(data => data.name !== variableName)
setWorkflow(workflow)
}
const deleteExecutionVariable = (variableName) => {
workflow.execution_variables = workflow.execution_variables.filter(data => data.name !== variableName)
setWorkflow(workflow)
}
const variableScrollStyle = {
margin: 15,
overflow: "scroll",
height: "66vh",
overflowX: "auto",
overflowY: "auto",
flex: "10",
}
return (
What are
WORKFLOW variables?
{workflow.workflow_variables === null ?
null : workflow.workflow_variables.map((variable, index) => {
return (
{
}}>
{
setNewVariableName(variable.name)
setNewVariableDescription(variable.description)
setNewVariableValue(variable.value)
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=> {
return (
{
}}>
{
setNewVariableName(variable.name)
setExecutionVariablesModalOpen(true)
}}>
Name: {variable.name}
)
})}
{
setExecutionVariablesModalOpen(true)
setLastSaved(false)
}}>New execution variable
)
}
const curTab = 0
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: leftBarSize/3,
minWidth: leftBarSize/3,
flex: 1,
textTransform: "none",
}
const iconStyle = {
marginTop: 3,
marginRight: 5,
}
return(
{thisview}
Apps
} style={tabStyle} />
Triggers
} style={tabStyle} />
Variables
}style={tabStyle} />
)
}
const triggers = [{
"name": "Webhook",
"type": "TRIGGER",
"status": "uninitialized",
"trigger_type": "WEBHOOK",
"errors": null,
"large_image": 'data:image/png;base64,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',
"is_valid": true,
"label": "Webhook",
"environment": "onprem",
"description": "Simple HTTP webhook",
"long_description": "Execute a workflow with an unauthicated POST request",
},
{
"name": "Schedule",
"type": "TRIGGER",
"status": "uninitialized",
"description": "Schedule execution time",
"trigger_type": "SCHEDULE",
"errors": null,
"large_image": "data:image/png;base64,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",
"label": "Schedule",
"is_valid": true,
"environment": "onprem",
"long_description": "Create a schedule based on cron",
},
{
"name": "Shuffle Workflow",
"type": "TRIGGER",
"status": "uninitialized",
"trigger_type": "SUBFLOW",
"errors": null,
"large_image": 'data:image/png;base64,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',
"is_valid": true,
"label": "Subflow",
"environment": "onprem",
"description": "Control another workflow",
"long_description": "Execute another workflow from this workflow",
},
{
"name": "User Input",
"type": "TRIGGER",
"status": "running",
"large_image": "data:image/png;base64,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",
"description": "Wait for user input",
"trigger_type": "USERINPUT",
"errors": null,
"is_valid": cloudSyncEnabled || isCloud ? true : false,
"label": "User input",
"environment": "cloud",
"long_description": "Take user input to continue execution",
},
{
"name": "Office365",
"type": "TRIGGER",
"status": "uninitialized",
"description": "Starts upon O365 email",
"trigger_type": "EMAIL",
"errors": null,
"is_valid": cloudSyncEnabled || isCloud ? true : false,
"label": "Email",
"environment": "cloud",
"large_image": 'data:image/jpg;base64,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',
"long_description": "Execute a workflow when you get an email",
},
{
"name": "Gmail",
"type": "TRIGGER",
"status": "uninitialized",
"description": "Trigger based on Gmail",
"trigger_type": "EMAIL",
"errors": null,
"is_valid": cloudSyncEnabled || isCloud ? true : false,
"label": "Email",
"environment": "cloud",
"large_image": 'data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAzFodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QTIyMjgyMEYwMDJDMTFFQkJBOEE5OUJBM0MzMTA2RDIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QTIyMjgyMTAwMDJDMTFFQkJBOEE5OUJBM0MzMTA2RDIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozQTMwMDQxRTAwMEUxMUVCQkE4QTk5QkEzQzMxMDZEMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBMjIyODIwRTAwMkMxMUVCQkE4QTk5QkEzQzMxMDZEMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAwICAwMCAwMDAwMEBwUEBAQECQYHBQcKCQsL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',
"long_description": "Execute a workflow when you get an email",
}
]
const TriggersView = () => {
const triggersViewStyle = {
marginLeft: "10px",
marginRight: "10px",
display: "flex",
flexDirection: "column",
}
// Predefined hurr
return (
{triggers.map((trigger, index) => {
var imageline = trigger.large_image.length === 0 ?
:
const color = trigger.is_valid ? green : yellow
return(
{handleTriggerDrag(e, trigger)}}
onStop={(e) => {
handleDragStop(e)
}}
dragging={false}
position={{
x: 0,
y: 0,
}}
>
{}}>
{imageline}
{trigger.name}
{trigger.description}
)
})}
)
}
var newNodeId = ""
var parsedApp = {}
const handleTriggerDrag = (e, data) => {
const cycontainer = cy.container()
// Chrome lol
//if (e.srcElement !== undefined && e.srcElement.localName === "canvas") {
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
}
//if (data.is_valid === false) {
// alert.error(data.name+" requires hybrid version of Shuffle")
// 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,
}
//if (data.trigger_type === "WEBHOOK") {
// newAppData.status = "running"
//}
// 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) => {
//console.log("STOP!: ", e)
//console.log("APP!: ", parsedApp)
//const onNodeAdded = (event) => {
//const node = event.target
//const nodedata = event.target.data()
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") {
// AUTHENTICATION
if (app.authentication.required) {
// 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
}
var tmpAuth = JSON.parse(JSON.stringify(appAuthentication))
for (var key in tmpAuth) {
var item = tmpAuth[key]
const newfields = {}
for (var filterkey in item.fields) {
newfields[item.fields[filterkey].key] = item.fields[filterkey].value
}
item.fields = newfields
if (item.app.name === app.name) {
authenticationOptions.push(item)
if (item.id === findAuthId) {
newAppData.selectedAuthentication = item
}
}
}
if (authenticationOptions !== undefined && authenticationOptions !== null && authenticationOptions.length > 0) {
for (var key in authenticationOptions) {
const option = authenticationOptions[key]
if (option.active) {
newAppData.selectedAuthentication = option
newAppData.authentication_id = option.id
break
}
}
}
//newAppData.authentication = authenticationOptions
//if (newAppData.selectedAuthentication === null || newAppData.selectedAuthentication === undefined || newAppData.selectedAuthentication.length === "") {
// newAppData.selectedAuthentication = {}
//} else {
// console.log("CAN WE SELECT AUTH?: ", authenticationOptions)
//}
//
//
//console.log(parsedApp)
} 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: bodyHeight-appBarSize-55-50,
minHeight: bodyHeight-appBarSize-55-50,
marginTop: 1,
overflowY: "auto",
overflowX: "hidden",
}
const handleAppDrag = (e, app) => {
const cycontainer = cy.container()
// Chrome lol
//if (e.srcElement !== undefined && e.srcElement.localName === "canvas") {
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)
var parameters = null
var example = ""
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
}
const parsedEnvironments = environments === null || environments === [] ? "cloud" : environments[defaultEnvironmentIndex] === undefined ? "cloud" : environments[defaultEnvironmentIndex].Name
const newAppData = {
app_name: app.name,
app_version: app.app_version,
app_id: app.id,
sharing: app.sharing,
private_id: app.private_id,
environment: parsedEnvironments,
errors: [],
id_: newNodeId,
_id_: newNodeId,
id: newNodeId,
is_valid: true,
label: actionLabel,
type: actionType,
name: app.actions[0].name,
parameters: parameters,
isStartNode: false,
large_image: app.large_image,
authentication: [],
execution_variable: undefined,
example: example,
category: app.categories !== null && app.categories !== undefined && app.categories.length > 0 ? app.categories[0] : "",
authentication_id: "",
finished: 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.layerX,
//y: e.layerY,
x: e.pageX-cycontainer.offsetLeft,
y: e.pageY-cycontainer.offsetTop,
}
}
parsedApp = nodeToBeAdded
cy.add(nodeToBeAdded)
return
}
}
}
const AppView = (props) => {
const { allApps, prioritizedApps, filteredApps } = props;
const [visibleApps, setVisibleApps] = React.useState(prioritizedApps.concat(filteredApps.filter(innerapp => !internalIds.includes(innerapp.id))))
const ParsedAppPaper = (props) => {
const app = props.app
const [hover, setHover] = React.useState(false)
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("_", " ")
//const image = "url("+app.large_image+")"
const image = app.large_image
const newAppStyle = JSON.parse(JSON.stringify(paperAppStyle))
const pixelSize = !hover ? "2px" : "4px"
newAppStyle.borderLeft = app.is_valid ? `${pixelSize} solid ${green}` : `${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)}}>
{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()))) && !(!app.activated && app.generated))
// Extend search
if (newApps.length === 0) {
const searchvalue = value.trim().toLowerCase()
newApps = allApps.filter(app => {
for (var key in app.actions) {
const inneraction = app.actions[key]
if (inneraction.name.toLowerCase().includes(searchvalue)) {
return true
}
}
return false
})
}
//setFilteredApps(responseJson.filter(app => !internalIds.includes(app.name) && !(!app.activated && app.generated)))
//console.log("FOUND: ", newApps)
setVisibleApps(newApps)
} else {
setVisibleApps(prioritizedApps.concat(filteredApps.filter(innerapp => !internalIds.includes(innerapp.id))))
}
}
return(
)
}}
fullWidth
color="primary"
placeholder={"Search Active Apps"}
id="appsearch"
onKeyPress={(event) => {
if (event.key === "Enter") {
event.target.blur(event)
}
}}
onBlur={(event) => {
console.log("BLUR: ", event.target.value)
runSearch(event.target.value)
}}
/>
{visibleApps.length > 0 ?
{visibleApps.map((app, index) => {
if (app.invalid) {
return null
}
return(
)
})}
:
apps.length > 0 ?
Couldn't find app. Is it active?
:
Loading Apps
}
)
}
const getNextActionName = (appName) => {
var highest = ""
//label = name + _number
const allitems = workflow.actions.concat(workflow.triggers)
for (var key in allitems) {
const item = allitems[key]
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]
}
}
}
if (highest) {
return appName+"_"+(parseInt(highest)+1)
} else {
return appName+"_"+1
}
}
const setNewSelectedAction = (e) => {
const newaction = selectedApp.actions.find(a => a.name === e.target.value)
if (newaction === undefined || newaction === null) {
alert.error("Failed to find the action")
return
}
// Does this one find the wrong one?
var newSelectedAction = selectedAction
newSelectedAction.name = newaction.name
newSelectedAction.parameters = JSON.parse(JSON.stringify(newaction.parameters))
newSelectedAction.errors = []
newSelectedAction.isValid = true
newSelectedAction.is_valid = true
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'
console.log("GRADIENT!: ", newSelectedAction)
//action.fillstyle =
//'background-fill': 'data(fillstyle)',
} else {
newSelectedAction.iconBackground = iconInfo.iconBackgroundColor
}
const foundnode = cy.getElementById(newSelectedAction.id)
if (foundnode !== null && foundnode !== undefined) {
console.log("UPDATING NODE!")
foundnode.data(newSelectedAction)
}
}
// Takes an action as input, then runs through and updates the relevant fields
// based on previous actions'
newSelectedAction = RunAutocompleter(newSelectedAction)
console.log("ACTION: ", newSelectedAction)
if (newaction.returns.example !== undefined && newaction.returns.example !== null && newaction.returns.example.length > 0) {
newSelectedAction.example = newaction.returns.example
}
// 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)
setSelectedAction(newSelectedAction)
setUpdate(Math.random())
// FIXME - should change icon-node (descriptor) as well
const allNodes = cy.nodes().jsons()
for (var key in allNodes) {
const currentNode = allNodes[key]
if (currentNode.data.attachedTo === selectedAction.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) => {
//console.log("OLDNAME: ", selectedAction.name)
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)
//console.log("SHOULD CHANGE NAME EVERYWHERE ITS USED TOO BASED ON OLD NAME!")
/*
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)
}
}
}
}
*/
}
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) => {
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 (var key in allkeys) {
var currentnode = cy.getElementById(allkeys[key])
if (currentnode === undefined || currentnode === null) {
continue
}
if (currentnode.data() === undefined) {
//console.log("Node doesn't have any data (getParents)! Probably a trigger.")
handled.push(allkeys[key])
results.push({"id": allkeys[key], "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 (var i = 0; i < incomingEdges.length; i++) {
var tmp = incomingEdges[i]
if (!allkeys.includes(tmp.data().source)) {
allkeys.push(tmp.data().source)
}
}
}
if (results.length === allkeys.length || iterations === maxiter) {
break
}
iterations += 1
}
// Remove self
results = results.filter(data => data.id !== action.id)
results = results.filter(data => data.type !== "TRIGGER")
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",
}
// Old static one
//var rightsidebarStyle = {
// position: "fixed",
// right: 0,
// top: appBarSize+1,
// height: "100%",
// bottom: 0,
// minWidth: 350,
// maxWidth: 350,
// borderLeft: "1px solid rgb(91, 96, 100)",
// overflow: "scroll",
// overflowX: "auto",
// overflowY: "auto",
// zIndex: 1000,
//}
var rightsidebarStyle = {
position: "fixed",
top: appBarSize+25,
right: 25,
height: "80vh",
width: 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
const 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 = [[]]
}
// 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 setTriggerFolderWrapper = (event) => {
// if (selectedTrigger.parameters === null) {
// selectedTrigger.parameters = []
// workflow.triggers[selectedTriggerIndex].parameters = []
// }
// const folder = triggerFolders.find(a => a.displayName === event.target.value)
// console.log(event.target.value)
// console.log(folder)
//
// if (folder !== undefined) {
// workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": folder.displayName, "name": "outlookfolder", "id": folder.id}
// selectedTrigger.parameters[0] = {"value": folder.displayName, "name": "outlookfolder", "id": folder.id}
// setWorkflow(workflow)
// // This resets state for some reason (:
// setSelectedActionEnvironment({})
// setSelectedAction({})
// setSelectedTrigger({})
// setSelectedApp({})
// setSelectedEdge({})
// // Set value
// setSelectedTrigger(selectedTrigger)
// } else {
// alert.error("Some error occurred with folder "+event.target.value)
// }
//}
const setTriggerCronWrapper = (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( var i = 0; i < splitItems.length; i++){
if (splitItems[i] === value) {
splitItems.splice(i, 1)
}
}
} else {
splitItems.push(value)
}
for( var i = 0; i < splitItems.length; i++){
if (splitItems[i] === "") {
splitItems.splice(i, 1)
}
}
workflow.triggers[selectedTriggerIndex].parameters[2].value = splitItems.join(",")
console.log(splitItems)
setWorkflow(workflow)
setLocalFirstrequest(!localFirstrequest)
}
const setTriggerTextInformationWrapper = (value) => {
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = []
}
workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": value, "name": "alertinfo"}
setWorkflow(workflow)
}
const setTriggerBodyWrapper = (value) => {
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = []
workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": value, "name": "cron"}
}
workflow.triggers[selectedTriggerIndex].parameters[1] = {"value": value, "name": "execution_argument"}
setWorkflow(workflow)
}
const AppConditionHandler = (props) => {
const { tmpdata, type } = props
const [data, ] = useState(tmpdata)
const [multiline, setMultiline] = useState(false)
const [showAutocomplete, setShowAutocomplete] = React.useState(false)
const [actionlist, setActionlist] = React.useState([])
if (tmpdata === undefined) {
return tmpdata
}
if (data.variant === "") {
data.variant = "STATIC_VALUE"
}
// Set actions based on NEXT node, since it should be able to involve those two
//console.log("IN APPACTIONARG: ", selectedEdge)
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": "Key:Value store", "name": "Shuffle KV store", "value": "$shuffle_cache", "highlight": "shuffle_cache", "autocomplete": "shuffle_cache", "example": ""})
if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) {
for (var key in workflow.workflow_variables) {
const item = workflow.workflow_variables[key]
actionlist.push({"type": "workflow_variable", "name": item.name, "value": item.value, "id": item.id, "autocomplete": `${item.name.split(" ").join("_")}`, "example": item.value})
}
}
// FIXME: Add values from previous executions if they exist
if (workflow.execution_variables !== null && workflow.execution_variables !== undefined && workflow.execution_variables.length > 0) {
for (var key in workflow.execution_variables) {
const item = workflow.execution_variables[key]
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)
//console.log(destAction.data())
var parents = getParents(destAction.data())
if (parents.length > 1) {
for (var key in parents) {
const item = parents[key]
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)
}
var staticcolor = "inherit"
var actioncolor = "inherit"
var varcolor = "inherit"
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
}
onClick={() => {
//console.log("CHANGE FIELD")
}}
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
//setConditionValue({})
if (type === "source") {
setSourceValue(data)
//setDestinationValue(destinationValue)
} else if (type === "destination") {
setDestinationValue(data)
//setSourceValue(sourceValue)
}
}
const changeActionParameterVariant = (variant) => {
if (data.variant === variant) {
return
}
data.variant = variant
data.value = ""
if (variant === "ACTION_RESULT") {
console.log("SHOULD FIND PARENTS OF EDGE")
// Uses the target's parents, as the target should be executing the checks (I think)
var parents = getParents(workflow.actions.find(a => a.id === selectedEdge["target"]))
if (parents.length > 0) {
data.action_field = parents[0].label
} else {
data.action_field = ""
}
} else if (variant === "WORKFLOW_VARIABLE") {
if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) {
data.action_field = workflow.workflow_variables[0].name
}
}
setSourceValue({})
setConditionValue({})
setDestinationValue({})
if (type === "source") {
setSourceValue(data)
setDestinationValue(destinationValue)
} else if (type === "destination") {
setDestinationValue(data)
setSourceValue(sourceValue)
}
}
return (
{datafield}
{actionlist.length === 0 ? null :
Autocomplete
{
setShowAutocomplete(false)
//if (!selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === "$") {
// setShowDropdown(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 (var key in workflow.triggers) {
const item = workflow.triggers[key]
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)
if (node.length > 0) {
if (inside) {
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 =
{
setConditionsModalOpen(false)
setSourceValue({})
setConditionValue({})
setDestinationValue({})
}}
>
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}
{
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) {
currentedge.data().label = label
}
setSelectedEdge(selectedEdge)
workflow.branches[selectedEdgeIndex] = selectedEdge
setWorkflow(workflow)
}} color="primary">
Submit
const EdgeSidebar = () => {
const ConditionHandler = (condition, index) => {
const [open, setOpen] = React.useState(false)
const [anchorEl, setAnchorEl] = React.useState(null)
const duplicateCondition = (conditionIndex) => {
var newEdge = JSON.parse(JSON.stringify(selectedEdge.conditions[conditionIndex]))
const newUuid = uuidv4()
newEdge.condition.id = newUuid
newEdge.source.id = newUuid
newEdge.destination.id = newUuid
selectedEdge.conditions.push(newEdge)
setUpdate(Math.random())
}
const deleteCondition = (conditionIndex) => {
console.log(selectedEdge)
if (selectedEdge.conditions.length === 1) {
selectedEdge.conditions = []
} else {
selectedEdge.conditions.splice(conditionIndex, 1)
}
setSelectedEdge(selectedEdge)
setOpen(false)
setUpdate(Math.random())
}
const paperVariableStyle = {
minHeight: 75,
maxHeight: 75,
minWidth: "100%",
maxWidth: "100%",
marginTop: "5px",
color: "white",
backgroundColor: surfaceColor,
cursor: "pointer",
display: "flex",
}
const menuClick = (event) => {
console.log("MENU CLICK")
setOpen(!open)
setAnchorEl(event.currentTarget)
}
return (
{}}>
{
setSourceValue(condition.source)
setConditionValue(condition.condition)
setDestinationValue(condition.destination)
setConditionsModalOpen(true)
}}>
{condition.source.value}
{}}>
{condition.condition.value}
{condition.destination.value}
)
}
var injectedData =
if (selectedEdge.conditions !== undefined && selectedEdge.conditions !== null && selectedEdge.conditions.length > 0) {
injectedData = selectedEdge.conditions.map((condition, index) => {
return ConditionHandler(condition, index)
})
}
// FIXME - remove index
const conditionId = uuidv4()
return(
Conditions
{injectedData}
{
setSourceValue({"name": "source", "value": "", "variant": "STATIC_VALUE", "action_field": "", "id": conditionId})
setConditionValue({"name": "condition", "value": "equals", "id": conditionId})
setDestinationValue({"name": "destination", "value": "", "variant": "STATIC_VALUE", "action_field": "", "id": conditionId})
setConditionsModalOpen(true)
}} fullWidth>New condition
)
}
// 1. GET the trigger authentication data
// 2. Parse the fields that are used (outlook & gmail)
// 3. Parse the folders that are selected
// 4. Start / stop
const EmailSidebar = () => {
if (Object.getOwnPropertyNames(selectedTrigger).length === 0) {
return null
}
if (workflow.triggers[selectedTriggerIndex] === undefined) {
return null
}
if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) {
workflow.triggers[selectedTriggerIndex].parameters = [{"value": "No folders selected yet", "name": "outlookfolder"}]
selectedTrigger.parameters = [{"value": "No folders selected yet", "name": "outlookfolder"}]
setWorkflow(workflow)
setSelectedTrigger(selectedTrigger)
}
const setGmailFolders = () => {
console.log("In set gmail folders")
fetch(globalUrl+"/api/v1/triggers/gmail/getFolders?trigger_id="+selectedTrigger.id, {
method: "GET",
headers: {"content-type": "application/json"},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
throw new Error("No folders :o!")
}
return response.json()
})
.then((responseJson) => {
if (responseJson !== undefined && responseJson !== null && responseJson.success !== false && responseJson.length > 0) {
setTriggerFolders(responseJson)
}
if (workflow.triggers[selectedTriggerIndex].parameters.length === 0 && responseJson.length > 0) {
workflow.triggers[selectedTriggerIndex].parameters = [{"value": responseJson[0].displayName, "name": "outlookfolder", "id": responseJson[0].id}]
selectedTrigger.parameters = [{"value": responseJson[0].displayName, "name": "outlookfolder", "id": responseJson[0].id}]
setWorkflow(workflow)
setSelectedTrigger(selectedTrigger)
}
})
.catch(error => {
console.log(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) {
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(error.toString())
})
}
const getTriggerAuth = () => {
fetch(globalUrl+"/api/v1/triggers/outlook/"+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())
})
}
// Getting the triggers and the folders if they exist
// This is horrible hahah
if (localFirstrequest) {
getTriggerAuth()
setOutlookFolders()
setGmailFolders()
setLocalFirstrequest(false)
}
const handleButtonRequest = () => {
}
const gmailButton = selectedTrigger.name !== "Gmail" ? null :
{
//const redirectUri = isCloud ? "https%3A%2F%2Fshuffler.io%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister" : "http%3A%2F%2Flocalhost:5001%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister"
console.log("HOST: ", window.location.host)
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"
: "http%3A%2F%2Flocalhost:5001%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister"
const client_id = "253565968129-c0a35knic7q1pdk6i6qk9gdkvr07ci49.apps.googleusercontent.com"
//const username = isCloud ? userdata.username : userdata.id
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/gmail/"+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(error.toString())
})
}, 2500)
saveWorkflow(workflow)
}}>
Sign in with Google
const outlookButton = selectedTrigger.name !== "Office365" ? null :
{
//const redirectUri = isCloud ? "https%3A%2F%2Fshuffler.io%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister" : "http%3A%2F%2Flocalhost:5001%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
//const redirectUri = isCloud ? "http%3A%2F%2Flocalhost:5002%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister" : "http%3A%2F%2Flocalhost:5001%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
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"
: "http%3A%2F%2Flocalhost:5001%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
//const client_id = "fd55c175-aa30-4fa6-b303-09a29fb3f750"
const client_id = "bb4bff85-0d0b-4f5d-8a69-3cee8029b11a"
//isCloud ? userdata.username :
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("SELECTED: ", selectedTrigger.
console.log("BRANCH: ", branch)
const startnode = branch.destination_id
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&prompt=login&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/outlook/"+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(error.toString())
})
}, 2500)
saveWorkflow(workflow)
}} >
Sign in with Office365
// 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}
}
{triggerFolders === undefined || triggerFolders === null ?
null :
{
//setTriggerFolderWrapper(e)
setTriggerFolderWrapperMulti(e)
}}
fullWidth
input={ }
key={selectedTrigger}
>
{triggerFolders.map(folder => {
//console.log("FOLDER: ", folder)
//var folderItem =
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}
return(
Name
Environment:
{argumentView}
{
startMailSub(selectedTrigger, selectedTriggerIndex)
}} color="primary">
Start
{
stopMailSub(selectedTrigger, selectedTriggerIndex)
}} color="primary">
Stop
)
}
const SubflowSidebar = () => {
const [menuPosition, setMenuPosition] = useState(null)
const [showDropdown, setShowDropdown] = React.useState(false)
const [showDropdownNumber, setShowDropdownNumber] = React.useState(0)
const [showAutocomplete, setShowAutocomplete] = 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"})
if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) {
for (var key in workflow.workflow_variables) {
const item = workflow.workflow_variables[key]
actionlist.push({"type": "workflow_variable", "name": item.name, "value": item.value, "id": item.id, "autocomplete": `${item.name.split(" ").join("_")}`, "example": item.value})
}
}
// FIXME: Add values from previous executions if they exist
if (workflow.execution_variables !== null && workflow.execution_variables !== undefined && workflow.execution_variables.length > 0) {
for (var key in workflow.execution_variables) {
const item = workflow.execution_variables[key]
actionlist.push({"type": "execution_variable", "name": item.name, "value": item.value, "id": item.id, "autocomplete": `${item.name.split(" ").join("_")}`, "example": ""})
}
}
var parents = getParents(selectedAction)
if (parents.length > 1) {
for (var key in parents) {
const item = parents[key]
if (item.label === "Execution Argument") {
continue
}
var exampledata = item.example === undefined ? "" : item.example
// Find previous execution and their variables
//exampledata === "" &&
if (workflowExecutions.length > 0) {
// Look for the ID
const found = false
for (var key in workflowExecutions) {
if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) {
continue
}
var foundResult = workflowExecutions[key].results.find(result => result.action.id === item.id)
if (foundResult === undefined) {
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 {
const tmp = String(JSON.parse(foundResult.result))
if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) {
jsonvalid = false
}
} catch (e) {
try {
foundResult.result = foundResult.result.split("\'").join("\"")
const tmp = String(JSON.parse(foundResult.result))
if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) {
jsonvalid = false
}
} catch (e) {
jsonvalid = false
}
}
// Finds the FIRST json only
if (jsonvalid) {
exampledata = JSON.parse(foundResult.result)
break
}
//else {
// console.log("Invalid JSON: ", foundResult.result)
//}
}
}
// 1. Take
const actionvalue = {"type": "action", "id": item.id, "name": item.label, "autocomplete": `${item.label.split(" ").join("_")}`, "example": exampledata}
actionlist.push(actionvalue)
}
}
//console.log("ACTIONS: ", actionlist)
setActionlist(actionlist)
}
// Shows nested list of nodes > their JSON lists
const ActionlistWrapper = (props) => {
const { data } = props;
const handleMenuClose = () => {
setShowAutocomplete(false)
//if (!selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === "$") {
// setShowDropdown(false)
//}
setUpdate(Math.random())
setMenuPosition(null)
}
const handleItemClick = (values) => {
if (values === undefined || values === null || values.length === 0) {
return
}
var toComplete = 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
}
// Handles the fields under OpenAPI body to be parsed.
if (data.name.startsWith("${") && data.name.endsWith("}")) {
console.log("INSIDE VALUE REPLACE: ", data.name, toComplete)
// PARAM FIX - Gonna use the ID field, even though it's a hack
const paramcheck = selectedAction.parameters.find(param => param.name === "body")
if (paramcheck !== undefined) {
if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) {
paramcheck["value_replace"] = [{
"key": data.name,
"value": toComplete,
}]
} else {
const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name)
if (subparamindex === -1) {
paramcheck["value_replace"].push({
"key": data.name,
"value": toComplete,
})
} else {
paramcheck["value_replace"][subparamindex]["value"] += toComplete
}
}
workflow.triggers[selectedTriggerIndex].parameters[1]["value_replace"] = paramcheck
setSelectedAction(selectedAction)
setUpdate(Math.random())
setShowDropdown(false)
setMenuPosition(null)
return
}
}
//workflow.triggers[selectedTriggerIndex].parameters[1].value = selectedActionParameters[count].value
setSelectedAction(selectedAction)
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 (var key in workflow.triggers) {
const item = workflow.triggers[key]
if (cy !== undefined) {
var node = cy.getElementById(item.id)
if (node.length > 0) {
if (inside) {
node.addClass('shuffle-hover-highlight')
} else {
node.removeClass('shuffle-hover-highlight')
}
}
}
}
}
}
const handleActionHover = (inside, actionId) => {
if (cy !== undefined) {
var node = cy.getElementById(actionId)
if (node.length > 0) {
if (inside) {
node.addClass('shuffle-hover-highlight')
} else {
node.removeClass('shuffle-hover-highlight')
}
}
}
}
const handleMouseover = () => {
if (innerdata.type === "Execution Argument") {
handleExecArgumentHover(true)
} else if (innerdata.type === "action") {
handleActionHover(true, innerdata.id)
}
}
const handleMouseOut = () => {
if (innerdata.type === "Execution Argument") {
handleExecArgumentHover(false)
} else if (innerdata.type === "action") {
handleActionHover(false, innerdata.id)
}
}
var parsedPaths = []
if (typeof(innerdata.example) === "object") {
parsedPaths = GetParsedPaths(innerdata.example, "")
}
return (
parsedPaths.length > 0 ?
{icon} {innerdata.name}
}
parentMenuOpen={!!menuPosition}
style={{backgroundColor: theme.palette.inputColor, color: "white", minWidth: 250,}}
onClick={() => {
handleItemClick([innerdata])
}}
>
{parsedPaths.map((pathdata, index) => {
// FIXME: Should be recursive in here
const icon = pathdata.type === "value" ? : pathdata.type === "list" ? :
return (
{}}
onClick={() => {
handleItemClick([innerdata, pathdata])
}}
>
{icon} {pathdata.name}
)
})}
:
handleMouseover()} onMouseOut={() => {handleMouseOut()}}
onClick={() => {
handleItemClick([innerdata])
}}
>
{icon} {innerdata.name}
)
})}
)
}
if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
if (workflow.triggers[selectedTriggerIndex] === undefined) {
return null
}
if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) {
workflow.triggers[selectedTriggerIndex].parameters = []
workflow.triggers[selectedTriggerIndex].parameters[0] = {"name": "workflow", "value": ""}
workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "argument", "value": ""}
workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "user_apikey", "value": ""}
workflow.triggers[selectedTriggerIndex].parameters[3] = {"name": "startnode", "value": ""}
workflow.triggers[selectedTriggerIndex].parameters[4] = {"name": "check_result", "value": "false"}
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}
}
}
return(
Name
{
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
}
/>
Parameters
Select a workflow to execute
{workflows === undefined || workflows === null || workflows.length === 0 ? null :
{
setUpdate(Math.random())
workflow.triggers[selectedTriggerIndex].parameters[0].value = e.target.value.id
setSubworkflow(e.target.value)
// Sets the startnode
if (e.target.value.id !== workflow.id) {
console.log("WORKFLOW: ", e.target.value)
const startnode = e.target.value.actions.find(action => action.id === e.target.value.start)
if (startnode !== undefined && startnode !== null) {
console.log("STARTNODE: ", startnode)
setSubworkflowStartnode(startnode)
try {
workflow.triggers[selectedTriggerIndex].parameters[3].value = startnode.id
} catch {
workflow.triggers[selectedTriggerIndex].parameters[3] = {
"name": "startnode",
"value": startnode.id,
}
}
setWorkflow(workflow)
}
console.log("STARTNODE: ", startnode)
} else {
console.log("WORKFLOW: ", workflow)
}
setWorkflow(workflow)
}}
style={{backgroundColor: inputColor, color: "white", height: 50}}
>
{workflows.map((data, index) => {
if (data.id === workflow.id) {
//return null
data = workflow
}
return (
{data.name}
)
})}
}
{workflow.triggers[selectedTriggerIndex].parameters[0].value.length === 0 ? null :
workflow.triggers[selectedTriggerIndex].parameters[0].value === props.match.params.key ? null :
Explore selected workflow
}
{subworkflow === undefined || subworkflow === null || subworkflow.id === undefined || subworkflow.actions === null || subworkflow.actions === undefined || subworkflow.actions.length === 0 ? null :
{
setSubworkflowStartnode(e.target.value)
//console.log("WF: ", workflow)
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)
//setUpdate(Math.random())
}}
style={{backgroundColor: inputColor, color: "white", height: 50}}
>
{subworkflow.actions.map((action, index) => {
//console.log(action)
const isParent = getParents(selectedTrigger).find(parent => parent.id === action.id)
return (
{action.label}
)
})}
}
{
setMenuPosition({
top: event.pageY+10,
left: event.pageX+10,
})
//setShowDropdownNumber(3)
setShowDropdown(true)
setShowAutocomplete(true)
}}/>
)
}}
rows="6"
multiline
fullWidth
color="primary"
placeholder="Some execution data"
defaultValue={workflow.triggers[selectedTriggerIndex].parameters[1].value}
onBlur={(e) => {
console.log("DATA: ", e.target.value)
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 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": ""}
setWorkflow(workflow)
} else {
// Always update
const newUrl = referenceUrl+"webhook_"+selectedTrigger.id
console.log("Validating webhook url: ", newUrl)
if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) {
console.log("Url is wrong - updating")
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 : ""
//const cronValue = "*/15 * * * *"
return(
Name
Environment
{
selectedTrigger.environment = e.target.value
setSelectedTrigger(selectedTrigger)
if (e.target.value === "cloud") {
var 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 {
var 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
}
setWorkflow(workflow)
setUpdate(Math.random())
}}
style={{backgroundColor: inputColor, color: "white", height: 50}}
>
{triggerEnvironments.map(data => {
if (data.archived) {
return null
}
return (
{data}
)
})}
Parameters
{
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)
}
}}
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",
},
}}
fullWidth
disabled
defaultValue={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
)
}
return null
}
//const getTriggerAuth = (trigger_id) => {
// fetch(globalUrl+"/api/v1/triggers/"+trigger_id, {
// method: "GET",
// headers: {"content-type": "application/json"},
// credentials: "include",
// })
// .then((response) => response.json())
// .then((responseJson) => {
// if (responseJson.success) {
// console.log("SUCCESS")
// console.log(responseJson)
// } else {
// console.log("FAIL")
// }
// })
// .catch(error => {
// console.log(error.toString())
// })
//}
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())
})
}
const startMailSub = (trigger, triggerindex) => {
var folders = []
if (triggerFolders === null || triggerFolders === undefined) {
return null
}
const splitItem = workflow.triggers[selectedTriggerIndex].parameters[0].value.split(splitter)
for (var key in splitItem) {
const item = splitItem[key]
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())
})
}
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
}
console.log("TRIG: ", trigger)
const data = {
"name": hookname,
"type": "webhook",
"id": trigger.id,
"workflow": workflow.id,
"start": startNode,
"environment": trigger.environment,
"auth": auth,
}
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())
})
}
const deleteWebhook = (trigger, triggerindex) => {
if (trigger.id === undefined) {
return
}
//alert.info("Stopping webhook")
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) {
//alert.success("Successfully stopped webhook")
// 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())
})
}
const UserinputSidebar = () => {
if (Object.getOwnPropertyNames(selectedTrigger).length > 0 && workflow.triggers[selectedTriggerIndex] !== undefined) {
if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) {
workflow.triggers[selectedTriggerIndex].parameters = []
workflow.triggers[selectedTriggerIndex].parameters[0] = {"name": "alertinfo", "value": "hello this is an alert"}
// boolean,
workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "options", "value": "boolean"}
// email,sms,app ...
workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "type", "value": "email"}
workflow.triggers[selectedTriggerIndex].parameters[3] = {"name": "email", "value": "test@test.com"}
workflow.triggers[selectedTriggerIndex].parameters[4] = {"name": "sms", "value": "0000000"}
setWorkflow(workflow)
}
return(
Name
Environment:
Parameters
{
setTriggerTextInformationWrapper(e.target.value)
}}
/>
{
setTriggerOptionsWrapper("email")
}}
color="primary"
value="email"
/>
}
label={Email
}
/>
{
setTriggerOptionsWrapper("sms")
}}
color="primary"
value="sms"
/>
}
label={SMS
}
/>
{workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("email") ?
{
workflow.triggers[selectedTriggerIndex].parameters[3].value = event.target.value
setWorkflow(workflow)
setUpdate(Math.random())
}}
/>
: null
}
{workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("sms") ?
{
workflow.triggers[selectedTriggerIndex].parameters[4].value = event.target.value
setWorkflow(workflow)
setUpdate(Math.random())
}}
/>
: null
}
)
}
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 ? "*/15 * * * *" : "120"}
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 = "*/15 * * * *"
//var 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 {
console.log("Set cloud config")
//var 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 = "120"
}
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)
}}
/>
{
setTriggerBodyWrapper(e.target.value)
}}
/>
{
submitSchedule(selectedTrigger, selectedTriggerIndex)
}} color="primary">
Start
{
stopSchedule(selectedTrigger, selectedTriggerIndex)
}} color="primary">
Stop
)
}
return null
}
const cytoscapeViewWidths = 800
const bottomBarStyle = {
position: "fixed",
right: 20,
left: leftBarSize,
bottom: 0,
minWidth: cytoscapeViewWidths,
maxWidth: cytoscapeViewWidths,
marginLeft: 20,
marginBottom: 20,
zIndex: 10,
}
const topBarStyle= {
position: "fixed",
right: 0,
left: leftBarSize+20,
top: appBarSize+20,
/*
minWidth: cytoscapeViewWidths,
maxWidth: cytoscapeViewWidths,
*/
}
const TopCytoscapeBar = (props) => {
return (
Workflows
{workflow.name}
{workflow.public ?
Public Workflow PREVIEW
:
null
}
)
}
const NoActionsBar = () => {
if (workflow.actions.length === 0 || !appAdded) {
return (
)
}
return null
}
const WorkflowMenu = () => {
const [newAnchor, setNewAnchor] = React.useState(null)
const [showShuffleMenu, setShowShuffleMenu] = React.useState(false)
{ /*const [showShuffleMenu, setShowShuffleMenu] = React.useState(true) */}
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")
//setShowShuffleMenu(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(false)
}} />
}
/>
{
setShowShuffleMenu(!showShuffleMenu)
setNewAnchor(event.currentTarget)
}}>
)
}
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
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()
}
}
if (historyIndex > 0) {
setHistoryIndex(historyIndex-1)
}
}
const BottomCytoscapeBar = () => {
const [anchorEl, setAnchorEl] = React.useState(null)
const boxSize = 100
const executionButton = executionRunning ?
{
abortExecution()
}}>
:
{
executeWorkflow(executionText, workflow.start, lastSaved)
}}>
return(
{executionButton}
{workflow.public ? null :
{
setExecutionText(e.target.value)
}}
/>
}
saveWorkflow()}>
{savingState === 2 ? : savingState === 1 ? : }
{workflow.public ?
{
//setExecutionModalOpen(true)
//getWorkflowExecution(props.match.params.key, "")
//data = sanitizeWorkflow(workflow)
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)
}}>
{
removeNode()
}}>
{
setExecutionModalOpen(true)
getWorkflowExecution(props.match.params.key, "")
}}>
{workflow.configuration !== null && workflow.configuration !== undefined && workflow.configuration.exit_on_error !== undefined ?
: null}
)
}
const RightSideBar = (props) => {
const {workflow, setWorkflow, setAction, setSelectedAction, setUpdate, appActionArguments, selectedApp, workflowExecutions, setSelectedResult, selectedAction, setSelectedApp, setSelectedTrigger, setSelectedEdge, setCurrentView, cy, setAuthenticationModalOpen,setVariablesModalOpen, setCodeModalOpen, selectedNameChange, rightsidebarStyle, showEnvironment, selectedActionEnvironment, environments, setNewSelectedAction, appApiViewStyle, globalUrl, setSelectedActionEnvironment, requiresAuthentication, hideExtraTypes, scrollConfig, setScrollConfig } = props
if (!rightSideBarOpen) {
return null
}
if (Object.getOwnPropertyNames(selectedAction).length > 0) {
if (Object.getOwnPropertyNames(selectedAction).length === 0) {
return null
}
return (
)
} else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
if (selectedTrigger.trigger_type === "SCHEDULE") {
//console.log("SCHEDULE")
return(
)
} else if (selectedTrigger.trigger_type === "WEBHOOK") {
return(
)
} else if (selectedTrigger.trigger_type === "SUBFLOW") {
return(
)
} else if (selectedTrigger.trigger_type === "EMAIL") {
return(
)
} else if (selectedTrigger.trigger_type === "USERINPUT") {
return(
)
} else if (selectedTrigger.trigger_type === undefined) {
return null
} else {
console.log("Unable to handle invalid trigger type "+selectedTrigger.trigger_type)
return null
}
} else if (Object.getOwnPropertyNames(selectedEdge).length > 0) {
return(
)
}
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
//
const leftView = 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 (
{
handleReactJsonClipboard(copy)
}}
onSelect={(select) => {
HandleJsonCopy(showResult, select, "exec")
console.log("SELECTED!: ", select)
}}
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)
}
//console.log("NEW: ", stringified)
navigator.clipboard.writeText(stringified)
copyText.select()
copyText.setSelectionRange(0, 99999) /* For mobile devices */
/* Copy the text inside the text field */
document.execCommand("copy")
//alert.success("Copied data")
} 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(" ", "_")
}
console.log("COPY: ", copy)
var newitem = JSON.parse(base)
to_be_copied = "$"+base_node_name.toLowerCase().replaceAll(" ", "_")
for (var key in copy.namespace) {
if (copy.namespace[key].includes("Results for")) {
continue
}
if (newitem !== undefined && newitem !== null) {
newitem = newitem[copy.namespace[key]]
if (!isNaN(copy.namespace[key])) {
to_be_copied += ".#"
} else {
to_be_copied += "."+copy.namespace[key]
}
}
}
if (newitem !== undefined && newitem !== null) {
newitem = newitem[copy.name]
if (!isNaN(copy.name)) {
to_be_copied += ".#"
} else {
to_be_copied += "."+copy.name
}
}
//console.log(document.activeElement)
//if (document.activeElement.nodeName == 'TEXTAREA' || document.activeElement.nodeName == 'INPUT') {
// console.log("HANDLE INPUT FIELD FOR COPY!")
//}
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");
//alert.success("Copied "+to_be_copied)
console.log("COPYING!")
} else {
console.log("Couldn't find element ", elementName)
}
}
const executionModal =
setExecutionModalOpen(false)} style={{resize: "both", overflow: "auto", zIndex: 10005}} PaperProps={{style: {resize: "both", overflow: "auto", minWidth: 400, maxWidth: 400, backgroundColor: "#1F2023", color: "white", fontSize: 18, zIndex: 10005}}}>
{executionModalView === 0 ?
All Executions
{
getWorkflowExecution(props.match.params.key, "")
}} color="primary">
Refresh executions
{workflowExecutions.length > 0 ?
{workflowExecutions.map((data, index) => {
const statusColor = data.status === "FINISHED" ? green : data.status === "ABORTED" || data.status === "FAILED" ? "red" : yellow
const timeElapsed = data.completed_at-data.started_at
const resultsLength = data.results !== undefined && data.results !== null ? data.results.length : 0
const timestamp = new Date(data.started_at*1000).toISOString().split('.')[0].split("T").join(" ")
var calculatedResult = data.workflow.actions !== undefined && data.workflow.actions !== null ? data.workflow.actions.length : 0
for (var key in data.workflow.triggers) {
const trigger = data.workflow.triggers[key]
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)
}
const cur_execution = {
"execution_id": data.execution_id,
"authorization": data.authorization,
}
setExecutionRequest(cur_execution)
setExecutionModalView(1)
setExecutionData(data)
handleUpdateResults(data, cur_execution)
}}>
{getExecutionSourceImage(data)}
{timestamp}
{data.workflow.actions !== null ?
{resultsLength}/{calculatedResult}
: null}
{lastExecution === data.execution_id ?
:
}
)
return
})}
:
There are no executions yet
}
:
{
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)
setExecutionModalOpen(false)
//executionText, workflow.start)
}}>
{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).toISOString()}
: null
}
{executionData.completed_at !== undefined && executionData.completed_at !== null && executionData.completed_at > 0 ?
Finished
{new Date(executionData.completed_at*1000).toISOString()}
: 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" || result.status === "FAILURE") ?
Show failed / skipped actions }
control={
{setShowSkippedActions(!showSkippedActions)}} />
}
/>
:
null
}
{/*
Actions */}
{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")? : null}
{executionData.results === undefined || executionData.results === null || executionData.results.length === 0 && executionData.status === "EXECUTING" ?
:
executionData.results.map((data, index) => {
if (executionData.results.length !== 1 && !showSkippedActions && (data.status === "SKIPPED" || data.status === "FAILURE")) {
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
}
/*
if (imgSrc.length === 0) {
console.log("CHECK IF ITS A
}
*/
}
var actionimg = curapp === null ?
null :
if (triggers.length > 2) {
if (data.action.app_name === "shuffle-subflow") {
const parsedImage = triggers[2].large_image
actionimg =
}
if (data.action.app_name === "User Input") {
actionimg =
}
}
if (data.action.app_name === "Shuffle Tools" && data.action.id !== undefined && cy !== undefined) {
//console.log("APP (TOOLS): ", data.action)
const nodedata = cy.getElementById(data.action.id).data()
if (nodedata !== undefined && nodedata !== null && nodedata.fillstyle === "linear-gradient") {
//console.log("LINEAR :D")
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})`
}
//console.log("STYLE: ", imgStyle)
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)
}
}
}
return (
{
var currentnode = cy.getElementById(data.action.id)
if (currentnode.length !== 0) {
currentnode.addClass('shuffle-hover-highlight')
}
}} onMouseOut={() => {
var currentnode = cy.getElementById(data.action.id)
if (currentnode.length !== 0) {
currentnode.removeClass('shuffle-hover-highlight')
}
}}>
{
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)
}}>
See sub-execution
:
{
}}>
:
""
}
: null
}
Status
{data.status}
{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)
}
//if (codeModalOpen && selectedResult.result.includes("file_id")) {
// console.log("SHOW RESULT WITH FILES: ", selectedResult.result)
// //const regex = "\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b"
// //const regex = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/i
// const regex = /^[A-F\d]{8}-[A-F\d]{4}-4[A-F\d]{3}-[89AB][A-F\d]{3}-[A-F\d]{12}$/i
// //const found = selectedResult.result.match(regex)
// const found = "hello how are you cf80fa70-65cf-4963-b474-b459a6dead81 what".match(regex)
// const regex = /\${(\w{8}-\w{4}-\w{3}-\w{3}-\w)}/g
// const found = placeholder.match(regex)
// console.log("FOUND: ", found)
// //cf80fa70-65cf-4963-b474-b459a6dead81
//}
//console.log(selectedResult)
const codePopoutModal = !codeModalOpen ? null :
{
// if (!dragging) {
// console.log("START")
// setDragging(true)
// }
//}}
onStart={(e) => {
if (!dragging) {
console.log("START")
setDragging(true)
}
}}
onStop={(e) => {
console.log("STOP")
if (!dragging) {
return
}
setDragging(false)
const newoffsetX = parseInt(dragPosition.x)-parseInt(e.layerX-e.offsetX)
const newoffsetY = parseInt(dragPosition.y)-parseInt(e.layerY-e.offsetY)
if ((newoffsetX <= 40 && newoffsetX >= -40) && (newoffsetY <= 40 && newoffsetY >= -40)) {
console.log("SKIP X & Y")
return
}
const newPosition = {
x: e.layerX-e.offsetX,
y: e.layerY-e.offsetY,
}
setDragPosition(newPosition)
}}
//disabled={!newdragging}
position={dragPosition}
>
{/*
{
//console.log("MOUSE DOWN IN HERE")
//setDragging(true)
//newdragging = true
}}>
*/}
{
e.preventDefault()
for (var key in workflowExecutions) {
const execution = workflowExecutions[key]
//console.log(execution.results[0])
const result = execution.results.find(data => data.status === "SUCCESS" && data.action.id === selectedResult.action.id)
if (result !== undefined) {
setSelectedResult(result)
setUpdate(Math.random())
break
}
}
}}>
{
e.preventDefault()
for (var key in workflowExecutions) {
const execution = workflowExecutions[key]
//console.log(execution.results[0])
const result = execution.results.find(data => data.action.id === selectedResult.action.id && data.status !== "SUCCESS" && data.status !== "SKIPPED" && data.status !== "WAITING")
if (result !== undefined) {
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)
setExecutionData(workflowExecutions[executionIndex])
}
}}>
{
e.preventDefault()
//console.log("CLICKING EXIT")
setCodeModalOpen(false)
}}>
{curapp === null ? null :
}
{selectedResult.action.label}
{selectedResult.action.name}
Status {selectedResult.status}
{validate.valid ?
{
handleReactJsonClipboard(copy)
}}
onSelect={(select) => {
HandleJsonCopy(JSON.stringify(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");
//alert.success("Copied "+to_be_copied)
} else {
console.log("Failed to copy. copy_element_shuffle is undefined")
}
}}>{selectedResult.result}
}
{selectedResult.action.parameters !== null && selectedResult.action.parameters !== undefined ?
Variables
{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 (
{data.name} : {data.value}
)
})}
:
null
}
const newView =
{leftView}
{workflow.id === undefined || workflow.id === null || apps.length === 0 ?
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}
{/*
*/}
/*
:
TMP FOR NOT LOGGED IN
*/
const executionVariableModal = executionVariablesModalOpen ?
{
setNewVariableName("")
setExecutionVariablesModalOpen(false)
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
color: "white",
},
}}
>
Execution Variable
Execution Variables are TEMPORARY variables that you can ony 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
}
: null
const variablesModal = variablesModalOpen ?
{
setNewVariableName("")
setNewVariableDescription("")
setNewVariableValue("")
setVariablesModalOpen(false)
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
color: "white",
},
}}
>
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
{
if (workflow.workflow_variables === undefined || workflow.workflow_variables === null) {
workflow.workflow_variables = []
}
// try to find one with the same name
const found = workflow.workflow_variables.findIndex(data => data.name === newVariableName)
//console.log(found)
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(),
})
}
setWorkflow(workflow)
setVariablesModalOpen(false)
setNewVariableName("")
setNewVariableDescription("")
setNewVariableValue("")
}} color="primary">
Submit
: null
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 (var key in selectedApp.authentication.parameters) {
if (authenticationOption.fields[selectedApp.authentication.parameters[key].name] === undefined) {
authenticationOption.fields[selectedApp.authentication.parameters[key].name] = ""
}
}
const handleSubmitCheck = () => {
console.log("NEW AUTH: ", authenticationOption)
if (authenticationOption.label.length === 0) {
authenticationOption.label = `Auth for ${selectedApp.name}`
//alert.info("Label can't be empty")
//return
}
// Automatically mapping fields that already exist (predefined).
// Warning if fields are NOT filled
for (var key in selectedApp.authentication.parameters) {
if (authenticationOption.fields[selectedApp.authentication.parameters[key].name].length === 0) {
if (selectedApp.authentication.parameters[key].value !== undefined && selectedApp.authentication.parameters[key].value !== null && selectedApp.authentication.parameters[key].value.length > 0) {
authenticationOption.fields[selectedApp.authentication.parameters[key].name] = selectedApp.authentication.parameters[key].value
} else {
if (selectedApp.authentication.parameters[key].schema.type === "bool") {
authenticationOption.fields[selectedApp.authentication.parameters[key].name] = "false"
} else {
alert.info("Field "+selectedApp.authentication.parameters[key].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 = []
for (const key in newAuthOption.fields) {
const value = newAuthOption.fields[key]
newFields.push({
key: key,
value: value,
})
}
console.log("FIELDS: ", newFields)
newAuthOption.fields = newFields
setNewAppAuth(newAuthOption)
//appAuthentication.push(newAuthOption)
//setAppAuthentication(appAuthentication)
//
if (configureWorkflowModalOpen) {
setSelectedAction({})
}
setUpdate(authenticationOption.id)
/*
{selectedAction.authentication.map(data => (
*/
}
if (authenticationOption.label === null || authenticationOption.label === undefined) {
authenticationOption.label = selectedApp.name+" authentication"
}
//console.log(
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.link.length > 0 ?
: null*/}
{selectedApp.authentication.parameters.map((data, index) => {
//console.log("AUTH: ", data)
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 EndpointData = () => {
const [tmpVar, setTmpVar] = React.useState("")
return (
The API endpoint to use (URL) - predefined in the app
{
setTmpVar(event.target.value)
}}
onBlur={() => {
selectedApp.link = tmpVar
console.log("LINK: ", selectedApp.link)
setSelectedApp(selectedApp)
}}
/>
)
}
const configureWorkflowModal = configureWorkflowModalOpen && apps.length !== 0 ?
{
//setConfigureWorkflowModalOpen(false)
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
color: "white",
minWidth: 600,
padding: 50,
},
}}
>
{
setConfigureWorkflowModalOpen(false)
}}>
: null
// This whole part is redundant. Made it part of Arguments instead.
//console.log(selectedApp)
const authenticationModal = authenticationModalOpen ?
{
//setAuthenticationModalOpen(false)
//
if (configureWorkflowModalOpen) {
setSelectedAction({})
}
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
color: "white",
minWidth: 1100,
minHeight: 700,
maxHeight: 700,
padding: 15,
overflow: "hidden",
zIndex: 10012,
},
}}
>
{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 with this app? Join the Discord!
Want to help change this app?
{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 && isLoggedIn && workflowDone ?
const loadedCheck = isLoaded && workflowDone ?
{newView}
{variablesModal}
{executionVariableModal}
{conditionsModal}
{authenticationModal}
{codePopoutModal}
{configureWorkflowModal}
:
// Awful way of handling scroll
if (scrollConfig !== undefined && setScrollConfig !== undefined && Object.getOwnPropertyNames(selectedAction).length !== 0) {
//console.log("SET CONFIG: ", scrollConfig)
const rightSideActionView = document.getElementById("rightside_actions")
if (rightSideActionView !== undefined && rightSideActionView !== null) {
//console.log("FOUND RIGHTSIDE: ", rightSideActionView.scrollTop, scrollConfig)
if (scrollConfig.top !== 0 && scrollConfig.top !== undefined && scrollConfig.top !== 0) {
//console.log("SCROLL IS NOT 0: ", scrollConfig.top)
//rightSideActionView.scrollTop = scrollConfig.top
setTimeout(() => {
//scroller.scrollTo('elements_wrapper', {
// containerId: 'rightside_actions',
// offset: scrollConfig.top,
//})
if (scrollConfig.selected !== undefined && scrollConfig.selected !== null) {
const selectedField = document.getElementById(scrollConfig.selected)
if (selectedField !== undefined && selectedField !== null) {
selectedField.focus()
//const val = selectedField.value
//console.log("VAL: ", val)
//selectedField.value = ''
//selectedField.value = val
}
}
}, 5)
} else {
//console.log("SCROLL IS 0: ", scrollConfig.top, rightSideActionView.scrollTop)
if (rightSideActionView.scrollTop !== scrollConfig.top) {
setScrollConfig({
top: rightSideActionView.scrollTop,
left: 0,
selected: "",
})
}
}
}
}
return (
)
//
//
}
export default AngularWorkflow