Files
shuffle-cracked/frontend/src/components/ParsedAction.jsx
T
2024-06-17 19:11:56 +05:30

4238 lines
142 KiB
React
Executable File

import React, { useState, useEffect, useLayoutEffect } from "react";
import { toast } from 'react-toastify';
import { makeStyles, createStyles } from "@mui/styles";
import theme from '../theme.jsx';
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import { GetParsedPaths } from "../views/Apps.jsx";
import { sortByKey } from "../views/AngularWorkflow.jsx";
import { NestedMenuItem } from "mui-nested-menu";
import { parsedDatatypeImages } from "../components/AppFramework.jsx";
//import { useAlert
import {
Chip,
ButtonGroup,
Popper,
TextField,
TextareaAutosize,
Drawer,
Button,
Paper,
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,
Collapse,
Autocomplete
} from "@mui/material";
import {
HelpOutline as HelpOutlineIcon,
Description as DescriptionIcon,
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,
AutoFixHigh as AutoFixHighIcon,
Circle as CircleIcon,
SquareFoot as SquareFootIcon,
Storage as StorageIcon,
} from '@mui/icons-material';
const useStyles = makeStyles({
notchedOutline: {
borderColor: "#f85a3e !important",
},
root: {
"& .MuiAutocomplete-listbox": {
border: "2px solid grey",
color: "white",
fontSize: 18,
"& li:nth-child(even)": {
backgroundColor: "#CCC",
},
"& li:nth-child(odd)": {
backgroundColor: "#FFF",
},
},
},
inputRoot: {
color: "white",
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: "#f86a3e",
},
},
});
const openApiFieldDesc = "Generated by OpenAPI body example";
const ParsedAction = (props) => {
const {
workflow,
files,
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,
authenticationType,
appAuthentication,
getAppAuthentication,
actionDelayChange,
getParents,
isCloud,
lastSaved,
setLastSaved,
setShowVideo,
toolsAppId,
aiSubmit,
expansionModalOpen,
setExpansionModalOpen,
listCache,
authGroups,
apps,
setEditorData,
setcodedata,
setAiQueryModalOpen,
} = props;
const classes = useStyles();
const [hideBody, setHideBody] = React.useState(true);
const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false);
const [appActionName, setAppActionName] = React.useState(selectedAction.label);
const [delay, setDelay] = React.useState(selectedAction?.execution_delay || 0);
const [fieldCount, setFieldCount] = React.useState(0);
const [hiddenDescription, setHiddenDescription] = React.useState(true);
const [autoCompleting, setAutocompleting] = React.useState(false);
const [selectedActionParameters, setSelectedActionParameters] = React.useState(selectedAction?.parameters || []);
const [selectedVariableParameter, setSelectedVariableParameter] = React.useState("");
const [paramValues, setParamValues] = React.useState(
selectedAction?.parameters.map((param) => {
return {
name: param.name,
value: param.value,
}
})
);
const [actionlist, setActionlist] = React.useState([]);
const [jsonList, setJsonList] = React.useState([]);
const [showDropdown, setShowDropdown] = React.useState(false);
const [showDropdownNumber, setShowDropdownNumber] = React.useState(0);
const [showAutocomplete, setShowAutocomplete] = React.useState(false);
const [menuPosition, setMenuPosition] = useState(null);
const isIntegration = selectedAction.app_id === "integration"
useEffect(() => {
if (setLastSaved !== undefined) {
setLastSaved(false)
}
}, [expansionModalOpen])
useEffect(() => {
setParamValues(selectedAction.parameters.map((param) => {
return {
name: param.name,
value: param.value,
}
}))
},[
selectedAction, selectedApp,setNewSelectedAction
])
useEffect(() => {
if (selectedAction.parameters === null || selectedAction.parameters === undefined) {
return
}
const paramcheck = selectedAction.parameters.find(param => param.name === "body")
if (paramcheck === undefined || paramcheck === null) {
return
}
// This was just opposite..
if (paramcheck.id === "TOGGLED"){
setHideBody(true)
} else {
setHideBody(false)
if (paramcheck.id === "UNTOGGLED") {
setActivateHidingBodyButton(false)
}
}
}, [])
const keywords = [
"len(",
"lower(",
"upper(",
"trim(",
"split(",
"length(",
"number(",
"parse(",
"join(",
];
const getApp = (appId, setApp) => {
fetch(globalUrl + "/api/v1/apps/" + appId + "/config?openapi=false", {
headers: {
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status === 200) {
//toast("Successfully GOT app "+appId)
} else {
toast("Failed getting app");
}
return response.json();
})
.then((responseJson) => {
console.log("RESPONSE: ", responseJson);
const parsedapp =
responseJson.app !== undefined && responseJson.app !== null
? JSON.parse(atob(responseJson.app))
: {};
console.log("PARSED: ", parsedapp);
//data = parsedapp.body === undefined ? parsedapp : parsedapp.body
if (
setApp &&
parsedapp.actions !== undefined &&
parsedapp.actions !== null
) {
console.log("Inside first if");
if (
selectedApp.versions !== undefined &&
selectedApp.versions !== null
) {
parsedapp.versions = selectedApp.versions;
}
if (
selectedApp.loop_versions !== undefined &&
selectedApp.loop_versions !== null
) {
parsedapp.loop_versions = selectedApp.loop_versions;
}
// Find authentication, and if it works?
// If authentication has less OR more fields, it has to change
//console.log(selected
console.log("Inside first if2");
var foundAction = parsedapp.actions.find(
(action) =>
action.name.toLowerCase() === selectedAction.name.toLowerCase()
);
console.log("FOUNDACTION: ", foundAction);
if (foundAction !== null && foundAction !== undefined) {
var foundparams = [];
for (let [paramkey,paramkeyval] in Object.entries(foundAction.parameters)) {
const param = foundAction.parameters[paramkey];
const foundParam = selectedAction.parameters.find(
(item) => item.name.toLowerCase() === param.name.toLowerCase()
);
if (foundParam === undefined) {
console.log("COULDNT find Param: ", param);
} else {
foundAction.parameters[paramkey] = foundParam;
}
//foundparams.push(param.name)
}
} else {
toast("Couldn't find action " + selectedAction.name);
}
selectedAction.errors = [];
selectedAction.is_valid = true;
// Updating params for the new action
selectedAction.parameters = foundAction.parameters;
selectedAction.app_id = appId;
selectedAction.app_version = parsedapp.app_version;
setSelectedAction(selectedAction);
setSelectedApp(parsedapp);
}
})
.catch((error) => {
toast(error.toString());
});
};
const defineStartnode = () => {
if (cy === undefined) {
return;
}
var oldstartnode = cy.getElementById(workflow.start);
if (oldstartnode.length > 0) {
oldstartnode[0].data("isStartNode", false);
var oldnodecnt = workflow.actions.findIndex(
(a) => a.id === workflow.start
);
if (workflow.actions[oldnodecnt] !== undefined) {
workflow.actions[oldnodecnt].isStartNode = false;
}
}
var newstartnode = cy.getElementById(selectedAction.id);
if (newstartnode.length > 0) {
newstartnode[0].data("isStartNode", true);
var newnodecnt = workflow.actions.findIndex(
(a) => a.id === selectedAction.id
);
console.log("NEW NODE CNT: ", newnodecnt);
if (workflow.actions[newnodecnt] !== undefined) {
workflow.actions[newnodecnt].isStartNode = true;
console.log(workflow.actions[newnodecnt]);
}
}
// Find branches with triggers as source nodes
// Move these targets to be the new node
// Set arrows pointing to new startnode with errors
//for (var key in workflow.branches) {
// var item = workflow.branches[key]
// if (item.destination_id === oldstartnode[0].data()["id"]) {
// var curbranch = cy.getElementById(item.id)
// if (curbranch.length > 0) {
// //console.log(curbranch[0].data())
// //curbranch[0].data("target", selectedAction.id)
// //curbranch[0].data("hasErrors", true)
// //workflow.branches[key].destination_id = selectedAction.id
// //console.log(curbranch[0].data())
// }
// }
//}
setUpdate("start_node" + selectedAction.id);
workflow.start = selectedAction.id;
setWorkflow(workflow);
//setStartNode(selectedAction.id)
};
useEffect(
() => {
console.log("UseEffect Rendered!")
console.log("Workflow", workflow)
setAppActionName(selectedAction.label)
setDelay(selectedAction?.execution_delay || 0)
if (selectedAction.parameters !== undefined && selectedAction.parameters !== null && selectedAction.parameters.length > 0) {
console.log("Setting action parameters!!")
setSelectedActionParameters(selectedAction.parameters);
// }
}
// if (selectedActionParameters !== undefined && selectedActionParameters !== null
// ) {
if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && workflow.workflow_variables !== null && workflow.workflow_variables.length > 0) {
// FIXME - this is the bad thing
setSelectedVariableParameter(workflow.workflow_variables[0].name);
}
if (actionlist.length === 0) {
// FIXME: Have previous execution values in here
if (workflowExecutions.length > 0) {
for (let [key,keyval] in Object.entries(workflowExecutions)) {
if (
workflowExecutions[key].execution_argument === undefined ||
workflowExecutions[key].execution_argument === null ||
workflowExecutions[key].execution_argument.length === 0
) {
continue;
}
const valid = validateJson(workflowExecutions[key].execution_argument)
if (valid.valid) {
actionlist.push({
type: "Execution Argument",
name: "Execution Argument",
value: "$exec",
highlight: "exec",
autocomplete: "exec",
example: valid.result,
})
break
}
}
}
if (actionlist.length === 0) {
actionlist.push({
type: "Execution Argument",
name: "Execution Argument",
value: "$exec",
highlight: "exec",
autocomplete: "exec",
example: "",
})
}
/*
actionlist.push({
type: "Shuffle DB",
name: "Shuffle DB",
value: "$shuffle_cache",
highlight: "shuffle_cache",
autocomplete: "shuffle_cache",
example: {
"what": "",
"unique gmail ids new": "",
},
})
*/
var cachekey = {
type: "Shuffle DB",
name: "Shuffle DB",
value: "$shuffle_cache",
highlight: "shuffle_cache",
autocomplete: "shuffle_cache",
example: "",
}
if (listCache !== undefined && listCache !== null && listCache.keys !== undefined && listCache.keys !== null && listCache.keys.length > 0) {
cachekey.example = {}
for (var i in listCache.keys) {
const item = listCache.keys[i]
if (item.key === undefined || item.key === null || item.key.length === 0) {
continue
}
var itemvalue = item.value === undefined || item.value === null ? "" : item.value
try{
if (itemvalue.length > 10000) {
itemvalue = ""
}
} catch (e) {
itemvalue = ""
}
var itemkey = item.key.split(" ").join("_")
cachekey.example[itemkey] = {
"value": itemvalue,
}
}
} else {
}
actionlist.push(cachekey)
if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) {
for (let [key,keyval] in Object.entries(workflow.workflow_variables)) {
const item = workflow.workflow_variables[key];
actionlist.push({
type: "workflow_variable",
name: item.name,
value: item.value,
id: item.id,
autocomplete: `${item.name.split(" ").join("_")}`,
example: item.value,
});
}
}
if (workflow.execution_variables !== null && workflow.execution_variables !== undefined && workflow.execution_variables.length > 0) {
for (let [key,keyval] in Object.entries(workflow.execution_variables)) {
const item = workflow.execution_variables[key]
var exampleoutput = ""
for (let execkey in workflowExecutions) {
const exec = workflowExecutions[execkey]
if (exec["execution_variables"] === undefined || exec["execution_variables"] === null) {
continue
}
const foundExec = exec.execution_variables.find((exvar) => exvar.name === item.name)
if (!foundExec) {
continue
}
if (foundExec.value !== undefined && foundExec.value !== null && foundExec.value.length > 0) {
exampleoutput = foundExec.value
break
}
}
actionlist.push({
type: "execution_variable",
name: item.name,
value: item.value,
id: item.id,
autocomplete: `${item.name.split(" ").join("_")}`,
example: exampleoutput,
});
}
}
// Loops parent nodes' old results to fix autocomplete
if (getParents !== undefined) {
var parents = getParents(selectedAction)
if (parents.length > 1) {
var labels = []
//for (let [parentkey, parentkeyval] in Object.entries(parents)) {
for (let parentkey in parents) {
const parentNode = parents[parentkey]
if (parentNode.label === "Execution Argument") {
continue
}
//if (labels.includes(item.label)) {
// continue
//}
labels.push(parentNode.label)
var exampledata = parentNode.example === undefined || parentNode.example === null ? "" : parentNode.example
// Find previous execution and their variables
//exampledata === "" &&
if (workflowExecutions.length > 0) {
// Look for the ID
const found = false;
for (let wfkey in workflowExecutions) {
if (workflowExecutions[wfkey].results === undefined || workflowExecutions[wfkey].results === null) {
continue;
}
var foundResult = workflowExecutions[wfkey].results.find((result) => result.action.id === parentNode.id)
if (foundResult === undefined || foundResult === null) {
continue
}
if (foundResult.result !== undefined && foundResult.result !== null) {
foundResult = foundResult.result
}
const valid = validateJson(foundResult)
if (valid.valid) {
if (valid.result.success === false) {
//console.log("Skipping success false autocomplete")
} else {
// FIXME: Have a merge system to allow to use kind of any key from that node in the last 10-20 execs
//if (exampledata.length > 0) {
// exampledata = valid.result
//} else {
// exampledata = valid.result
//}
exampledata = valid.result
break
}
} else {
exampledata = foundResult
}
}
}
// 1. Take
const itemlabelComplete = parentNode.label === null || parentNode.label === undefined ? "" : parentNode.label.split(" ").join("_");
const actionvalue = {
type: "action",
id: parentNode.id,
name: parentNode.label,
autocomplete: itemlabelComplete,
example: exampledata,
}
actionlist.push(actionvalue)
}
}
setActionlist(actionlist);
}
}
},
[selectedAction,selectedApp,setNewSelectedAction]
);
useEffect(() => {
selectedNameChange(appActionName)
actionDelayChange(delay)
},[appActionName,delay])
console.log("selectedActionParameters: ", selectedActionParameters)
console.log("selectedApp:", selectedApp)
console.log("selectedAction: ", selectedAction)
console.log("ACTIONLIST: ", actionlist)
console.log("selectedVariableParameter", selectedVariableParameter)
const handleParamChange = (event, count,data) => {
const newParams = [...paramValues];
newParams.map((param) => {
if (param.name === data.name) {
param.value = event.target.value;
}
})
setParamValues(newParams);
changeActionParameter(event, count, data)
}
const calculateHelpertext = (input_data) => {
var helperText = ""
var looperText = ""
//const found = input_data.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g)
var found = input_data.match(/[\\]{0,1}[$]{1}([a-zA-Z0-9_@-]+\.?){1}([a-zA-Z0-9#_@-]+\.?){0,}/g)
if (found !== null && found !== undefined) {
var new_occurences = []
for (let [key,keyval] in Object.entries(found)) {
if (found[key][0] !== "\\") {
new_occurences.push(found[key])
}
}
found = new_occurences.valueOf()
}
if (found !== null) {
try {
// When the found array is empty.
for (let i = 0; i < found.length; i++) {
const variableSplit = found[i].split(".#")
if ((variableSplit.length-1) > 1) {
//console.log("Larger than 1: ", variableSplit)
if (looperText.length === 0) {
looperText += "PS: Double looping (.#.#) may cause problems."
}
}
var foundSlice = false
for (let j = 0; j < actionlist.length; j++) {
//console.log("ACTION: ", found[i], actionlist[j])
//console.log("ACTION :", found[i].split(".")[0].slice(1,).toLowerCase(), actionlist[j].autocomplete.toLowerCase())
if(found[i].split(".")[0].slice(1,).toLowerCase() == actionlist[j].autocomplete.toLowerCase()){
//console.log("Found: ", found[i])
// Validate path?
foundSlice = true
}
}
if (!foundSlice) {
if (!helperText.includes("Invalid variables")) {
helperText+= "Invalid variables: "
}
helperText+= found[i] + ", "
}
}
} catch (e) {
console.log("Parsing error: ", e)
}
}
if (looperText.length > 0) {
if (helperText.length > 0) {
helperText += ". "
}
helperText += looperText
}
return helperText
}
const changeActionParameter = (event, count, data, viewForceUpdate) => {
//console.log("Action change: ", selectedAction, data)
if (data.name.startsWith("${") && data.name.endsWith("}")) {
// 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) {
// Escapes all double quotes
var toReplace = event.target.value.trim()
if (!toReplace.startsWith("{") && !toReplace.startsWith("[")) {
toReplace = toReplace.replaceAll('\\"', '"').replaceAll('"', '\\"')
}
console.log("REPLACE WITH: ", toReplace);
if (
paramcheck["value_replace"] === undefined ||
paramcheck["value_replace"] === null
) {
paramcheck["value_replace"] = [
{
key: data.name,
value: toReplace,
},
];
console.log("IN IF: ", paramcheck);
} else {
const subparamindex = paramcheck["value_replace"].findIndex(
(param) => param.key === data.name
);
if (subparamindex === -1) {
paramcheck["value_replace"].push({
key: data.name,
value: toReplace,
});
} else {
paramcheck["value_replace"][subparamindex]["value"] = toReplace;
}
console.log("IN ELSE: ", paramcheck);
}
//console.log("PARAM: ", paramcheck)
//if (paramcheck.id === undefined) {
// console.log("Normal paramcheck")
//} else {
// selectedActionParameters[count]["value_replace"] = paramcheck
// selectedAction.parameters[count]["value_replace"] = paramcheck
//}
if (paramcheck["value_replace"] === undefined) {
selectedActionParameters[count]["value_replace"] = paramcheck;
selectedAction.parameters[count]["value_replace"] = paramcheck;
} else {
selectedActionParameters[count]["value_replace"] =
paramcheck["value_replace"];
selectedAction.parameters[count]["value_replace"] =
paramcheck["value_replace"];
}
setSelectedAction(selectedAction);
//setUpdate(Math.random())
return;
}
}
if (event.target.value[event.target.value.length - 1] === "$") {
if (!showDropdown) {
setShowAutocomplete(false);
setShowDropdown(true);
setShowDropdownNumber(count);
}
} else {
if (showDropdown) {
setShowDropdown(false);
}
}
// bad detection mechanism probably
if (event.target.value[event.target.value.length - 1] === "." && actionlist.length > 0) {
console.log("GET THE LAST ARGUMENT FOR NODE!");
// THIS IS AN EXAMPLE OF SHOWING IT
/*
const inputdata = {"data": "1.2.3.4", "dataType": "4.5.6.6"}
setJsonList(GetParsedPaths(inputdata, ""))
if (!showDropdown) {
setShowAutocomplete(false)
setShowDropdown(true)
setShowDropdownNumber(count)
}
console.log(jsonList)
*/
// Search for the item backwards
// 1. Reverse search backwards from . -> $
// 2. Search the actionlist for the item
// 3. Find the data for the specific item
var curstring = "";
var record = false;
for (let [key,keyval] in Object.entries(selectedActionParameters[count].value)) {
const item = selectedActionParameters[count].value[key];
if (record) {
curstring += item;
}
if (item === "$") {
record = true;
curstring = "";
}
}
//console.log("CURSTRING: ", curstring)
if (curstring.length > 0 && actionlist !== null) {
// Search back in the action list
curstring = curstring.split(" ").join("_").toLowerCase();
var actionItem = actionlist.find(
(data) =>
data.autocomplete.split(" ").join("_").toLowerCase() === curstring
);
if (actionItem !== undefined) {
console.log("Found item: ", actionItem);
//actionItem.example = actionItem.example.trim()
//actionItem.example = actionItem.example.split(" None").join(" \"None\"")
//actionItem.example = actionItem.example.split("\'").join("\"")
var jsonvalid = true;
try {
const tmp = String(JSON.parse(actionItem.example));
if (
!actionItem.example.includes("{") &&
!actionItem.example.includes("[")
) {
jsonvalid = false;
}
} catch (e) {
jsonvalid = false;
}
if (jsonvalid) {
setJsonList(GetParsedPaths(JSON.parse(actionItem.example), ""));
if (!showDropdown) {
setShowAutocomplete(false);
setShowDropdown(true);
setShowDropdownNumber(count);
}
}
}
}
} else {
if (jsonList.length > 0) {
setJsonList([]);
}
}
//console.log("CHANGING ACTION COUNT !")
selectedActionParameters[count].autocompleted = false
selectedAction.parameters[count].autocompleted = false
selectedActionParameters[count].value = event.target.value;
selectedAction.parameters[count].value = event.target.value;
var forceUpdate = false
if (isCloud && (selectedAction.app_name === "Shuffle Tools" || selectedAction.app_name === "email") && (selectedAction.name === "send_email_shuffle" || selectedAction.name === "send_sms_shuffle") && data.name === "apikey") {
console.log("APIKEY - this shouldn't show up!")
}
if (selectedAction.app_name === "Shuffle Tools" && selectedAction.name === "filter_list" && data.name === "input_list") {
//console.log("FILTER LIST!: ", event, count, data)
const parsedvalue = event.target.value
if (parsedvalue.includes("#")) {
const splitparsed = parsedvalue.split(".#.")
//console.log("Cant contain #: ", splitparsed)
if (splitparsed.length > 1) {
data.value = splitparsed[0]
selectedActionParameters[count].value = splitparsed[0]
selectedAction.parameters[count].value = splitparsed[0]
selectedActionParameters[1].value = splitparsed[1]
selectedAction.parameters[1].value = splitparsed[1]
forceUpdate = true
}
}
}
setSelectedAction(selectedAction);
if (forceUpdate || viewForceUpdate === true) {
setUpdate(Math.random())
}
//setUpdate(event.target.value)
};
const changeActionParameterCodeMirror = (event, count, data) => {
if (data.startsWith("${") && data.endsWith("}")) {
// 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) {
// Escapes all double quotes
const toReplace = event.target.value.trim().replaceAll("\\\"", "\"").replaceAll("\"", "\\\"");
console.log("REPLACE WITH: ", toReplace)
if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) {
paramcheck["value_replace"] = [{
"key": data.name,
"value": toReplace,
}]
console.log("IN IF: ", paramcheck)
} else {
const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name)
if (subparamindex === -1) {
paramcheck["value_replace"].push({
"key": data.name,
"value": toReplace,
})
} else {
paramcheck["value_replace"][subparamindex]["value"] = toReplace
}
console.log("IN ELSE: ", paramcheck)
}
//console.log("PARAM: ", paramcheck)
//if (paramcheck.id === undefined) {
// console.log("Normal paramcheck")
//} else {
// selectedActionParameters[count]["value_replace"] = paramcheck
// selectedAction.parameters[count]["value_replace"] = paramcheck
//}
if (paramcheck["value_replace"] === undefined) {
selectedActionParameters[count]["value_replace"] = paramcheck
selectedAction.parameters[count]["value_replace"] = paramcheck
} else {
selectedActionParameters[count]["value_replace"] = paramcheck["value_replace"]
selectedAction.parameters[count]["value_replace"] = paramcheck["value_replace"]
}
console.log("RESULT: ", selectedAction)
setSelectedAction(selectedAction)
//setUpdate(Math.random())
return
}
}
if (event.target.value[event.target.value.length-1] === "$") {
if (!showDropdown) {
setShowAutocomplete(false)
setShowDropdown(true)
setShowDropdownNumber(count)
}
} else {
if (showDropdown) {
setShowDropdown(false)
}
}
// bad detection mechanism probably
if (event.target.value[event.target.value.length-1] === "." && actionlist.length > 0) {
console.log("GET THE LAST ARGUMENT FOR NODE!")
// THIS IS AN EXAMPLE OF SHOWING IT
/*
const inputdata = {"data": "1.2.3.4", "dataType": "4.5.6.6"}
setJsonList(GetParsedPaths(inputdata, ""))
if (!showDropdown) {
setShowAutocomplete(false)
setShowDropdown(true)
setShowDropdownNumber(count)
}
console.log(jsonList)
*/
// Search for the item backwards
// 1. Reverse search backwards from . -> $
// 2. Search the actionlist for the item
// 3. Find the data for the specific item
var curstring = ""
var record = false
for (let [key,keyval] in Object.entries(selectedActionParameters[count].value)) {
const item = selectedActionParameters[count].value[key]
if (record) {
curstring += item
}
if (item === "$") {
record = true
curstring = ""
}
}
//console.log("CURSTRING: ", curstring)
if (curstring.length > 0 && actionlist !== null) {
// Search back in the action list
curstring = curstring.split(" ").join("_").toLowerCase()
var actionItem = actionlist.find(data => data.autocomplete.split(" ").join("_").toLowerCase() === curstring)
if (actionItem !== undefined) {
console.log("Found item: ", actionItem)
//actionItem.example = actionItem.example.trim()
//actionItem.example = actionItem.example.split(" None").join(" \"None\"")
//actionItem.example = actionItem.example.split("\'").join("\"")
var jsonvalid = true
try {
const tmp = String(JSON.parse(actionItem.example))
if (!actionItem.example.includes("{") && !actionItem.example.includes("[")) {
jsonvalid = false
}
} catch (e) {
jsonvalid = false
}
if (jsonvalid) {
setJsonList(GetParsedPaths(JSON.parse(actionItem.example), ""))
if (!showDropdown) {
setShowAutocomplete(false)
setShowDropdown(true)
setShowDropdownNumber(count)
}
}
}
}
} else {
if (jsonList.length > 0) {
setJsonList([])
}
}
selectedActionParameters[count].autocompleted = false
selectedAction.parameters[count].autocompleted = false
selectedActionParameters[count].value = data
selectedAction.parameters[count].value = data
setSelectedAction(selectedAction)
//setUpdate(Math.random())
//setUpdate(event.target.value)
}
const changeActionParameterVariable = (fieldvalue, count) => {
//console.log("CALLED THIS ONE WITH VALUE!", fieldvalue)
//if (selectedVariableParameter === fieldvalue) {
// return
//}
setSelectedVariableParameter(fieldvalue);
selectedActionParameters[count].action_field = fieldvalue;
selectedAction.parameters = selectedActionParameters;
setSelectedApp(selectedApp);
setSelectedAction(selectedAction);
setUpdate(fieldvalue);
};
// Sets ACTION_RESULT things
const changeActionParameterActionResult = (fieldvalue, count) => {
//cy.nodes().forEach(function( ele ) {
// if (ele.data()["label"] === fieldvalue) {
// selectedActionParameters[count].action_field = ele.id()
// return
// }
//});
selectedActionParameters[count].action_field = fieldvalue;
selectedAction.parameters = selectedActionParameters;
// FIXME - check if startnode
// Set value
setSelectedApp(selectedApp);
setSelectedAction(selectedAction);
setUpdate(Math.random());
};
const changeActionParameterVariant = (data, count) => {
selectedActionParameters[count].variant = data;
selectedActionParameters[count].value = "";
if (data === "ACTION_RESULT" && getParents !== undefined) {
var parents = getParents(selectedAction);
if (parents.length > 0) {
selectedActionParameters[count].action_field = parents[0].label;
} else {
selectedActionParameters[count].action_field = "";
}
} else if (data === "WORKFLOW_VARIABLE") {
if (
workflow.workflow_variables !== null &&
workflow.workflow_variables !== undefined &&
workflow.workflow_variables.length > 0
) {
selectedActionParameters[count].action_field =
workflow.workflow_variables[0].name;
}
}
selectedAction.parameters = selectedActionParameters;
// This is a stupid workaround to make it refresh rofl
setSelectedAction({});
if (setSelectedTrigger !== undefined) {
setSelectedTrigger({});
setSelectedApp({});
setSelectedEdge({});
}
// FIXME - check if startnode
// Set value
setSelectedApp(selectedApp);
setSelectedAction(selectedAction);
setUpdate(Math.random());
};
const returnHelperText = (name, value) => {
if (name === undefined || value === undefined || name === null || value === null || name === "" || value === "") {
//console.log("RETURNING EMPTY")
return ""
}
var helperText = ""
if (name.includes("url")) {
if (value.includes("localhost") || value.includes("127.0.0.1")) {
helperText = "Can't use localhost. Please change to your external IP."
}
}
// Helpertext for openapi fields
//if (helperText === "" && name === "body" && selectedApp.generated && selectedApp.activated) {
// helperText = <span style={{color: "white", marginBottom: 5, marginleft: 5}}>
//}
return helperText
}
const analyzeFields = () => {
if (selectedAction === undefined || selectedAction === null) {
return null
}
if (selectedActionParameters === undefined || selectedActionParameters === null || selectedActionParameters.length === 0) {
return null
}
// Only shuffle tools for now
if (selectedAction.app_name !== "Shuffle Tools") {
return null
}
// Custom rules
if (selectedAction.name === "set_cache_value") {
var actionKey = ""
var actionValue = ""
for (let [key,keyval] in Object.entries(selectedActionParameters)) {
const param = selectedActionParameters[key]
if (param.name === "key") {
actionKey = param.value
}
if (param.name === "value") {
actionValue = param.value
}
}
if (actionKey === "" || actionValue === "") {
return null
}
if (!actionKey.includes(".#") && actionValue.includes(".#")) {
return <span>When the key ({actionKey}) is static, but the value is a list ({actionValue}), it will overwrite the list. You may be looking for the <span onClick={() => {}} style={{cursor: "pointer", color: "#f85a3e", }}>Check Cache Contains</span> action instead.</span>
}
}
return null
}
const suggestionInfo = () => {
const suggestionText = analyzeFields()
if (suggestionText === undefined || suggestionText === null) {
return null
}
if (selectedAction.errors === undefined || selectedAction.errors === null || selectedAction.errors.length === 0) {
selectedAction.errors = ["Suggestion: " + suggestionText]
}
return <Paper style={{padding: 10, backgroundColor: theme.palette.surfaceColor, border: "1px solid red",}}>
<Typography variant="body" style={{color: "white", }}>
<b>Tip:</b> {suggestionText}
</Typography>
</Paper>
}
// FIXME: Issue #40 - selectedActionParameters not reset
if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) {
var wrapperapp = {
"id": "",
"name": "noapp",
"large_image": "",
}
var actionname = selectedAction.name.toLowerCase()
if (actionname === "email" || actionname === "communication") {
actionname = "comms"
}
if (isIntegration) {
// Check if actionname uppercase is in the parsedDatatypeImages() dictionary
if (parsedDatatypeImages()[actionname.toUpperCase()] !== undefined) {
var newimage = parsedDatatypeImages()[actionname.toUpperCase()]
//newimage = <img src={newimage} style={{width: 35, height: 35, marginRight: 5, borderRadius: 5, cursor: "pointer", border: noAppSelected ? "3px solid #86c142" : "2px solid rgba(255,255,255,0.6)"}} />
wrapperapp.large_image = newimage
} else {
console.log("Couldn't find actionname: ", actionname)
}
}
var authWritten = false;
var noAppSelected = false
var paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name")
if (paramIndex === -1 || selectedAction.parameters[paramIndex].value === "" || selectedAction.parameters[paramIndex].value === "noapp") {
// Check the actual value and if it's the same
noAppSelected = true
}
}
const ActionSelectOption = (actionprops) => {
const { option, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops;
const [hover, setHover] = React.useState(false);
return (
<Tooltip
color="secondary"
title={newActiondescription}
placement="left"
>
<div style={{
cursor: "pointer",
padding: 8,
paddingLeft: 14,
paddingBottom: 4,
backgroundColor: hover ? theme.palette.surfaceColor : theme.palette.inputColor,
}} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
onClick={(event) => {
// event.preventDefault()
//setSelectedAction(actionprops)
//setShowActionList(false)
//setUpdate(Math.random())
//
if (option !== undefined && option !== null) {
setNewSelectedAction({
target: {
value: option.name
}
});
}
document.activeElement.blur();
}}
>
<div style={{ display: "flex", marginBottom: 0,}}>
<span
style={{
marginRight: 10,
marginTop: "auto",
marginBottom: 0,
}}
>
{useIcon}
</span>
<span style={{marginBottom: 0, marginTop: 3, }}>{newActionname}</span>
</div>
{extraDescription.length > 0 ?
<Typography variant="body2" color="textSecondary" style={{marginTop: 0, overflow: "hidden", whiteSpace: "nowrap", display: "block",}}>
{extraDescription}
</Typography>
: null}
</div>
</Tooltip>
)
}
const sortByCategoryLabel = (a, b) => {
const aHasCategoryLabel = a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0
const bHasCategoryLabel = b.category_label !== undefined && b.category_label !== null && b.category_label.length > 0
// Sort by existence and length of "category_label"
if (aHasCategoryLabel && !bHasCategoryLabel) {
return -1
} else if (!aHasCategoryLabel && bHasCategoryLabel) {
return 1
} else {
return 0
}
}
// Function to deduplicate based on the "name" field
const deduplicateByName = (array) => {
const uniqueNames = {};
return array.filter(item => {
if (!item.hasOwnProperty('name') || !item.name.length) {
return true
}
if (!uniqueNames[item.name]) {
uniqueNames[item.name] = true
return true
}
return false
})
}
// Gets the most important actions first
const renderedActionOptions = deduplicateByName((
selectedApp.actions === undefined || selectedApp.actions === null ? [] :
selectedApp.actions.filter((a) =>
a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0).concat(sortByKey(selectedApp.actions, "label"))
).sort(sortByCategoryLabel))
const selectedAppIcon = selectedAction.large_image
// console.log("Selected action: ", selectedAction)
console.log("Selected action paramaters: ", selectedAction.parameters)
var baselabel = selectedAction.label
return (
<div style={appApiViewStyle} id="parsed_action_view">
{hideExtraTypes === true ? null : (
<span>
<div style={{ display: "flex", minHeight: 40, marginBottom: 30 }}>
<div style={{ flex: 1 }}>
<div style={{ display: "flex", }}
onClick={() => {
//window.open("/apps/${selectedAction.app_id}", "_blank")
}}
>
<Tooltip title={"App: "+selectedAction.app_name} placement="top">
<img src={selectedAppIcon} style={{
width: 30,
height: 30,
marginRight: 10,
borderRadius: 5,
marginTop: 13,
border: "2px solid rgba(255,255,255,0.3)",
}} />
</Tooltip>
<h3 style={{ }}>
{(
selectedAction.app_name.charAt(0).toUpperCase() +
selectedAction.app_name.substring(1)
).replaceAll("_", " ")}
</h3>
</div>
<div style={{display: "flex", marginTop: 0, }}>
<IconButton
style={{
marginTop: "auto",
marginBottom: "auto",
height: 30,
paddingLeft: 0,
paddingRight: 0,
}}
onClick={() => {
if (workflowExecutions.length > 0) {
// Look for the ID
var found = false;
for (let [key,keyval] in Object.entries(workflowExecutions)) {
if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) {
continue;
}
var foundResult = workflowExecutions[key].results.find(
(result) => result.action.id === selectedAction.id
)
if (foundResult === undefined || foundResult === null) {
continue;
}
const oldstartnode = cy.getElementById(selectedAction.id);
if (oldstartnode !== undefined && oldstartnode !== null) {
const foundname = oldstartnode.data("label")
if (foundname !== undefined && foundname !== null) {
foundResult.action.label = foundname
}
}
setSelectedResult(foundResult);
if (setCodeModalOpen !== undefined) {
setCodeModalOpen(true);
found = true
}
break;
}
if (!found) {
toast("No result for this action yet. Please run the workflow first.")
}
}
}}
>
<Tooltip
color="primary"
title="See previous results for this action"
placement="top"
>
<ArrowLeftIcon style={{ color: "rgba(255,255,255,0.7)" }} />
</Tooltip>
</IconButton>
<IconButton
style={{
marginTop: "auto",
marginBottom: "auto",
height: 30,
marginLeft: 15,
paddingRight: 0,
}}
onClick={() => {
setAuthenticationModalOpen(true)
}}
>
<Tooltip
color="primary"
title="Find app documentation"
placement="top"
>
<DescriptionIcon style={{ color: "rgba(255,255,255,0.7)" }} />
</Tooltip>
</IconButton>
{/*
<IconButton
style={{
marginTop: "auto",
marginBottom: "auto",
height: 30,
marginLeft: 15,
paddingRight: 0,
}}
onClick={() => {}}
>
<a
href="https://shuffler.io/docs/workflows#nodes"
rel="norefferer"
target="_blank"
style={{ textDecoration: "none", color: "#f85a3e" }}
>
<Tooltip
color="primary"
title="What are actions?"
placement="top"
>
<HelpOutlineIcon style={{ color: "rgba(255,255,255,0.7)" }} />
</Tooltip>
</a>
</IconButton>
*/}
{/*
<IconButton
style={{
marginTop: "auto",
marginBottom: "auto",
height: 30,
marginLeft: 15,
paddingRight: 0,
}}
onClick={() => {
//setAuthenticationModalOpen(true);
console.log("Should enable/disable magic!")
console.log("Action: ", selectedAction)
if (selectedAction.run_magic_output === undefined) {
selectedAction.run_magic_output = true
} else {
if (selectedAction.run_magic_output === true) {
selectedAction.run_magic_output = false
} else {
selectedAction.run_magic_output = true
}
}
setSelectedAction(selectedAction)
setUpdate(Math.random());
}}
>
<Tooltip
color="primary"
title={selectedAction.run_magic_output === undefined || selectedAction.run_magic_output === null || selectedAction.run_magic_output === false ? "Click to enable magic parsing" : "Click to disable magic parsing"}
placement="top"
>
<AutoFixHighIcon style={{ color: selectedAction.run_magic_output === undefined || selectedAction.run_magic_output === null || selectedAction.run_magic_output === false ? "rgba(255,255,255,0.7)" : "#f86a3e"}} />
</Tooltip>
</IconButton>
*/}
{/*
<IconButton
style={{
marginTop: "auto",
marginBottom: "auto",
height: 30,
marginLeft: 15,
paddingRight: 0,
}}
onClick={() => {
}}
>
<Tooltip
color="primary"
title={"Find related tworkflows"}
placement="top"
>
<a href={`https://shuffler.io/search?tab=workflows&q=${selectedAction.app_name}`} target="_blank">
<SearchIcon style={{ color: "rgba(255,255,255,0.7)"}} />
</a>
</Tooltip>
</IconButton>
*/}
<IconButton
style={{
marginTop: "auto",
marginBottom: "auto",
height: 30,
marginLeft: 15,
paddingRight: 0,
}}
disabled={autoCompleting}
onClick={() => {
//if (setAiQueryModalOpen !== undefined) {
// setAiQueryModalOpen(true)
//} else {
aiSubmit("Fill based on previous values", undefined, undefined, selectedAction)
//}
setAutocompleting(true)
}}
>
<Tooltip
color="primary"
title={"Autocomplete fields. Uses the name of the current action, the fields and previous actions' results"}
placement="top"
>
{autoCompleting ?
<CircularProgress style={{height: 20, width: 20, }} />
:
<AutoFixHighIcon style={{ color: "rgba(255,255,255,0.7)", height: 24, }} />
}
</Tooltip>
</IconButton>
</div>
</div>
<div style={{ display: "flex", flexDirection: "column" }}>
{/*selectedAction.id === workflow.start ? null :
<Tooltip color="primary" title={"Make this node the start action"} placement="top">
<Button style={{zIndex: 5000, marginTop: 10,}} color="primary" variant="outlined" onClick={(e) => {
defineStartnode(e)
}}>
<KeyboardArrowRightIcon />
</Button>
</Tooltip>
*/}
{selectedApp.versions !== null &&
selectedApp.versions !== undefined &&
selectedApp.versions.length > 1 ? (
<Select
MenuProps={{
disableScrollLock: true,
}}
value={selectedAction.app_version}
onChange={(event) => {
const newversion = selectedApp.versions.find(
(tmpApp) => tmpApp.version == event.target.value
)
if (newversion !== undefined && newversion !== null) {
getApp(newversion.id, true)
}
// Change in all actions in the workflow at the same time and add a toast.success() about it
for (var actionkey in workflow.actions) {
const action = workflow.actions[actionkey]
if (action.app_name === selectedAction.app_name) {
workflow.actions[actionkey].app_version = event.target.value
}
}
toast.success("Changed version of all nodes to "+event.target.value)
}}
style={{
marginTop: 10,
backgroundColor: theme.palette.surfaceColor,
backgroundColor: theme.palette.inputColor,
color: "white",
height: 35,
marginleft: 10,
borderRadius: theme.palette.borderRadius,
}}
SelectDisplayProps={{
style: {
},
}}
>
{selectedApp.versions.map((data, index) => {
return (
<MenuItem
key={index}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value={data.version}
>
{data.version}
</MenuItem>
);
})}
</Select>
) : null}
</div>
</div>
<div style={{display: "flex"}}>
<div style={{flex: 5}}>
<Typography style={{color: "rgba(255,255,255,0.7)"}}>Name</Typography>
<TextField
style={theme.palette.textFieldStyle}
InputProps={{
style: theme.palette.innerTextfieldStyle,
disableUnderline: true,
}}
fullWidth
color="primary"
placeholder={selectedAction.label}
value={appActionName}
onChange={
(event) => {
let newValue = event.target.value
newValue = newValue.replaceAll(" ", "_")
setAppActionName(newValue)
}
}
onBlur={(e) => {
// Copy the name value
const name = appActionName
const parsedBaseLabel = "$"+baselabel.toLowerCase().replaceAll(" ", "_")
const newname = "$"+name.toLowerCase().replaceAll(" ", "_")
// Check if it's the same as the current name in use
//if (name === selectedAction.label) {
// console.log("Returning from name thing")
// return
//}
// Change in actions, triggers & conditions
// Highlight the changes somehow with a glow?
if (workflow.branches !== undefined && workflow.branches !== null) {
for (let [key,keyval] in Object.entries(workflow.branches)) {
if (workflow.branches[key].conditions !== undefined && workflow.branches[key].conditions !== null) {
for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) {
const condition = workflow.branches[key].conditions[subkey]
const sourceparam = condition.source
const destinationparam = condition.destination
// Should have a smarter way of discovering node names
// Finding index(es) and replacing at the location
if (sourceparam.value.includes("$")) {
try {
var cnt = -1
var previous = 0
while (true) {
cnt += 1
// Need to make sure e.g. changing the first here doesn't change the 2nd
// $change_me
// $change_me_2
const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous)
if (foundindex === previous && foundindex !== 0) {
break
}
if (foundindex >= 0) {
previous = foundindex+newname.length
// Need to add diff of length to word
// Check location:
// If it's a-zA-Z_ then don't replace
if (sourceparam.value.length > foundindex+parsedBaseLabel.length) {
const regex = /[a-zA-Z0-9_]/g;
const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex);
if (match !== null) {
continue
}
}
console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value)
const extralength = newname.length-parsedBaseLabel.length
sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length)
console.log("New: ", workflow.branches[key].conditions[subkey].source.value)
} else {
break
}
// Break no matter what after 5 replaces. May need to increase
if (cnt >= 5) {
break
}
}
} catch (e) {
console.log("Failed value replacement based on index: ", e)
}
}
if (destinationparam.value.includes("$")) {
try {
var cnt = -1
var previous = 0
while (true) {
cnt += 1
// Need to make sure e.g. changing the first here doesn't change the 2nd
// $change_me
// $change_me_2
const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous)
if (foundindex === previous && foundindex !== 0) {
break
}
if (foundindex >= 0) {
previous = foundindex+newname.length
// Need to add diff of length to word
// Check location:
// If it's a-zA-Z_ then don't replace
if (destinationparam.value.length > foundindex+parsedBaseLabel.length) {
const regex = /[a-zA-Z0-9_]/g;
const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex);
if (match !== null) {
continue
}
}
console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value)
const extralength = newname.length-parsedBaseLabel.length
destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length)
console.log("New: ", workflow.branches[key].conditions[subkey].destination.value)
} else {
break
}
// Break no matter what after 5 replaces. May need to increase
if (cnt >= 5) {
break
}
}
} catch (e) {
console.log("Failed value replacement based on index: ", e)
}
}
}
}
}
}
for (let [key,keyval] in Object.entries(workflow.actions)) {
if (workflow.actions[key].id === selectedAction.id) {
continue
}
const params = workflow.actions[key].parameters
console.log(params)
if (params === null || params === undefined) {
continue
}
for (let [subkey, subkeyval] in Object.entries(params)) {
const param = workflow.actions[key].parameters[subkey];
if (!param.value.includes("$")) {
continue
}
// Should have a smarter way of discovering node names
// Do regex?
// Finding index(es) and replacing at the location
//
try {
var cnt = -1
var previous = 0
while (true) {
cnt += 1
// Need to make sure e.g. changing the first here doesn't change the 2nd
// $change_me
// $change_me_2
const foundindex = param.value.toLowerCase().indexOf(parsedBaseLabel, previous)
if (foundindex === previous && foundindex !== 0) {
break
}
if (foundindex >= 0) {
previous = foundindex+newname.length
// Need to add diff of length to word
// Check location:
// If it's a-zA-Z_ then don't replace
if (param.value.length > foundindex+parsedBaseLabel.length) {
const regex = /[a-zA-Z0-9_]/g;
const match = param.value[foundindex+parsedBaseLabel.length].match(regex);
if (match !== null) {
continue
}
}
console.log("Old found: ", workflow.actions[key].parameters[subkey].value)
const extralength = newname.length-parsedBaseLabel.length
param.value = param.value.substring(0, foundindex) + newname + param.value.substring(foundindex-extralength+newname.length, param.value.length)
console.log("New: ", workflow.actions[key].parameters[subkey].value)
} else {
break
}
// Break no matter what after 5 replaces. May need to increase
if (cnt >= 5) {
break
}
}
} catch (e) {
console.log("Failed value replacement based on index: ", e)
}
}
}
setWorkflow(workflow);
setUpdate(Math.random());
baselabel = name
}}
/>
</div>
{/*!isCloud ? null :*/}
<div style={{flex: 1, marginLeft: 5,}}>
<Tooltip
color="primary"
title={"Delay before action executes (in seconds)"}
placement="top"
>
<span>
<Typography style={{color: "rgba(255,255,255,0.7)"}}>Delay</Typography>
<TextField
InputProps={{
style: theme.palette.innerTextfieldStyle,
disableUnderline: true,
}}
placeholder={selectedAction.execution_delay}
value={delay}
onChange={(event) => {
setDelay(event.target.value)
}}
/>
</span>
</Tooltip>
</div>
{/**/}
</div>
</span>
)}
{selectedApp.name !== undefined &&
selectedAction.authentication !== null &&
selectedAction.authentication !== undefined &&
selectedAction.authentication.length === 0 &&
requiresAuthentication ? (
<div style={{ marginTop: 15 }}>
<Tooltip
color="primary"
title={"Add authentication option"}
placement="top"
>
<span>
<Button
color="primary"
style={{}}
fullWidth
variant="contained"
onClick={() => {
//if (authenticationType.type === "oauth2" && authenticationType.redirect_uri !== undefined && authenticationType.redirect_uri !== null) {
// return null
//}
setAuthenticationModalOpen(true);
}}
>
<AddIcon style={{ marginRight: 10 }} /> Authenticate{" "}
{selectedApp.name.replaceAll("_", " ")}
</Button>
</span>
</Tooltip>
</div>
) : null}
{selectedAction.authentication !== undefined &&
selectedAction.authentication !== null &&
selectedAction.authentication.length > 0 ? (
<div style={{ marginTop: 15 }}>
<Typography style={{color: "rgba(255,255,255,0.7)"}}>Authentication</Typography>
<div style={{ display: "flex" }}>
<Select
MenuProps={{
disableScrollLock: true,
}}
labelId="select-app-auth"
value={
Object.getOwnPropertyNames(selectedAction.selectedAuthentication).length === 0
? "No selection"
: selectedAction.selectedAuthentication
}
SelectDisplayProps={{
style: {
maxWidth: 250,
},
}}
fullWidth
onChange={(e) => {
if (e.target.value === "No selection") {
selectedAction.selectedAuthentication = {};
selectedAction.authentication_id = "";
for (let [key,keyval] in Object.entries(selectedAction.parameters)) {
//console.log(selectedAction.parameters[key])
if (selectedAction.parameters[key].configuration) {
selectedAction.parameters[key].value = "";
}
}
setSelectedAction(selectedAction);
setUpdate(Math.random());
} else {
selectedAction.selectedAuthentication = e.target.value;
selectedAction.authentication_id = e.target.value.id;
setSelectedAction(selectedAction);
setUpdate(Math.random());
}
}}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
height: 50,
maxWidth: rightsidebarStyle.maxWidth - 80,
borderRadius: theme.palette.borderRadius,
}}
>
<MenuItem
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value="No selection"
>
<em>No selection</em>
</MenuItem>
{selectedAction.authentication.map((data) => {
if (data.last_modified === true) {
//console.log("LAST MODIFIED: ", data.label)
}
return (
<MenuItem
key={data.id}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
maxWidth: 500,
overflowX: "auto",
}}
value={data}
>
{data.last_modified === true ?
<Chip
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}}
label={"Latest"}
variant="outlined"
color="secondary"
/>
: null}
{data.app.app_version !== undefined && data.app.app_version !== null && data.app.app_version !== "" && data.app.app_version !== "undefined" ?
<Chip
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}}
label={data.app.app_version}
variant="outlined"
color="secondary"
/>
: null}
{data.label}
</MenuItem>
);
})}
</Select>
{/*
<Button fullWidth style={{margin: "auto", marginTop: "10px",}} color="primary" variant="contained" onClick={() => setAuthenticationModalOpen(true)}>
AUTHENTICATE
</Button>
curaction.authentication = authenticationOptions
if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "")
*/}
<Tooltip
color="primary"
title={"Add authentication option"}
placement="top"
>
<IconButton
color="primary"
variant="outlined"
style={{}}
onClick={() => {
setAuthenticationModalOpen(true);
}}
>
<AddIcon />
</IconButton>
</Tooltip>
</div>
</div>
) : null}
{showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? (
<div style={{ marginTop: "20px" }}>
<Typography style={{color: "rgba(255,255,255,0.7)"}}>Environment</Typography>
<Select
MenuProps={{
disableScrollLock: true,
}}
value={
selectedActionEnvironment === undefined || selectedActionEnvironment === null ||
selectedActionEnvironment.Name === undefined || selectedActionEnvironment.Name === null
? isCloud ? "Cloud" : "Shuffle"
: selectedActionEnvironment.Name
}
SelectDisplayProps={{
style: {
},
}}
fullWidth
onChange={(e) => {
const env = environments.find((a) => a.Name === e.target.value);
setSelectedActionEnvironment(env);
selectedAction.environment = env.Name;
setSelectedAction(selectedAction);
for (let actionkey in workflow.actions) {
workflow.actions[actionkey].environment = env.Name
}
setWorkflow(workflow)
toast("Set environment for ALL actions to " + env.Name)
}}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
height: "50px",
borderRadius: theme.palette.borderRadius,
}}
>
{environments.map((data, index) => {
if (data.archived === true) {
return null;
}
return (
<MenuItem
key={data.Name}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value={data.Name}
>
{data.default === true ?
<Chip
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}}
label={"Default"}
variant="outlined"
color="secondary"
/>
: null}
{data.Name}
</MenuItem>
);
})}
</Select>
</div>
) : null}
{workflow.execution_variables !== undefined &&
workflow.execution_variables !== null &&
workflow.execution_variables.length > 0 ? (
<div style={{ marginTop: "20px" }}>
<Typography>Execution variable (optional)</Typography>
<Select
MenuProps={{
disableScrollLock: true,
}}
value={
selectedAction.execution_variable !== undefined
&& selectedAction.execution_variable !== null
&& selectedAction.execution_variable.name !== undefined
&& selectedAction.execution_variable.name !== null
&& selectedAction.execution_variable.name.length > 0
? selectedAction.execution_variable.name
: "No selection"
}
SelectDisplayProps={{
style: {
},
}}
fullWidth
onChange={(e) => {
if (e.target.value === "No selection") {
selectedAction.execution_variable = { name: "No selection" };
} else {
const value = workflow.execution_variables.find(
(a) => a.name === e.target.value
);
selectedAction.execution_variable = value;
}
setSelectedAction(selectedAction);
setUpdate(Math.random());
}}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
height: "50px",
borderRadius: theme.palette.borderRadius,
}}
>
<MenuItem
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value="No selection"
>
<em>No selection</em>
</MenuItem>
<Divider style={{ backgroundColor: theme.palette.inputColor }} />
{workflow.execution_variables.map((data) => (
<MenuItem
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value={data.name}
>
{data.name}
</MenuItem>
))}
</Select>
</div>
) : null}
<Divider
style={{
marginTop: "20px",
height: "1px",
width: "100%",
backgroundColor: "rgb(91, 96, 100)",
}}
/>
<div style={{ flex: "6", marginTop: "20px" }}>
{/*hideExtraTypes ? null :
<div style={{marginBottom: 5}}>
<b>Actions</b>
</div>
*/}
{setNewSelectedAction !== undefined ? (
<Autocomplete
id="action_search"
autoHighlight
value={selectedAction}
classes={{ inputRoot: classes.inputRoot }}
groupBy={(option) => {
// Most popular
// Is categorized
// Uncategorized
return option.category_label !== undefined && option.category_label !== null && option.category_label.length > 0 ? "Most used" : "All Actions";
}}
renderGroup={(params) => {
return (
<li key={params.key}>
<Typography variant="body1" style={{textAlign: "center", marginLeft: 10, marginTop: 25, marginBottom: 10, }}>{params.group}</Typography>
<Typography variant="body2">{params.children}</Typography>
</li>
)
}}
options={renderedActionOptions}
ListboxProps={{
style: {
backgroundColor: theme.palette.surfaceColor,
color: "white",
},
}}
filterOptions={(options, { inputValue }) => {
//console.log("Option contains?: ", inputValue, options)
const lowercaseValue = inputValue.toLowerCase()
options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue))
return options
}}
getOptionLabel={(option) => {
if (option === undefined || option === null || option.name === undefined || option.name === null ) {
return null;
}
const newname = (
option.name.charAt(0).toUpperCase() + option.name.substring(1)
).replaceAll("_", " ");
return newname;
}}
fullWidth
style={{
backgroundColor: theme.palette.inputColor,
height: 50,
borderRadius: theme.palette.borderRadius,
}}
onChange={(event, newValue) => {
// Workaround with event lol
if (newValue !== undefined && newValue !== null) {
setNewSelectedAction({
target: {
value: newValue.name
}
});
}
event.target.blur();
}}
renderOption={(props, option, state) => {
var newActionname = option.name;
if (option.label !== undefined && option.label !== null && option.label.length > 0) {
newActionname = option.label;
}
var newActiondescription = option.description;
//console.log("DESC: ", newActiondescription)
if (option.description === undefined || option.description === null) {
newActiondescription = "Description: No description defined for this action"
} else {
newActiondescription = "Description: "+newActiondescription
}
const iconInfo = GetIconInfo({ name: option.name });
const useIcon = iconInfo.originalIcon;
if (newActionname === undefined || newActionname === null) {
newActionname = "No name"
option.name = "No name"
option.label = "No name"
}
newActionname = (newActionname.charAt(0).toUpperCase() + newActionname.substring(1)).replaceAll("_", " ");
var method = ""
var extraDescription = ""
if (option.name.includes("get_")) {
method = "GET"
} else if (option.name.includes("post_")) {
method = "POST"
} else if (option.name.includes("put_")) {
method = "PUT"
} else if (option.name.includes("patch_")) {
method = "PATCH"
} else if (option.name.includes("delete_")) {
method = "DELETE"
} else if (option.name.includes("options_")) {
method = "OPTIONS"
} else if (option.name.includes("connect_")) {
method = "CONNECT"
}
// FIXME: Should it require a base URL?
if (method.length > 0 && option.description !== undefined && option.description !== null && option.description.includes("http")) {
var extraUrl = ""
const descSplit = option.description.split("\n")
// Last line of descSplit
if (descSplit.length > 0) {
extraUrl = descSplit[descSplit.length-1]
}
//for (let [line,lineval] in Object.entries(descSplit)) {
// if (descSplit[line].includes("http") && descSplit[line].includes("://")) {
// const urlsplit = descSplit[line].split("/")
// try {
// extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/")
// } catch (e) {
// //console.log("Failed - running with -1")
// extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/")
// }
// //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line])
// //break
// }
//}
if (extraUrl.length > 0) {
if (extraUrl.includes(" ")) {
extraUrl = extraUrl.split(" ")[0]
}
if (extraUrl.includes("#")) {
extraUrl = extraUrl.split("#")[0]
}
extraDescription = `${method} ${extraUrl}`
} else {
//console.log("No url found. Check again :)")
}
}
return (
<ActionSelectOption
{...props}
option={option}
newActiondescription={newActiondescription}
useIcon={useIcon}
newActionname={newActionname}
extraDescription={extraDescription}
/>
);
}}
renderInput={(params) => {
if (params.inputProps?.value) {
const prefixes = ["Post", "Put", "Patch"];
for (let prefix of prefixes) {
if (params.inputProps.value.startsWith(prefix)) {
let newValue = params.inputProps.value.replace(prefix + " ", "");
if (newValue.length > 1) {
newValue = newValue.charAt(0).toUpperCase() + newValue.substring(1);
}
// Set the new value without mutating inputProps
params = { ...params, inputProps: { ...params.inputProps, value: newValue } };
break;
}
}
// Check if it starts with "Get List" and method is "Get"
if (params.inputProps.value.startsWith("Get List")) {
console.log("Get List");
}
}
return (
<TextField
{...params}
data-lpignore="true"
autocomplete="off"
dataLPIgnore="true"
autoComplete="off"
color="primary"
id="checkbox-search"
variant="body1"
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
label={isIntegration ? "Choose a category" : "Find Actions"}
variant="outlined"
name={`disable_autocomplete_${Math.random()}`}
/>
);
}}
/>
) : null}
{/*setNewSelectedAction !== undefined ?
<Select
MenuProps={{
disableScrollLock: true,
}}
value={selectedAction.name}
fullWidth
onChange={setNewSelectedAction}
style={{backgroundColor: theme.palette.inputColor, color: "white", height: 50, borderRadius: theme.palette.borderRadius,}}
SelectDisplayProps={{
style: {
marginLeft: 10,
maxHeight: 200,
}
}}
>
{sortByKey(selectedApp.actions, "label").map(data => {
var newActionname = data.name
if (data.label !== undefined && data.label !== null && data.label.length > 0) {
newActionname = data.label
}
const iconInfo = GetIconInfo({"name": data.name})
const useIcon = iconInfo.originalIcon
// ROFL FIXME - loop
newActionname = newActionname.replaceAll("_", " ")
newActionname = newActionname.charAt(0).toUpperCase()+newActionname.substring(1)
return (
<MenuItem key={data.name} style={{maxWidth: 400, overflowX: "hidden", backgroundColor: theme.palette.inputColor, color: "white", display: "flex",}} value={data.name}>
<span style={{marginRight: 10, marginTop: "auto", marginBottom: "auto",}}>{useIcon}</span>
<span style={{}}>{newActionname}</span>
</MenuItem>
)
})}
</Select>
: null*/}
<div
style={{
marginTop: "10px",
borderColor: "white",
borderWidth: "2px",
marginBottom: hideExtraTypes ? 50 : 200,
}}
> {
Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0 ?
<div style={{ marginTop: hideExtraTypes ? 10 : 30 }}>
{isIntegration ?
apps !== undefined && apps !== null && apps.length > 0 ?
<div style={{display: "flex", maxWidth: 335, overflowX: "auto", overflowY: "hidden",}}>
<div onClick={() => {
selectedAction.example = "noapp"
selectedAction.large_image = newimage
if (cy !== undefined && cy !== null) {
const foundnode = cy.getElementById(selectedAction.id)
if (foundnode !== undefined && foundnode !== null) {
foundnode.data("large_image", newimage)
}
}
const iconInfo = GetIconInfo(selectedAction)
if (iconInfo !== undefined && iconInfo !== null) {
selectedAction.fillGradient = iconInfo.fillGradient
selectedAction.iconBackground = iconInfo.iconBackgroundColor
selectedAction.fillstyle = "linear-gradient"
}
if (paramIndex === -1) {
console.log("Couldn't find app_name parameter")
selectedAction.parameters.push({
name: "app_name",
value: wrapperapp.name,
autocompleted: false,
})
} else {
selectedAction.parameters[paramIndex].value = wrapperapp.name
}
setSelectedAction(selectedAction)
setUpdate(Math.random())
}}>
<Tooltip title={"Unselect which App to use"} placement="top">
<div style={{textAlign: "center", }}>
<img
src={wrapperapp.large_image}
style={{
minWidth: 30,
maxWidth: 30,
minHeight: 30,
maxHeight: 30,
marginRight: 5,
borderRadius: 5,
cursor: "pointer",
border: noAppSelected ? "3px solid #86c142" : "2px solid rgba(255,255,255,0.6)",
paddingLeft: 5,
paddingTop: 5,
}} />
</div>
</Tooltip>
</div>
{apps.map((app, appIndex) => {
if (app.categories === undefined || app.categories === null || app.categories.length === 0) {
return null
}
var found = false
for (var key in app.categories) {
if (app.categories[key].toLowerCase() !== actionname) {
continue
}
found = true
break
}
if (!found) {
return null
}
var isAppSelected = false
const paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name")
if (paramIndex > -1) {
// Check the actual value and if it's the same
if (selectedAction.parameters[paramIndex].value === app.name) {
isAppSelected = true
}
}
return (
<div onClick={() => {
selectedAction.example = ""
selectedAction.large_image = app.large_image
if (cy !== undefined && cy !== null) {
const foundnode = cy.getElementById(selectedAction.id)
if (foundnode !== undefined && foundnode !== null) {
foundnode.data("large_image", app.large_image)
}
}
if (paramIndex === -1) {
console.log("Couldn't find app_name parameter")
selectedAction.parameters.push({
name: "app_name",
value: app.name,
autocompleted: false,
})
} else {
selectedAction.parameters[paramIndex].value = app.name
}
setSelectedAction(selectedAction)
setUpdate(Math.random())
}}>
<Tooltip title={`Select ${app.name.replaceAll("_", " ")}`} placement="top">
<img
src={app.large_image}
style={{
width: 35,
height: 35,
marginRight: 5,
borderRadius: 5,
cursor: "pointer",
border: isAppSelected ? "3px solid #86c142" : "2px solid rgba(255,255,255,0.6)",
}} />
</Tooltip>
</div>
)
})}
</div>
: null
:
<Tooltip
color="secondary"
title={"Click to learn more about this action"}
placement="top"
>
<Button
variant="text"
color="secondary"
style={{justifyContent: "flex-start", textAlign: "left", textTransform: "none", width: "100%",}}
fullWidth
disabled={selectedAction.description === undefined || selectedAction.description === null || selectedAction.description.length === 0}
onClick={() => {
setHiddenDescription(!hiddenDescription)
}}
>
<b>Parameters</b>
</Button>
</Tooltip>
}
{selectedAction.template === true && selectedAction.matching_actions !== undefined && selectedAction.matching_actions !== null && selectedAction.matching_actions.length > 0 ?
<div>
<Typography variant="body1">
Select an app you want to use
</Typography>
<Autocomplete
id="template_action_search"
autoHighlight
value={selectedAction}
classes={{ inputRoot: classes.inputRoot }}
ListboxProps={{
style: {
backgroundColor: theme.palette.inputColor,
color: "white",
},
}}
getOptionLabel={(option) => {
console.log("LABEL: ", option)
if (
option === undefined ||
option === null ||
option.app_name === undefined ||
option.app_name === null
) {
return null;
}
const newname = (
option.app_name.charAt(0).toUpperCase() + option.app_name.substring(1)
).replaceAll("_", " ");
return newname;
}}
options={selectedAction.matching_actions}
fullWidth
style={{
backgroundColor: theme.palette.inputColor,
height: 50,
borderRadius: theme.palette.borderRadius,
}}
onChange={(event, newValue) => {
console.log("SELECT: ", event, newValue)
// Workaround with event lol
//if (newValue !== undefined && newValue !== null) {
// setNewSelectedAction({ target: { value: newValue.name } });
//}
}}
renderOption={(props, data, state) => {
var newActionname = data.app_name;
if (
data.label !== undefined &&
data.label !== null &&
data.label.length > 0
) {
newActionname = data.label;
}
const iconInfo = GetIconInfo({ name: data.app_name });
const useIcon = iconInfo.originalIcon;
console.log("Actionname 1: ", newActionname)
newActionname = (
newActionname.charAt(0).toUpperCase() +
newActionname.substring(1)
).replaceAll("_", " ");
return (
<div style={{ display: "flex" }}>
<span
style={{
marginRight: 10,
marginTop: "auto",
marginBottom: "auto",
}}
>
{useIcon}
</span>
<span style={{}}>{newActionname}</span>
</div>
);
}}
renderInput={(params) => {
if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) {
const prefixes = ["Post", "Put", "Patch"]
for (let [key,keyval] in Object.entries(prefixes)) {
if (params.inputProps.value.startsWith(prefixes[key])) {
params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1)
if (params.inputProps.value.length > 1) {
params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1)
}
break
}
}
}
return (
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
{...params}
label="Find App to Translate"
variant="outlined"
/>
);
}}
/>
</div>
: null}
{selectedAction.description !== undefined && selectedAction.description !== null && selectedAction.description.length > 0 && hiddenDescription === false ? (
<div
style={{
border: "1px solid rgba(255,255,255,0.6)",
borderRadius: theme.palette.borderRadius,
marginTop: 15,
marginBottom: 10,
maxHeight: 70,
overflow: "auto",
padding: 15,
}}
>
<Typography style={{}}>
<b>Description</b>
</Typography>
<Typography style={{}}>
{selectedAction.description}
</Typography>
</div>
) : null}
{suggestionInfo()}
{selectedActionParameters.map((data, count) => {
if (data.variant === "") {
data.variant = "STATIC_VALUE";
}
if (isIntegration && data.name === "app_name") {
return null
}
// selectedAction.selectedAuthentication = e.target.value
// selectedAction.authentication_id = e.target.value.id
if (
!selectedAction.auth_not_required &&
selectedAction.selectedAuthentication !== undefined &&
selectedAction.selectedAuthentication.fields !== undefined &&
selectedAction.selectedAuthentication.fields[data.name] !==
undefined
) {
// This sets the placeholder in the frontend. (Replaced in backend)
selectedActionParameters[count].value =
selectedAction.selectedAuthentication.fields[data.name];
selectedAction.parameters[count].value =
selectedAction.selectedAuthentication.fields[data.name];
setSelectedAction(selectedAction);
//setUpdate(Math.random())
if (authWritten) {
return null;
}
authWritten = true;
return (
<Typography
key={count}
id="skip_auth"
variant="body2"
color="textSecondary"
style={{ marginTop: 5 }}
>
Authentication fields are hidden
</Typography>
);
}
// Added autofill to make this ALOT simpler
if (isCloud && (selectedAction.app_name === "Shuffle Tools" || selectedAction.app_name === "email") && (selectedAction.name === "send_email_shuffle" || selectedAction.name === "send_sms_shuffle") && data.name === "apikey") {
if (selectedActionParameters[count].length === 0) {
selectedAction.parameters[count].value = "TMP: Will be replaced during execution if cloud"
setSelectedAction(selectedAction)
}
return null
}
var staticcolor = "inherit";
var actioncolor = "inherit";
var varcolor = "inherit";
var multiline
if (
data.multiline !== undefined &&
data.multiline !== null &&
data.multiline === true
) {
multiline = true;
}
// make data.value from array to comma separated string if it is an array
if (data.value !== undefined && data.value !== null && Array.isArray(data.value)) {
data.value = data.value.join(",")
}
if (
data.value !== undefined &&
data.value !== null &&
data.value.startsWith("{") &&
data.value.endsWith("}")
) {
multiline = true;
}
var placeholder = "Value";
if (data.example !== undefined && data.example !== null && data.example.length > 0) {
placeholder = data.example;
// if (data.name === "url") {
// data.value = data.example;
// }
// In case of data.example
if (data.value === undefined || data.value === null) {
data.value = ""
}
if (data.value.length === 0) {
if (data.name.toLowerCase() === "headers") {
console.log("Should show headers field instead with + and -!")
// Check if file ID exists
//
const fileFound = selectedActionParameters.find(param => param.name === "file_id")
if (fileFound === undefined || fileFound === null) {
data.value = data.example
} else {
// Purposely unset it if set by default when using files
data.value = ""
}
}
}
/*
if (data.name !== "queries" && data.name !== "key" && data.name !== "value" ) {
data.value = data.example
}
}
*/
}
if (selectedAction.name === "custom_action" && data.name === "body") {
for (var key in selectedActionParameters) {
const param = selectedActionParameters[key]
if (param.name === "method") {
if (param.value === "GET") {
return null
}
}
}
}
if (data.name.startsWith("${") && data.name.endsWith("}")) {
const paramcheck = selectedAction.parameters.find((param) => param.name === "body");
if (paramcheck !== undefined && paramcheck !== null) {
if (
paramcheck["value_replace"] !== undefined &&
paramcheck["value_replace"] !== null
) {
//console.log("IN THE VALUE REPLACE: ", paramcheck["value_replace"])
const subparamindex = paramcheck["value_replace"].findIndex(
(param) => param.key === data.name
);
if (subparamindex !== -1) {
data.value =
paramcheck["value_replace"][subparamindex]["value"];
}
}
}
}
var showCacheConfig = false
if (data.name === "key" && selectedAction.name.includes("cache") && selectedAction.app_name === "Shuffle Tools") {
// Show a key popout button
showCacheConfig = true
}
var disabled = false;
var rows = "3";
var openApiHelperText = "This is an OpenAPI specific field";
if (selectedApp.generated && data.name === "headers") {
//console.log("HEADER: ", data)
//if (data.value.length === 0) {
//}
//setSelectedActionParameters(selectedActionParameters)
}
var hideBodyButton = "";
const hideBodyButtonValue = (
<div
key={data.name}
id="hide_body_button"
style={{
marginTop: 50,
border: "1px solid rgba(255,255,255,0.7)",
borderTop: "1px solid rgba(255,255,255,0.7)",
borderRadius: theme.palette.borderRadius,
alignItems: "center",
textAlign: "center",
}}
>
<Tooltip
color="secondary"
title={hideBody ? "Hide all body fields and only show the body itself" : "Show all body fields instead of the body itself"}
placement="top"
>
<FormControlLabel
control={
<Checkbox
tabIndex="-1"
checked={hideBody}
style={{
color: theme.palette.primary.secondary,
}}
onChange={(event) => {
var tag = "TOGGLED"
if (hideBody) {
tag = "UNTOGGLED"
}
setHideBody(!hideBody)
for (let paramkey in Object.entries(selectedActionParameters)) {
var currentItem = selectedActionParameters[paramkey];
if (currentItem.name === "ssl_verify") {
}
if (currentItem.name === "body") {
currentItem.id = tag
}
if (currentItem.description === openApiFieldDesc) {
currentItem.field_active = !hideBody
}
}
// Scroll to hide_body_button
setTimeout(() => {
var element = document.getElementById("hide_body_button")
if (element !== undefined && element !== null) {
// Keep the button a little below the top
element.scrollIntoView({
behavior: "smooth",
block: "center",
})
}
}, 100)
}}
name="requires_unique"
/>
}
label={hideBody ? "Show Body" : "Hide Body"}
/>
</Tooltip>
</div>
)
if (selectedApp.generated && data.name === "body") {
const regex = /\${(\w+)}/g;
const found = placeholder.match(regex);
// setActivateHidingBodyButton(false)
//
hideBodyButton = hideBodyButtonValue;
if (found === null || !hideBody) {
if (found === null) {
setActivateHidingBodyButton(true);
} else {
//console.log("In found: ", found, hideBody)
}
} else {
rows = "1";
disabled = true;
openApiHelperText = "OpenAPI spec: fill the following fields.";
var changed = false;
var tempArray = []
for (let specKey in found) {
const tmpitem = found[specKey];
var skip = false;
for (let innerkey in selectedActionParameters) {
if (selectedActionParameters[innerkey].name === tmpitem) {
skip = true;
break;
}
}
if (skip) {
//console.log("SKIPPING ", tmpitem)
continue;
}
changed = true;
var isRequired = false
// Check if original field name is in the selectedAction.required_body_fields
if (selectedAction.required_body_fields !== undefined && selectedAction.required_body_fields !== null) {
for (let innerkey in selectedAction.required_body_fields) {
if (selectedAction.required_body_fields[innerkey] === tmpitem) {
isRequired = true
break
}
}
}
tempArray.push({
action_field: "",
configuration: false,
description: openApiFieldDesc,
example: "",
id: "",
multiline: true,
name: tmpitem,
options: null,
required: isRequired,
schema: { type: "string" },
skip_multicheck: false,
tags: null,
value: "",
variant: "STATIC_VALUE",
field_active: true,
autocompleted: true,
});
}
console.log("TEMP ARRAY: ", tempArray)
var required = selectedActionParameters.filter(item => item.required === true)
var notRequired = selectedActionParameters.filter(item => item.required === false)
if (tempArray.length > 0) {
// Sort tempArray based on tempArray.required
tempArray.sort((a, b) => (a.required < b.required) ? 1 : -1)
// Add all items to the selectedActionParameters array
for (let innerkey in tempArray) {
tempArray[innerkey].id = "ADDED"
if (tempArray[innerkey].required === true) {
required.push(tempArray[innerkey])
} else {
notRequired.push(tempArray[innerkey])
}
}
}
//selectedActionParameters
if (changed) {
// Sort selectedActionParameters based on selectedActionParameters.required
//selectedActionParameters.sort((a, b) => (a.required < b.required) ? 1 : -1)
// Find the "headers" and "queries" field names and put them on the first indexes anyway
var newArray = required.concat(notRequired)
setSelectedActionParameters(newArray)
}
return hideBodyButton;
}
}
if (activateHidingBodyButton === true) {
hideBodyButton = "";
}
const clickedFieldId = "rightside_field_" + count;
var baseHelperText = ""
if (data !== undefined && data !== null && data.value !== undefined && data.value !== null && data.value.length > 0) {
baseHelperText = calculateHelpertext(data.value)
}
var tmpitem = data.name.valueOf();
if (data.name.startsWith("${") && data.name.endsWith("}")) {
tmpitem = tmpitem.slice(2, data.name.length - 1);
}
if (tmpitem === "from_shuffle") {
tmpitem = "from"
}
tmpitem = (
tmpitem.charAt(0).toUpperCase() + tmpitem.substring(1)
).replaceAll("_", " ");
if (tmpitem === "Username basic") {
tmpitem = "Username"
} else if (tmpitem === "Password basic") {
tmpitem = "Password"
}
multiline = data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline
var datafield = (
<TextField
disabled={disabled}
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
border:
selectedActionParameters[count].required ||
selectedActionParameters[count].configuration
? "2px solid #f85a3e"
: "",
color: "white",
width: "100%",
fontSize: "1em",
}}
InputProps={{
disableUnderline: true,
endAdornment: hideExtraTypes ? null : (
<InputAdornment position="end">
<ButtonGroup orientation={multiline ? "vertical" : "horizontal"}>
<Tooltip title="Expand window" placement="top">
<AspectRatioIcon
style={{ cursor: "pointer", margin: multiline ? 5 : 0 ,}}
onClick={(event) => {
event.preventDefault()
setFieldCount(count)
setExpansionModalOpen(true)
//setcodedata(data.value)
var parsedvalue = data.value
if (parsedvalue === undefined || parsedvalue === null) {
parsedvalue = ""
}
setEditorData({
"name": data.name,
"value": parsedvalue,
"field_number": count,
"actionlist": actionlist,
"field_id": clickedFieldId,
})
}}
/>
</Tooltip>
<Tooltip title="Autocomplete text" placement="bottom">
<AddCircleOutlineIcon
style={{ cursor: "pointer", margin: multiline ? 5 : 0, }}
onClick={(event) => {
event.preventDefault()
// Get cursor position
// This makes it so we can put it in the right location?
setMenuPosition({
top: event.pageY + 10,
left: event.pageX + 10,
});
setShowDropdownNumber(count);
setShowDropdown(true);
setShowAutocomplete(true);
}}
/>
</Tooltip>
</ButtonGroup>
</InputAdornment>
),
}}
multiline={data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline}
onClick={() => {
/*
setExpansionModalOpen(false);
*/
if (setScrollConfig !== undefined && scrollConfig !== null && scrollConfig !== undefined && scrollConfig.selected !== clickedFieldId) {
scrollConfig.selected = clickedFieldId
setScrollConfig(scrollConfig)
}
}}
id={clickedFieldId}
rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows}
color="primary"
// defaultValue={data.value}
value={data.value}
//options={{
// theme: 'gruvbox-dark',
// keyMap: 'sublime',
// mode: 'python',
//}}
//height={multiline ? 50 : 150}
type={
placeholder.includes("***") ||
(data.configuration &&
(data.name.toLowerCase().includes("api") ||
data.name.toLowerCase().includes("key") ||
data.name.toLowerCase().includes("pass")))
? "password"
: "text"
}
placeholder={placeholder}
onChange={(event) => {
//changeActionParameterCodemirror(event, count, data)
// changeActionParameter(event, count, data);
handleParamChange(event, count, data)
}}
helperText={returnHelperText(data.name, data.value)}
onBlur={(event) => {
baseHelperText = calculateHelpertext(event.target.value)
if (setLastSaved !== undefined) {
setLastSaved(false)
}
}}
/>
);
// Finds headers from a string to be used for autocompletion
const findHeaders = (inputdata) => {
var splitdata = inputdata.split("\n")
var foundnewline = false
var allValues = []
for (let [key,keyval] in Object.entries(splitdata)) {
const line = splitdata[key]
if (line === "") {
foundnewline = true
continue
}
var splitvalue = ""
if (line.includes(":")) {
splitvalue = ":"
}
if (line.includes("=")) {
splitvalue = "="
}
if (splitvalue.length === 0){
allValues.push({
key: line,
value: "",
})
continue
}
var splitKeys = line.split(splitvalue)
if (splitKeys.length > 1) {
allValues.push({
key: splitKeys[0].trim(),
value: splitKeys[1].trim(),
})
} else {
console.log("No keys for ", line)
}
}
// Just add one
if (foundnewline) {
allValues.push({
key: "",
value: "",
})
}
return allValues
}
if (data.name.toLowerCase() === "headers") {
//var tmpheaders = findHeaders(data.value)
var tmpheaders = findHeaders(selectedActionParameters[count].value)
const tmpdatafield =
<div>
{tmpheaders.map((inputdata, index) => {
const oldkey = inputdata.key
const oldval = inputdata.value
return (
<span key={index}>
<div style={{display: "flex"}}>
<TextField
placeholder="Key"
style={{flex: 5}}
defaultValue={inputdata.key}
onBlur={(e) => {
console.log("Change from oldkey to new: ", oldkey, e.target.value)
// Find the right line to replace!
//const newval = selectedActionParameters[count].value.replace(oldval, e.target.value, 1)
const tmpsplit = selectedActionParameters[count].value.split("\n")
var valsplit = []
var add_empty = false
for (let [key,keyval] in Object.entries(tmpsplit)) {
if (tmpsplit[key] === "") {
add_empty = true
continue
}
valsplit.push(tmpsplit[key])
}
if (add_empty) {
valsplit.push("")
}
console.log("Split: ", valsplit)
var newarr = []
for (let [key,keyval] in Object.entries(valsplit)) {
var line = valsplit[key]
if (key == index) {
if (oldkey === "") {
if (line.includes("=") || line.includes(":")) {
newarr.push(e.target.value + line)
} else {
newarr.push(e.target.value + ": " + line)
}
} else {
newarr.push(line.replace(oldkey, e.target.value, 1))
}
} else {
newarr.push(line)
}
}
var newval = newarr.join("\n")
console.log("Fixed: ", newval)
selectedActionParameters[count].value = newval
selectedAction.parameters[count].value = newval
setSelectedAction(selectedAction)
setSelectedActionParameters(selectedActionParameters)
}}
/>
<TextField
placeholder="Value"
style={{flex: 6}}
defaultValue={inputdata.value}
onBlur={(e) => {
console.log("Change from oldval to new: ", oldval, e.target.value)
// Find the right line to replace!
//const newval = selectedActionParameters[count].value.replace(oldval, e.target.value, 1)
var tmpsplit = selectedActionParameters[count].value.split("\n")
var valsplit = []
var add_empty = false
for (let [key,keyval] in Object.entries(tmpsplit)) {
if (tmpsplit[key] === "") {
add_empty = true
continue
}
valsplit.push(tmpsplit[key])
}
if (add_empty) {
valsplit.push("")
}
console.log("Split: ", valsplit)
var newarr = []
for (let [key,keyval] in Object.entries(valsplit)) {
var line = valsplit[key]
if (key == index) {
if (oldval === "") {
if (line.includes("=") || line.includes(":")) {
newarr.push(line + e.target.value)
} else {
newarr.push(line + ": " + e.target.value)
}
} else {
newarr.push(line.replace(oldval, e.target.value, 1))
}
} else {
newarr.push(line)
}
}
var newval = newarr.join("\n")
console.log("Fixed: ", newval)
selectedActionParameters[count].value = newval
selectedAction.parameters[count].value = newval
setSelectedAction(selectedAction)
setSelectedActionParameters(selectedActionParameters)
}}
/>
</div>
</span>
)
})}
<Button variant="outlined" fullWidth onClick={() => {
console.log("Add header")
selectedActionParameters[count].value += "\n"
selectedAction.parameters[count].value += "\n"
setSelectedActionParameters(selectedActionParameters)
setSelectedAction(selectedAction)
setUpdate(Math.random())
}}>
<AddIcon style={{ marginRight: 10 }} /> Add header
</Button>
</div>
}
//console.log("FIELD VALUE: ", data.value)
//const regexp = new RegExp("\W+\.", "g")
//let match
//while ((match = regexp.exec(data.value)) !== null) {
// console.log(`Found ${match[0]} start=${match.index} end=${regexp.lastIndex}.`);
//}
//const str = = data.value.search(submatch)
//console.log("FOUND? ", n)
//for (var key in keywords) {
// const keyword = keywords[key]
// if (data.value.includes(keyword)) {
// console.log("INCLUDED: ", keyword)
// }
//}
// Basic helpertext
if (files !== undefined && files !== null && data.name.toLowerCase() === "file_category") {
//selectedActionParameters[count].options.length > 0
console.log("FileS: ", files)
if (files.namespaces !== undefined && files.namespaces !== null && files.namespaces.length > 0) {
data.options = files.namespaces
}
}
//const keywords = ["len", "lower", "upper", "trim", "split", "length", "number", "parse", "join"]
if (
selectedActionParameters[count].schema !== undefined &&
selectedActionParameters[count].schema !== null &&
selectedActionParameters[count].schema.type === "file"
) {
datafield = (
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
InputProps={{
endAdornment: hideExtraTypes ? null : (
<InputAdornment position="end">
<Tooltip title="Autocomplete text" placement="top">
<AddCircleOutlineIcon
style={{ cursor: "pointer" }}
onClick={(event) => {
setMenuPosition({
top: event.pageY + 10,
left: event.pageX + 10,
});
setShowDropdownNumber(count);
setShowDropdown(true);
setShowAutocomplete(true);
}}
/>
</Tooltip>
</InputAdornment>
),
}}
helperText={returnHelperText(data.name, data.value)}
fullWidth
multiline={multiline}
rows={"3"}
color="primary"
defaultValue={data.value}
type={"text"}
placeholder={"The file ID to get"}
id={"rightside_field_" + count}
onChange={(event) => {
changeActionParameter(event, count, data);
}}
onBlur={(event) => {}}
/>
);
//const fileId = "6daabec1-892b-469c-b603-c902e47223a9"
//datafield = `SHOW FILES FROM OTHER NODES? Filename: ${selectedActionParameters[count].value}`
/*
if (selectedActionParameters[count].value != fileId) {
changeActionParameter(fileId, count, data)
setUpdate(Math.random())
}
*/
} else if (
(data.options !== undefined &&
data.options !== null &&
data.options.length > 0)
||
(selectedActionParameters[count].options !== undefined &&
selectedActionParameters[count].options !== null &&
selectedActionParameters[count].options.length > 0)
) {
const parsedoptions = data.options !== undefined && data.options !== null && data.options.length > 0 ? data.options : selectedActionParameters[count].options
if (selectedActionParameters[count].value === "") {
// && selectedActionParameters[count].required) {
// Rofl, dirty workaround :)
const e = {
target: {
value: parsedoptions[0],
},
};
changeActionParameter(e, count, data);
}
var multi = false
if (selectedActionParameters[count].multiselect !== undefined && selectedActionParameters[count].multiselect !== null && selectedActionParameters[count].multiselect === true) {
multi = true
selectedActionParameters[count].value = selectedActionParameters[count].value.split(",")
}
datafield = (
<Select
MenuProps={{
disableScrollLock: true,
}}
SelectDisplayProps={{
style: {
},
}}
multiple={multi}
value={selectedActionParameters[count].value}
fullWidth
id={"rightside_field_" + count}
onChange={(e) => {
console.log("MULTI SELECT: ", multi, e.target.value)
changeActionParameter(e, count, data);
setUpdate(Math.random());
}}
style={{
backgroundColor: theme.palette.surfaceColor,
color: "white",
height: "50px",
borderRadius: theme.palette.borderRadius,
}}
>
{parsedoptions.map(
(data, index) => {
const split_data = data.split("||");
var viewed_data = data
if (split_data.length > 1) {
viewed_data = split_data[0]
}
viewed_data = (viewed_data.charAt(0).toUpperCase() + viewed_data.slice(1)).replaceAll("_", " ")
return (
<MenuItem
key={data}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value={data}
>
{viewed_data}
</MenuItem>
);
}
)}
</Select>
);
} else if (data.variant === "STATIC_VALUE") {
staticcolor = "#f85a3e";
}
if (data.field_active === false) {
return null;
}
// Shows nested list of nodes > their JSON lists
const ActionlistWrapper = (props) => {
const handleMenuClose = () => {
setShowAutocomplete(false);
if (
!selectedActionParameters[count].value[
selectedActionParameters[count].value.length - 1
] === "$"
) {
setShowDropdown(false);
}
setUpdate(Math.random());
setMenuPosition(null);
};
const handleItemClick = (values) => {
console.log("In normal itemclick")
if (values === undefined ||values === null ||values.length === 0) {
return;
}
var toComplete = selectedActionParameters[count].value.trim()
.endsWith("$")
? values[0].autocomplete
: "$" + values[0].autocomplete;
toComplete = toComplete.toLowerCase().replaceAll(" ", "_");
for (let [key,keyval] in Object.entries(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;
}
}
selectedActionParameters[count]["value_replace"] = paramcheck;
selectedAction.parameters[count]["value_replace"] = paramcheck;
setSelectedAction(selectedAction);
setUpdate(Math.random());
setShowDropdown(false);
setMenuPosition(null);
return;
}
}
console.log("In nestedclick!!")
var newValue = selectedActionParameters[count].value + toComplete
changeActionParameter({target: {value: newValue}}, count, data, true)
//selectedActionParameters[count].value += toComplete;
//selectedAction.parameters[count].value = selectedActionParameters[count].value;
//setSelectedAction(selectedAction);
//setUpdate(Math.random());
setShowDropdown(false);
setMenuPosition(null);
};
const iconStyle = {
marginRight: 15,
};
return (
<Menu
anchorReference="anchorPosition"
anchorPosition={menuPosition}
onClose={() => {
handleMenuClose();
}}
open={!!menuPosition}
style={{
color: "white",
marginTop: 2,
maxHeight: 650,
}}
>
{actionlist.map((innerdata) => {
const icon =
innerdata.type === "action" ? (
<AppsIcon style={{ marginRight: 10 }} />
) : innerdata.type === "workflow_variable" ||
innerdata.type === "execution_variable" ? (
<FavoriteBorderIcon style={{ marginRight: 10 }} />
) : (
<ScheduleIcon style={{ marginRight: 10 }} />
);
const handleExecArgumentHover = (inside) => {
var exec_text_field = document.getElementById(
"execution_argument_input_field"
);
if (exec_text_field !== null) {
if (inside) {
exec_text_field.style.border = "2px solid #f85a3e";
} else {
exec_text_field.style.border = "";
}
}
// Also doing arguments
if (
workflow.triggers !== undefined &&
workflow.triggers !== null &&
workflow.triggers.length > 0
) {
for (let [key,keyval] in Object.entries(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, "");
}
const coverColor = "#82ccc3"
//menuPosition.left -= 50
//menuPosition.top -= 250
//console.log("POS: ", menuPosition1)
var menuPosition1 = menuPosition
if (menuPosition1 === null) {
menuPosition1 = {
"left": 0,
"top": 0,
}
} else if (menuPosition1.top === null || menuPosition1.top === undefined) {
menuPosition1.top = 0
} else if (menuPosition1.left === null || menuPosition1.left === undefined) {
menuPosition1.left = 0
}
//console.log("POS1: ", menuPosition1)
return parsedPaths.length > 0 ? (
<NestedMenuItem
key={innerdata.name}
label={
<div style={{ display: "flex", marginLeft: 0, }}>
{icon} {innerdata.name}
</div>
}
parentMenuOpen={!!menuPosition}
style={{
color: "white",
minWidth: 250,
maxWidth: 250,
maxHeight: 50,
overflow: "hidden",
}}
onClick={() => {
console.log("CLICKED: ", innerdata);
console.log(innerdata.example)
handleItemClick([innerdata]);
}}
>
<Paper style={{minHeight: 500, maxHeight: 500, minWidth: 275, maxWidth: 275, position: "fixed", top: menuPosition1.top-200, left: menuPosition1.left-450, padding: "10px 0px 10px 10px", overflow: "hidden", overflowY: "auto", border: "1px solid rgba(255,255,255,0.3)",}}>
<MenuItem
key={innerdata.name}
style={{
marginLeft: 15,
color: "white",
minWidth: 250,
maxWidth: 250,
padding: 0,
position: "relative",
}}
value={innerdata}
onMouseOver={() => {
//console.log("HOVER: ", pathdata);
}}
onClick={() => {
handleItemClick([innerdata]);
}}
>
<Typography variant="h6" style={{paddingBottom: 5}}>
{innerdata.name}
</Typography>
</MenuItem>
{parsedPaths.map((pathdata, index) => {
// FIXME: Should be recursive in here
//<VpnKeyIcon style={iconStyle} />
const icon =
pathdata.type === "value" ? (
<span style={{marginLeft: 9, }} />
) : pathdata.type === "list" ? (
<FormatListNumberedIcon style={{marginLeft: 9, marginRight: 10, }} />
) : (
<CircleIcon style={{marginLeft: 9, marginRight: 10, color: coverColor}}/>
);
//<ExpandMoreIcon style={iconStyle} />
const indentation_count = (pathdata.name.match(/\./g) || []).length+1
const baseIndent = <div style={{marginLeft: 20, height: 30, width: 1, backgroundColor: coverColor,}} />
//const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0
const boxPadding = 0
const namesplit = pathdata.name.split(".")
const newname = namesplit[namesplit.length-1]
return (
<MenuItem
key={pathdata.name}
style={{
color: "white",
minWidth: 250,
maxWidth: 250,
padding: boxPadding,
}}
value={pathdata}
onMouseOver={() => {
//console.log("HOVER: ", pathdata);
}}
onClick={() => {
handleItemClick([innerdata, pathdata]);
}}
>
<Tooltip
color="primary"
title={`Ex. value: ${pathdata.value}`}
placement="left"
>
<div style={{ display: "flex", height: 30, }}>
{Array(indentation_count).fill().map((subdata, subindex) => {
return (
baseIndent
)
})}
{icon} {newname}
{pathdata.type === "list" ? <SquareFootIcon style={{marginleft: 10, }} onClick={(e) => {
e.preventDefault()
e.stopPropagation()
console.log("INNER: ", innerdata, pathdata)
// Removing .list from autocomplete
var newname = pathdata.name
if (newname.length > 5) {
newname = newname.slice(0, newname.length-5)
}
selectedActionParameters[count].value += `{{ $${innerdata.name}.${newname} | size }}`
selectedAction.parameters[count].value = selectedActionParameters[count].value;
setSelectedAction(selectedAction);
setUpdate(Math.random());
setShowDropdown(false);
setMenuPosition(null);
}} /> : null}
</div>
</Tooltip>
</MenuItem>
);
})}
</Paper>
</NestedMenuItem>
) : (
<MenuItem
key={innerdata.name}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
minWidth: 250,
maxWidth: 250,
marginRight: 0,
}}
value={innerdata}
onMouseOver={() => handleMouseover()}
onMouseOut={() => {
handleMouseOut();
}}
onClick={() => {
handleItemClick([innerdata]);
}}
>
<Tooltip
color="primary"
title={`Value: ${innerdata.value}`}
placement="left"
>
<div style={{ display: "flex" }}>
{icon} {innerdata.name}
</div>
</Tooltip>
</MenuItem>
);
})}
</Menu>
);
};
const description =
data.description === undefined ? "" : data.description;
const tooltipDescription = (
<span>
<Typography variant="body2">
- Required:{" "}
{data.required === true || data.configuration === true
? "True"
: "False"}
</Typography>
<Typography variant="body2">
- Example: {data.example}
</Typography>
<Typography variant="body2">
- Description: {description}
</Typography>
</span>
);
//var itemColor = "#f85a3e"
//if (!data.required) {
// itemColor = "#ffeb3b"
//}
{
/*<div style={{width: 17, height: 17, borderRadius: 17 / 2, backgroundColor: itemColor, marginRight: 10, marginTop: 2, marginTop: "auto", marginBottom: "auto",}}/>*/
}
const buttonTitle = `Authenticate ${selectedApp.name.replaceAll("_", " ")}`
const hasAutocomplete = data.autocompleted === true
return (
<div key={data.name}>
{hideBodyButton}
<div
style={{ marginTop: 20, marginBottom: 0, display: "flex" }}
>
{data.configuration === true ? (
<Tooltip
title={buttonTitle}
placement="top"
>
<LockOpenIcon
style={{
cursor: "pointer",
width: 24,
height: 24,
marginRight: 10,
color: "rgba(255,255,255,0.6)",
}}
onClick={() => {
setAuthenticationModalOpen(true);
}}
/>
</Tooltip>
) : null}
{hasAutocomplete === true ?
<Tooltip
color="primary"
title={"Field was autocompleted by Shuffle based on previous actions (same fields or parent nodes)"}
placement="top"
>
<AutoFixHighIcon style={{
color: "rgba(255,255,255,0.7)" ,
marginRight: 10,
}}/>
</Tooltip>
:
null}
{showCacheConfig === true ?
<Tooltip
color="primary"
title={"Explore your keys in Datastore"}
placement="top"
>
<a href="/admin?tab=cache" target="_blank" style={{textDecoration: "none"}}>
<StorageIcon style={{
color: "#f85a3e",
marginRight: 10,
}}/>
</a>
</Tooltip>
: null}
<div
style={{
flex: "10",
marginTop: "auto",
marginBottom: "auto",
}}
>
<Tooltip title={tooltipDescription} placement="top">
<b>{tmpitem} </b>
</Tooltip>
</div>
{/*selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 ? null :
<div style={{display: "flex"}}>
<Tooltip color="primary" title="Static data" placement="top">
<div style={{cursor: "pointer", color: staticcolor}} onClick={(e) => {
e.preventDefault()
changeActionParameterVariant("STATIC_VALUE", count)
}}>
<CreateIcon />
</div>
</Tooltip>
&nbsp;|&nbsp;
<Tooltip color="primary" title="Data from previous action" placement="top">
<div style={{cursor: "pointer", color: actioncolor}} onClick={(e) => {
e.preventDefault()
changeActionParameterVariant("ACTION_RESULT", count)
}}>
<AppsIcon />
</div>
</Tooltip>
&nbsp;|&nbsp;
<Tooltip color="primary" title="Use local variable" placement="top">
<div style={{cursor: "pointer", color: varcolor}} onClick={(e) => {
e.preventDefault()
changeActionParameterVariant("WORKFLOW_VARIABLE", count)
}}>
<FavoriteBorderIcon />
</div>
</Tooltip>
</div>
*/}
{/*(selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 && selectedActionParameters[count].required === true && selectedActionParameters[count].unique_toggled !== undefined) || hideExtraTypes ? null :
<div style={{display: "flex"}}>
<Tooltip color="secondary" title="Value must be unique" placement="top">
<div style={{cursor: "pointer", color: staticcolor}} onClick={(e) => {}}>
<Checkbox
tabIndex="-1"
checked={selectedActionParameters[count].unique_toggled}
style={{
color: theme.palette.primary.secondary,
}}
onChange={(event) => {
//console.log("CHECKED!: ", selectedActionParameters[count])
selectedActionParameters[count].unique_toggled = !selectedActionParameters[count].unique_toggled
selectedAction.parameters[count].unique_toggled = selectedActionParameters[count].unique_toggled
setSelectedActionParameters(selectedActionParameters)
setSelectedAction(selectedAction)
setUpdate(Math.random())
}}
name="requires_unique"
/>
</div>
</Tooltip>
</div>
*/}
</div>
{datafield}
{/*shufflecode*/}
{showDropdown &&
showDropdownNumber === count &&
data.variant === "STATIC_VALUE" &&
jsonList.length > 0 ? (
<FormControl fullWidth style={{ marginTop: 0 }}>
<InputLabel
id="action-autocompleter"
style={{ marginLeft: 10, color: "white" }}
>
Autocomplete
</InputLabel>
<Select
MenuProps={{
disableScrollLock: true,
}}
labelId="action-autocompleter"
SelectDisplayProps={{
style: {
},
}}
onClose={() => {
setShowAutocomplete(false);
if (
!selectedActionParameters[count].value[
selectedActionParameters[count].value.length - 1
] === "."
) {
setShowDropdown(false);
}
setUpdate(Math.random());
}}
onClick={() => {
setShowAutocomplete(true)
}}
fullWidth
open={showAutocomplete}
style={{
color: "white",
height: 50,
marginTop: 2,
borderRadius: theme.palette.borderRadius,
}}
onChange={(e) => {
if (selectedActionParameters[count].value[selectedActionParameters[count].value.length - 1] === ".") {
e.target.value.autocomplete = e.target.value.autocomplete.slice(1,e.target.value.autocomplete.length);
}
selectedActionParameters[count].value += e.target.value.autocomplete;
selectedAction.parameters[count].value = selectedActionParameters[count].value;
setSelectedAction(selectedAction);
setUpdate(Math.random());
setShowDropdown(false);
}}
>
{jsonList.map((data) => {
const iconStyle = {
marginRight: 15,
};
const icon =
data.type === "value" ? (
<VpnKeyIcon style={iconStyle} />
) : data.type === "list" ? (
<FormatListNumberedIcon style={iconStyle} />
) : (
<ExpandMoreIcon style={iconStyle} />
)
return (
<MenuItem
key={data.name}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value={data}
onMouseOver={() => {}}
>
<Tooltip
color="primary"
title={`Ex. value: ${data.value}`}
placement="left"
>
<div style={{ display: "flex" }}>
{icon} {data.name}
</div>
</Tooltip>
</MenuItem>
);
})}
</Select>
</FormControl>
) : null}
{showDropdown &&
showDropdownNumber === count &&
data.variant === "STATIC_VALUE" &&
jsonList.length === 0 ? (
<ActionlistWrapper actionlist={actionlist} />
) : null}
</div>
);
})}
</div>
: null
}
</div>
</div>
</div>
);
};
export default ParsedAction;