{
}}
>
Click one of the relevant public apps below to Activate it for your organization.
{
console.log("CLICKED")
}}>
:
Apps need to be activated before they can be used. Search from our 2500+ apps to activate them for your organisation.
}
) : apps.length > 0 ? (
{
console.log("Should load in extra apps?")
}}
>
Couldn't find the apps you were looking for? Searching unactivated apps. Click one of these apps to Activate it for your organisation.
{
console.log("CLICKED")
}}>
) : (
Loading Apps
)}
);
}
const getNextActionName = (appName) => {
var highest = "";
const allitems = workflow.actions.concat(workflow.triggers);
if (allitems !== undefined && allitems !== null) {
for (let itemkey in allitems) {
const item = allitems[itemkey];
if (item.app_name === appName && item.label !== undefined && item.label !== null) {
var number = item.label.split("_");
if (isNaN(number[-1]) && parseInt(number[number.length - 1]) > highest) {
highest = number[number.length - 1];
}
}
}
}
appName = appName.replaceAll(" ", "_")
if (highest) {
return appName + "_" + (parseInt(highest) + 1);
} else {
return appName + "_" + 1;
}
};
const setNewSelectedAction = (e) => {
if (selectedApp.actions === undefined || selectedApp.actions === null) {
return
}
if (selectedApp.actions.length === 1) {
// Find if there's a new app
const newApp = apps.find((app) => (app.name === selectedApp.name && app.app_version !== selectedApp.app_version) || app.id == selectedApp.id)
if (newApp !== undefined && newApp !== null) {
if (selectedApp.actions !== undefined && selectedApp.actions !== null && selectedApp.actions.length > 1) {
setSelectedApp(newApp)
}
selectedApp.actions = newApp.actions
}
}
const newaction = selectedApp.actions.find((a) => a.name === e.target.value)
if (newaction === undefined || newaction === null) {
toast("Failed to find the action you selected. Please try again or contact support@shuffler.io if it persists.");
return;
}
if (workflow.actions !== undefined && workflow.actions !== null) {
const foundInfo = workflow.actions.find(ac => ac.id === selectedAction.id)
}
// Setting an old reference just to use the same memory space elsewhere
// for selectedAction
const oldaction = JSON.parse(JSON.stringify(selectedAction))
// Does this one find the wrong one?
//var newSelectedAction = JSON.parse(JSON.stringify(selectedAction))
var newSelectedAction = selectedAction
newSelectedAction.name = newaction.name;
newSelectedAction.parameters = JSON.parse(JSON.stringify(newaction.parameters))
newSelectedAction.errors = [];
newSelectedAction.isValid = true;
newSelectedAction.is_valid = true;
newSelectedAction.required_body_fields = newaction.required_body_fields
// Simple action swap autocompleter
if (oldaction.parameters !== undefined && oldaction.parameters !== null && newSelectedAction.parameters !== undefined && oldaction.id === newSelectedAction.id) {
var fileid_found = false
for (let [paramkey,paramkeyval] in Object.entries(oldaction.parameters)) {
const param = oldaction.parameters[paramkey];
if (param.name === "file_id") {
fileid_found = true
}
if (param.value === null || param.value === undefined || param.value.length === 0) {
continue
}
if (param.name === "body") {
//console.log("Param: ", param)
continue
}
if (param.name === "headers") {
if (fileid_found) {
newSelectedAction.parameters[paramkey].value = ""
newSelectedAction.parameters[paramkey].autocompleted = true
continue
}
//newSelectedAction.parameters[newParamIndex].value = param.value
}
if (newSelectedAction.parameters === undefined || newSelectedAction.parameters === null) {
continue
}
// Not doing options fields
const newParamIndex = newSelectedAction.parameters.findIndex(paramdata => paramdata.name === param.name)
if (newParamIndex < 0) {
continue
}
if (newSelectedAction.parameters[newParamIndex].name === "headers") {
if (param.value !== undefined && param.value !== null && param.value.includes("=undefined")) {
if (newSelectedAction.parameters[newParamIndex].example !== undefined && newSelectedAction.parameters[newParamIndex].example !== null) {
newSelectedAction.parameters[newParamIndex].value = newSelectedAction.parameters[newParamIndex].example
} else {
newSelectedAction.parameters[newParamIndex].value = ""
}
continue
}
}
newSelectedAction.parameters[newParamIndex].value = param.value
newSelectedAction.parameters[newParamIndex].autocompleted = true
if (param.options !== undefined && param.options !== null && param.options.length > 0) {
newSelectedAction.parameters[newParamIndex].autocompleted = false
}
}
}
if (newSelectedAction.app_name === "Shuffle Tools") {
const iconInfo = GetIconInfo(newSelectedAction);
const svg_pin = ``;
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
newSelectedAction.large_image = svgpin_Url;
newSelectedAction.fillGradient = iconInfo.fillGradient;
newSelectedAction.fillstyle = "solid";
if (
newSelectedAction.fillGradient !== undefined &&
newSelectedAction.fillGradient !== null &&
newSelectedAction.fillGradient.length > 0
) {
newSelectedAction.fillstyle = "linear-gradient";
} else {
newSelectedAction.iconBackground = iconInfo.iconBackgroundColor;
}
const foundnode = cy.getElementById(newSelectedAction.id);
if (foundnode !== null && foundnode !== undefined) {
foundnode.data(newSelectedAction);
}
}
// Takes an action as input, then runs through and updates the relevant parameters based on previous actions' results (parent nodes)
// Further checks if those fields are already set in a previously used action
newSelectedAction = RunAutocompleter(newSelectedAction);
if (
newaction.return !== undefined &&
newaction.return !== null &&
newaction.returns.example !== undefined &&
newaction.returns.example !== null &&
newaction.returns.example.length > 0
) {
newSelectedAction.example = newaction.returns.example;
}
if (
newaction.description !== undefined &&
newaction.description !== null &&
newaction.description.length > 0
) {
newSelectedAction.description = newaction.description
}
// FIXME - this is broken sometimes lol
//var env = environments.find(a => a.Name === newaction.environment)
//if ((!env || env === undefined) && selectedAction.environment === undefined ) {
// env = environments[defaultEnvironmentIndex]
//}
//setSelectedActionEnvironment(env)
if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
const foundActionIndex = workflow.actions.findIndex(actiondata => actiondata.id === newSelectedAction.id)
if (foundActionIndex >= 0) {
workflow.actions[foundActionIndex] = newSelectedAction
setWorkflow(workflow)
}
}
// Last fix for params
if (newSelectedAction.parameters !== undefined && newSelectedAction.parameters !== null && newSelectedAction.parameters.length > 0) {
for (let paramkey in newSelectedAction.parameters) {
const param = newSelectedAction.parameters[paramkey]
if (param.name !== "body") {
continue
}
if (param.example !== undefined && param.example !== null && param.example.length > 0) {
if (param.value === undefined || param.value === null || param.value.length === 0) {
param.value = param.example
}
}
newSelectedAction.parameters[paramkey] = param
}
}
console.log("New selected action: ", newSelectedAction)
setSelectedAction(newSelectedAction)
setUpdate(Math.random())
const allNodes = cy.nodes().jsons()
if (allNodes !== undefined && allNodes !== null) {
for (let nodekey in allNodes) {
const currentNode = allNodes[nodekey];
if (
currentNode.data.attachedTo === oldaction.id &&
currentNode.data.isDescriptor
) {
const foundnode = cy.getElementById(currentNode.data.id);
if (foundnode !== null && foundnode !== undefined) {
const iconInfo = GetIconInfo(newaction);
const svg_pin = ``;
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
foundnode.data("image", svgpin_Url);
foundnode.data("imageColor", iconInfo.iconBackgroundColor);
}
break;
}
}
}
// Send it in here, after all fields are filled
// Disabled for now :(
// const aiMsg = "Fill based on previous values"
// aiSubmit(aiMsg, undefined, undefined, newSelectedAction)
};
// APPSELECT at top
// appname & version
// description
// ACTION select
const selectedNameChange = (appActionName) => {
appActionName = appActionName.replaceAll("(", "");
appActionName = appActionName.replaceAll(")", "");
appActionName = appActionName.replaceAll("]", "");
appActionName = appActionName.replaceAll("[", "");
appActionName = appActionName.replaceAll("{", "");
appActionName = appActionName.replaceAll("}", "");
appActionName = appActionName.replaceAll("*", "");
appActionName = appActionName.replaceAll("!", "");
appActionName = appActionName.replaceAll("@", "");
appActionName = appActionName.replaceAll("#", "");
appActionName = appActionName.replaceAll("$", "");
appActionName = appActionName.replaceAll("%", "");
appActionName = appActionName.replaceAll("&", "");
appActionName = appActionName.replaceAll("#", "");
appActionName = appActionName.replaceAll(".", "");
appActionName = appActionName.replaceAll(",", "");
appActionName = appActionName.replaceAll(" ", "_");
appActionName = appActionName.replaceAll("^", "_");
appActionName = appActionName.replaceAll("'", "_");
appActionName = appActionName.replaceAll("\"", "_");
appActionName = appActionName.replaceAll("\"", "_");
appActionName = appActionName.replaceAll(":", "_");
appActionName = appActionName.replaceAll(";", "_");
appActionName = appActionName.replaceAll("=", "_");
appActionName = appActionName.replaceAll("+", "_");
selectedAction.label = appActionName;
setSelectedAction(selectedAction);
};
const actionDelayChange = (delay) => {
if (isNaN(delay)) {
console.log("NAN: ", delay)
return
}
const parsedNumber = parseInt(delay)
if (parsedNumber > 86400) {
console.log("Max number is 1 day (86400)")
return
}
selectedAction.execution_delay = parsedNumber
setSelectedAction(selectedAction)
}
const selectedTriggerChange = (event) => {
selectedTrigger.label = event.target.value;
setSelectedTrigger(selectedTrigger);
};
// Starts on current node and climbs UP the tree to the root object.
// Sends back everything in it's path
const getParents = (action) => {
if (action === undefined || action === null) {
return []
}
var allkeys = [action.id];
var handled = [];
var results = [];
// maxiter = max amount of parent nodes to loop
// also handles breaks if there are issues
var iterations = 0;
var maxiter = 10;
while (true) {
for (let parentkey in allkeys) {
var currentnode = cy.getElementById(allkeys[parentkey]);
if (currentnode === undefined || currentnode === null) {
continue;
}
if (currentnode.data() === undefined) {
handled.push(allkeys[parentkey]);
results.push({ id: allkeys[parentkey], type: "TRIGGER" });
} else {
if (handled.includes(currentnode.data().id)) {
continue
} else {
handled.push(currentnode.data().id);
results.push(currentnode.data());
}
}
// Get the name / label here too?
if (currentnode.length === 0) {
continue;
}
const incomingEdges = currentnode.incomers("edge");
if (incomingEdges.length === 0) {
continue;
}
for (let i = 0; i < incomingEdges.length; i++) {
var tmp = incomingEdges[i];
if (tmp.data("decorator")) {
continue
}
if (!allkeys.includes(tmp.data("source"))) {
allkeys.push(tmp.data("source"));
}
}
}
if (results.length === allkeys.length || iterations === maxiter) {
break;
}
iterations += 1;
}
// Remove on the end as we don't want to remove everything
results = results.filter((data) => data.id !== action.id)
results = results.filter((data) => data.type === "ACTION" || data.app_name === "Shuffle Workflow" || data.app_name === "User Input")
results.push({ label: "Execution Argument", type: "INTERNAL" })
return results
}
// BOLD name: type: required?
// FORM
// Dropdown -> static, action, local env, global env
// VALUE (JSON)
// {data.name}, {data.description}, {data.required}, {data.schema.type}
//height: "100%",
const appApiViewStyle = {
display: "flex",
flexDirection: "column",
backgroundColor: "#1F2023",
color: "white",
paddingRight: 15,
paddingLeft: 15,
minHeight: "100%",
zIndex: 1000,
resize: "vertical",
overflow: "auto",
paddingBottom: 100,
overflowAnchor: "none",
};
const minSize = 370
var rightsidebarStyle = {
position: "fixed",
top: appBarSize + 25,
right: 25,
height: "80vh",
width: isMobile ? "100%" : minSize,
minWidth: minSize,
maxWidth: 600,
maxHeight: "100vh",
border: "1px solid rgb(91, 96, 100)",
zIndex: 1000,
borderRadius: theme.palette.borderRadius,
resize: "both",
overflow: "auto",
overflowAnchor: "none",
};
const setTriggerFolderWrapperMulti = (event) => {
const { options } = event.target;
var value = [];
for (let i = 0, l = options.length; i < l; i += 1) {
if (options[i].selected) {
value.push(options[i].value);
}
}
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = [[]];
workflow.triggers[selectedTriggerIndex].parameters = [[]];
}
// Max 1 folder for office for some reason. MailFolders('MAILBOX_ID') in resource
// Can't parse URL with multiple folders.
if (selectedTrigger.name === "Office365" & value !== undefined && value !== null && value.length > 1) {
toast("Max 1 folder at a time allowed for Office365")
console.log("VALUE: ", value)
value = [value[0]]
}
// This is a dirty workaround for the static values in the go backend and datastore db
const fixedValue = value.join(splitter);
selectedTrigger.parameters[0] = {
value: fixedValue,
name: "outlookfolder",
};
workflow.triggers[selectedTriggerIndex].parameters[0] = {
value: fixedValue,
name: "outlookfolder",
};
// This resets state for some reason (:
setSelectedAction({});
setSelectedTrigger({});
setSelectedApp({});
setSelectedEdge({});
// Set value
setSelectedTrigger(selectedTrigger);
setWorkflow(workflow);
};
const setTriggerCronWrapper = (value) => {
console.log("Cron Value: ", value)
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = [];
}
workflow.triggers[selectedTriggerIndex].parameters[0] = {
value: value,
name: "cron",
};
setWorkflow(workflow);
};
const setTriggerOptionsWrapper = (value) => {
if (selectedTrigger.parameters === null || selectedTrigger.parameters === undefined) {
selectedTrigger.parameters = [
{ name: "", value: "" },
{ name: "", value: "" },
{ name: "", value: "" },
]
}
if (selectedTrigger.parameters.length < 3 ) {
selectedTrigger.parameters.push({ name: "", value: "" })
}
const splitItems = workflow.triggers[selectedTriggerIndex].parameters[2].value.split(",");
console.log(splitItems);
if (splitItems.includes(value)) {
for (let i = 0; i < splitItems.length; i++) {
if (splitItems[i] === value) {
splitItems.splice(i, 1);
}
}
} else {
splitItems.push(value);
}
for (let i = 0; i < splitItems.length; i++) {
if (splitItems[i] === "") {
splitItems.splice(i, 1);
}
}
workflow.triggers[selectedTriggerIndex].parameters[2].value = splitItems.join(",");
setWorkflow(workflow);
setLocalFirstrequest(!localFirstrequest);
setUpdate(Math.random());
};
const setTriggerTextInformationWrapper = (value) => {
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = [];
}
workflow.triggers[selectedTriggerIndex].parameters[0] = {
value: value,
name: "alertinfo",
};
setWorkflow(workflow);
};
const setTriggerBodyWrapper = (value) => {
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = [];
workflow.triggers[selectedTriggerIndex].parameters[0] = {
value: value,
name: "cron",
}
}
workflow.triggers[selectedTriggerIndex].parameters[1] = {
value: value,
name: "execution_argument",
};
setWorkflow(workflow);
};
const AppConditionHandler = (props) => {
const { tmpdata, type } = props;
const [data] = useState(tmpdata);
const [multiline, setMultiline] = useState(false);
const [showAutocomplete, setShowAutocomplete] = React.useState(false);
const [actionlist, setActionlist] = React.useState([]);
if (tmpdata === undefined) {
return tmpdata;
}
if (data.variant === "") {
data.variant = "STATIC_VALUE";
}
// Set actions based on NEXT node, since it should be able to involve those two
if (actionlist.length === 0) {
// FIXME: Have previous execution values in here
actionlist.push({
type: "Execution Argument",
name: "Execution Argument",
value: "$exec",
highlight: "exec",
autocomplete: "exec",
example: "tmp",
})
actionlist.push({
type: "Shuffle DB",
name: "Shuffle DB",
value: "$shuffle_cache",
highlight: "shuffle_cache",
autocomplete: "shuffle_cache",
example: "tmp",
})
if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) {
for (let varkey in workflow.workflow_variables) {
const item = workflow.workflow_variables[varkey];
actionlist.push({
type: "workflow_variable",
name: item.name,
value: item.value,
id: item.id,
autocomplete: `${item.name.split(" ").join("_")}`,
example: item.value,
});
}
}
// FIXME: Add values from previous executions if they exist
if (
workflow.execution_variables !== null &&
workflow.execution_variables !== undefined &&
workflow.execution_variables.length > 0
) {
for (let varkey in workflow.execution_variables) {
const item = workflow.execution_variables[varkey];
actionlist.push({
type: "execution_variable",
name: item.name,
value: item.value,
id: item.id,
autocomplete: `${item.name.split(" ").join("_")}`,
example: "",
});
}
}
const destAction = cy.getElementById(selectedEdge.target);
var parents = getParents(destAction.data());
if (parents.length > 1) {
for (let parentkey in parents) {
const item = parents[parentkey];
if (item.label === "Execution Argument") {
continue;
}
// 1. Take
const actionvalue = {
type: "action",
id: item.id,
name: item.label,
autocomplete: `${item.label.split(" ").join("_")}`,
example: item.example === undefined ? "" : item.example,
};
actionlist.push(actionvalue);
}
}
setActionlist(actionlist);
}
if (
data.multiline !== undefined &&
data.multiline !== null &&
data.multiline === true
) {
setMultiline(true);
}
var placeholder = "Static value";
if (
data.example !== undefined &&
data.example !== null &&
data.example.length > 0
) {
placeholder = data.example;
}
var datafield = (
Use "Shuffle Tools" app with "Filter List" action to handle loops
) : null
}
onBlur={(e) => {
changeActionVariable(data.action_field, e.target.value);
setUpdate(Math.random());
}}
/>
);
const changeActionVariable = (variable, value) => {
// set the name
data.value = value;
data.action_field = variable;
if (type === "source") {
setSourceValue(data);
} else if (type === "destination") {
setDestinationValue(data);
}
};
return (
{injectedData}
{/* Check if dest is the same as start */}
{conditionsDisabled ?
Conditions are unavailable between triggers and the startnode.
: null}
);
};
// 1. GET the trigger authentication data
// 2. Parse the fields that are used (outlook & gmail)
// 3. Parse the folders that are selected
// 4. Start / stop
const EmailSidebar = () => {
if (Object.getOwnPropertyNames(selectedTrigger).length === 0) {
return null;
}
if (workflow.triggers[selectedTriggerIndex] === undefined) {
return null;
}
if (
workflow.triggers[selectedTriggerIndex].parameters === undefined ||
workflow.triggers[selectedTriggerIndex].parameters === null ||
workflow.triggers[selectedTriggerIndex].parameters.length === 0
) {
workflow.triggers[selectedTriggerIndex].parameters = [
{ value: "No folders selected yet", name: "outlookfolder" },
];
selectedTrigger.parameters = [
{ value: "No folders selected yet", name: "outlookfolder" },
];
setWorkflow(workflow);
setSelectedTrigger(selectedTrigger);
}
const setGmailFolders = () => {
console.log("In set gmail folders");
fetch(
globalUrl +
"/api/v1/triggers/gmail/getFolders?trigger_id=" +
selectedTrigger.id,
{
method: "GET",
headers: { "content-type": "application/json" },
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
throw new Error("No folders :o!");
}
return response.json();
})
.then((responseJson) => {
if (
responseJson !== undefined &&
responseJson !== null &&
responseJson.success !== false &&
responseJson.length > 0
) {
setTriggerFolders(responseJson);
}
if (
workflow.triggers[selectedTriggerIndex].parameters.length === 0 &&
responseJson.length > 0
) {
workflow.triggers[selectedTriggerIndex].parameters = [
{
value: responseJson[0].displayName,
name: "outlookfolder",
id: responseJson[0].id,
},
];
selectedTrigger.parameters = [
{
value: responseJson[0].displayName,
name: "outlookfolder",
id: responseJson[0].id,
},
];
setWorkflow(workflow);
setSelectedTrigger(selectedTrigger);
}
})
.catch((error) => {
console.log("Get gmail folder error: ", error.toString());
});
};
const setOutlookFolders = () => {
fetch(
globalUrl +
"/api/v1/triggers/outlook/getFolders?trigger_id=" +
selectedTrigger.id,
{
method: "GET",
headers: { "content-type": "application/json" },
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
throw new Error("No folders :o!");
}
return response.json();
})
.then((responseJson) => {
if (
responseJson !== null &&
responseJson.success !== false &&
responseJson.length > 0
) {
console.log("Got trigger folders: ", triggerFolders)
setTriggerFolders(responseJson);
}
if (
workflow.triggers[selectedTriggerIndex].parameters.length === 0 &&
responseJson.length > 0
) {
workflow.triggers[selectedTriggerIndex].parameters = [
{
value: responseJson[0].displayName,
name: "outlookfolder",
id: responseJson[0].id,
},
]
selectedTrigger.parameters = [
{
value: responseJson[0].displayName,
name: "outlookfolder",
id: responseJson[0].id,
},
];
setWorkflow(workflow);
setSelectedTrigger(selectedTrigger);
}
})
.catch((error) => {
console.log("Get outlook folders error: ", error.toString());
});
};
const getTriggerAuth = () => {
fetch(`${globalUrl}/api/v1/triggers/${selectedTrigger.id}`, {
method: "GET",
headers: { "content-type": "application/json" },
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
throw new Error("No trigger info :o!");
}
return response.json();
})
.then((responseJson) => {
setTriggerAuthentication(responseJson);
})
.catch((error) => {
//console.log(error.toString());
console.log("Set trigger auth error: ", error.toString());
});
};
// Getting the triggers and the folders if they exist
if (localFirstrequest) {
//console.log("Trigger: ", selectedTrigger)
//console.log("Triggername: ", selectedTrigger.name)
//if (selectedTrigger.name.toLowerCase() === "gmail") {
setGmailFolders();
setOutlookFolders();
getTriggerAuth();
setLocalFirstrequest(false);
}
const gmailButton =
selectedTrigger.name !== "Gmail" ? null : (
);
const outlookButton =
selectedTrigger.name !== "Office365" ? null : (
);
// FIXME - set everything in here to multifolder etc
var triggerInfo = "SET UP BUT NO TYPE :)";
if (Object.getOwnPropertyNames(triggerAuthentication).length > 0) {
// Should get the folders if they don't already exist
if (
triggerAuthentication.type === "outlook" ||
triggerAuthentication.type === "gmail"
) {
triggerInfo = (
{selectedTrigger.status === "running" ? null : (
Change auth
{outlookButton}
{gmailButton}
If you have trouble using this trigger, please contact us to get access
)}
{triggerFolders === undefined || triggerFolders === null ? null : (
Select {triggerAuthentication.type === "gmail" ? "labels" : "a folder"}
{triggerFolders.length === 0 ?
No folders found. Please authenticate first. If this persists, contact us.
:
}
)}
{outlookButton}
{gmailButton}
If you have trouble using this trigger, please {
if (window.drift !== undefined) {
window.drift.api.startInteraction({ interactionId: 340043 })
} else {
console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift)
}
}}>contact us to get access
{workflow.triggers[selectedTriggerIndex].parameters.length > 4 ?
{
if (selectedTrigger.parameters === null) {
selectedTrigger.parameters = [];
}
// Sets the webhook to run as version 2.. kinda
var value = "v2"
if (workflow.triggers[selectedTriggerIndex].parameters[4].value.includes("v2")) {
value = "v1"
}
workflow.triggers[selectedTriggerIndex].parameters[4] = {
name: "await_response",
value: value
}
setWorkflow(workflow)
setUpdate(Math.random())
}}
color="primary"
value="await_response"
/>
}
label={
);
//return null;
};
const unPublishWorkflow = (data) => {
data.id = props.match.params.key
if (!isCloud) {
toast("Function only supported on cloud")
return
}
if (data.public !== true) {
toast("Workflow is not public. Can't unpublish");
return
}
// This ALWAYS talks to Shuffle cloud
data = JSON.parse(JSON.stringify(data));
const url = `${globalUrl}/api/v1/workflows/${props.match.params.key}/unpublish`;
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(data),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflow publish :O!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.reason !== undefined) {
toast("Unpublishing: "+responseJson.reason)
}
if (responseJson.success === true) {
workflow.public = false
setWorkflow(workflow)
}
})
.catch((error) => {
toast("Failed publishing: is the workflow valid? Remember to save the workflow first.")
console.log(error.toString())
})
}
// This can execute a workflow with firestore. Used for test, as datastore is old and stuff
// Too much work to move everything over alone, so won't touch it for now
//
//
// A list used for FRONTEND handling of whether a public workflow
// should be change-able
const allowList = ["frikky", "m1nk-code", "DavidtheGoliath"]
// console.log(allowList, userdata.public_username)
const leftView = workflow.public === true ?
{workflow.name}
{workflow.validated === true ?
: null}
This workflow is public and {
saveWorkflow(workflow)
}}>must be saved or exported before use.
{Object.getOwnPropertyNames(creatorProfile).length !== 0 && creatorProfile.github_avatar !== undefined && creatorProfile.github_avatar !== null ?
You can see these buttons because you may have the correct access rights as a creator to help modify this workflow.
{userdata.support === true ?
Manual Verification: {workflow.validated === undefined || workflow.validated === null || workflow.validated === false ? "Not valided" : "Validated"}
{environments.length > 0 && defaultEnvironmentIndex < environments.length && nonskippedResults.length === 0 && environments[defaultEnvironmentIndex].Name !== "Cloud" ?
No results yet. Is Orborus running for the "{environments[defaultEnvironmentIndex].Name}" environment? Learn more. If the Workflow doesn't start within 30 seconds with Orborus running, contact support: support@shuffler.io
:
null}
Action Logs
Logs for an action are not available without an onprem environment with the SHUFFLE_LOGS_DISABLED environment variable set to false: SHUFFLE_LOGS_DISABLED=false. Logs are enabled by default, except in scale mode.
)
}
var showlink = false
if (data.name.endsWith("-Url")) {
//data.name = data.name.toLowerCase().replaceAll("-", "_")
if (data.value.startsWith(", ")) {
data.value = data.value.substring(2)
}
if (!data.value.startsWith("http") || (data.value.startsWith("/") && data.value.includes("?"))) {
showlink = true
}
}
return (
)
}
var draggingDisabled = false;
// Should probably put this on the backend instead when notifications are made :))
const getErrorSuggestion = (result) => {
if (result === undefined || result === null) {
return ""
}
// Check if array with json inside to handle one item at a time~
if (typeof result === "object" && result.length !== undefined) {
if (result.length > 0) {
// Check type inside
if (typeof result[0] === "object") {
result = result[0]
}
}
}
if (result.success === true && result.status === 200) {
if (result.body !== undefined && result.body !== null) {
const stringbody = result.body.toString()
if ((stringbody.startsWith("{") && stringbody.endsWith("}")) || (stringbody.startsWith("[") && stringbody.endsWith("]"))) {
return ""
}
if (stringbody.length > 1000) {
return "Body looks to be big in a standard format. Consider using the 'To File' parameter to automatically make it into a file."
}
}
}
if (result.status === 429) {
return "Rate limit exceeded. Consider using a different API key or wait a bit before trying again."
}
if (result.status === 405) {
return "Method not allowed. Check the URL to ensure it has all the required parameters. If you keep getting a 405, please forward a screenshot of this to support@shuffler.io"
}
if (result.status === 415) {
return "Content-Type header missing or wrong. Please add the correct Content-Type header and save the workflow."
}
if (result.status === 401) {
return "Authentication failed (401). The URL or auth key is wrong. Check the body of the result for more information."
}
if (result.status === 403) {
return "Authorization failed (403). The API user most likely doesn't have the correct permissions. Check the body of the result for more information."
}
if (result.status === 404) {
return "The URL, or content of the URL is incorrect. Check it and try again."
}
if (result.status === 400) {
return "The queries or data sent to the API is most likely wrong (400). Check the body of the result for more information."
}
if (result.status === 200 || result.status === 201 || result.status === 204) {
return "It looks like the result was successful! If it didn't work, make sure to check if the body you are sending was correct."
}
// Validate and check for newlines
if (result.success !== false) {
var stringjson = result
const valid = validateJson(stringjson, true)
if (valid.valid === false) {
if (stringjson.startsWith("{") && stringjson.endsWith("}")) {
// Look for newline
if (stringjson.includes("\n") && !stringjson.includes("\n")) {
return "Looks like you have a newline problem. Consider using the | replace: '\n', '\\n' }} filter in Liquid."
} else {
return "The result looks like it should be JSON, but is invalid. Look for potential"
}
}
}
//return ""
}
try {
stringjson = JSON.stringify(result)
} catch (e) {
}
stringjson = stringjson.toLowerCase()
if (stringjson.includes("localhost")) {
return "You can't use localhost in apps. Use the external ip or url of the server instead"
}
if (stringjson.includes("manifest unknown")) {
return "The app's Docker Image is not available in the environment yet. Re-run the app to force a re-download of the app. If the problem persists, contact support"
}
if (result.status !== 200 && result.url !== undefined && result.url !== null && (result.url.includes("192.168") || result.url.includes("172.16") || result.url.includes("10.0"))) {
return "Consider whether your Orborus environment can connect to a local IP or not."
}
if (stringjson.includes("kms/")) {
return "KMS authentication most likely failed. Check your notifications for more details on this page: /admin?admin_tab=priorities. If you need help with KMS, please contact support@shuffler.io"
}
if (stringjson.includes("invalidurl")) {
// IF count of "http" is more than one, 1, it's prolly invalid
var additionalinfo = ""
if (stringjson.includes("http") && stringjson.match(/http/g).length > 1) {
additionalinfo = "You may be using multiple 'http' in the URL. "
}
return "The URL is invalid. Change the URL to a valid one, and try again. "+additionalinfo
}
if (stringjson.includes("result too large to handle")) {
return "Execution loading failed. Reload the execution by closing it and clicking it again"
}
if (isCloud && stringjson.toLowerCase().includes("timeout error")) {
return "Run this workflow in a local environment to increase the timeout. Go to https://shuffler.io/admin?tab=environments to create an environment to connect to"
}
if (stringjson.toLowerCase().includes("invalid header")) {
return "A header or authentication token in the app is invalid. Check the app's configuration"
}
if (stringjson.includes("connectionerror")) {
if (stringjson.includes("kms")) {
return "KMS authentication most likely failed (2). Check your notifications for more details on this page: /admin?admin_tab=priorities&kms=true. If you need help with KMS, please contact support@shuffler.io"
}
return "The URL is incorrect, or Shuffle can't reach it. Set up a Shuffle Environment in the same VLAN, or whitelist Shuffle's IPs."
}
return ""
}
const currentSuggestion = getErrorSuggestion(validate.result)
const codePopoutModal = !codeModalOpen ? null : (
);
const newView = (
: null
}
{/* Looks for triggers" */}
{/* Only fixed the ones that require scrolling on a small screen */}
{/* Most important: Actions. But these are a lot more complex */}
{rightSideBarOpen && (selectedTrigger.trigger_type === "SCHEDULE" || selectedTrigger.trigger_type === "WEBHOOK" || selectedTrigger.trigger_type === "PIPELINE" || selectedTrigger.trigger_type === "USERINPUT") ?
Authentication for {selectedApp.name.replaceAll("_", " ", -1)}
What is app authentication?
These are required fields for authenticating with {selectedApp.name}
Name - what is this used for? {
authenticationOption.label = event.target.value;
}}
/>
{selectedApp.authentication.parameters.map((data, index) => {
return (