diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index a03df771..324993e4 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -2166,10 +2166,87 @@ class AppBase: self.logger.info(f"[ERROR] Liquid Template error: {e}") error = True error_msg = e + + self.action["parameters"].append({ + "name": "liquid_template_error", + "value": f"There was a Liquid input error (1). Details: {e}", + }) + + self.action_result["action"] = self.action + except SyntaxError as e: + self.logger.info(f"[ERROR] Liquid Syntax error: {e}") + error = True + error_msg = e + + self.action["parameters"].append({ + "name": "liquid_python_syntax_error", + "value": f"There was a syntax error in your Liquid input (2). Details: {e}", + }) + + self.action_result["action"] = self.action + except IndentationError as e: + self.logger.info(f"[ERROR] Liquid IndentationError: {e}") + error = True + error_msg = e + + self.action["parameters"].append({ + "name": "liquid_indentiation_error", + "value": f"There was an indentation error in your Liquid input (2). Details: {e}", + }) + + self.action_result["action"] = self.action except jinja2.exceptions.TemplateSyntaxError as e: self.logger.info(f"[ERROR] Liquid Syntax error: {e}") error = True error_msg = e + + self.action["parameters"].append({ + "name": "liquid_syntax_error", + "value": f"There was a syntax error in your Liquid input (2). Details: {e}", + }) + + self.action_result["action"] = self.action + except json.decoder.JSONDecodeError as e: + self.logger.info(f"[ERROR] Liquid JSON Syntax error: {e}") + + replace = False + skip_next = False + newlines = [] + thisline = [] + for line in template.split("\n"): + #print("LINE: %s" % repr(line)) + if "\"\"\"" in line or "\'\'\'" in line: + if replace: + skip_next = True + else: + replace = not replace + + if replace == True: + thisline.append(line) + if skip_next == True: + if len(thisline) > 0: + #print(thisline) + newlines.append(" ".join(thisline)) + thisline = [] + + replace = False + else: + newlines.append(line) + + new_template = "\n".join(newlines) + if new_template != template: + #check_template(new_template) + return parse_liquid(new_template, self) + else: + error = True + error_msg = e + + self.action["parameters"].append({ + "name": "liquid_json_error", + "value": f"There was a syntax error in your input JSON(2). This is typically an issue with escaping newlines. Details: {e}", + }) + + self.action_result["action"] = self.action except TypeError as e: try: if "string as left operand" in f"{e}": @@ -2194,6 +2271,13 @@ class AppBase: except Exception as e: print(f"SubError in Liquid: {e}") + + self.action["parameters"].append({ + "name": "liquid_general_error", + "value": f"There was general error Liquid input (2). Details: {e}", + }) + + self.action_result["action"] = self.action #return template self.logger.info(f"[ERROR] Liquid TypeError error: {e}") @@ -2205,6 +2289,13 @@ class AppBase: error = True error_msg = e + self.action["parameters"].append({ + "name": "liquid_general_exception", + "value": f"There was general exception Liquid input (2). Details: {e}", + }) + + self.action_result["action"] = self.action + if "fmt" in error_msg and "liquid_date" in error_msg: return template diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 092b52ad..d463ced6 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -395,6 +395,40 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) { } } + for _, action := range workflowExecution.Workflow.Actions { + found := false + for _, result := range workflowExecution.Results { + if result.Action.ID == action.ID { + found = true + break + } + } + + if found { + continue + } + + //log.Printf("[DEBUG] Maybe not handled yet: %s", action.ID) + cacheId := fmt.Sprintf("%s_%s_result", workflowExecution.ExecutionId, action.ID) + cache, err := shuffle.GetCache(ctx, cacheId) + if err != nil { + //log.Printf("[WARNING] Couldn't find in fix exec %s (2): %s", cacheId, err) + continue + } + + actionResult := shuffle.ActionResult{} + cacheData := []byte(cache.([]uint8)) + + // Just ensuring the data is good + err = json.Unmarshal(cacheData, &actionResult) + if err != nil { + continue + } else { + log.Printf("[DEBUG] APPENDING %s result to send to app or something\n\n\n\n", action.ID) + workflowExecution.Results = append(workflowExecution.Results, actionResult) + } + } + newjson, err := json.Marshal(workflowExecution) if err != nil { resp.WriteHeader(401) diff --git a/frontend/src/components/Appsearch.jsx b/frontend/src/components/Appsearch.jsx index ec77d93e..b063a204 100644 --- a/frontend/src/components/Appsearch.jsx +++ b/frontend/src/components/Appsearch.jsx @@ -119,7 +119,7 @@ const WorkflowSearch = props => { var counted = 0 return ( - + {hits.map((data, index) => { const paperStyle = { backgroundColor: index === mouseHoverIndex ? "rgba(255,255,255,0.8)" : theme.palette.inputColor, @@ -131,6 +131,8 @@ const WorkflowSearch = props => { position: "relative", overflow: "hidden", width: "100%", + minHeight: 37, + maxHeight: 52, } if (counted === 12/xs*rowHandler) { diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index 14810c94..43456cb3 100644 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -164,7 +164,14 @@ const AuthenticationOauth2 = (props) => { state += `%26refresh_uri%3d${authentication_url}`; } - const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`; + // Force new consent + //const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`; + + // Admin consent + //const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=admin_consent&state=${state}&access_type=offline`; + + // Skip consent + const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&state=${state}&access_type=offline`; //const url = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${client_id}&scope=AaaServer.profile.Read&redirect_uri=${redirectUri}&prompt=consent` //console.log("Full URI: ", url) @@ -373,7 +380,7 @@ const AuthenticationOauth2 = (props) => { "efe4c3fe-84a1-4821-a84f-23a6cfe8e72d", "", "https://graph.microsoft.com", - ["Mail.ReadWrite"], + ["Mail.ReadWrite", "Mail.Send"], ); } else if (selectedApp.name.toLowerCase() == "gmail") { handleOauth2Request( diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 050da7cd..03b97805 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -88,9 +88,9 @@ import { import Autocomplete from "@material-ui/lab/Autocomplete"; -import CodeMirror from "@uiw/react-codemirror"; -import "codemirror/keymap/sublime"; -import "codemirror/theme/gruvbox-dark.css"; +//import CodeMirror from "@uiw/react-codemirror"; +//import "codemirror/keymap/sublime"; +//import "codemirror/theme/gruvbox-dark.css"; import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx"; const useStyles = makeStyles({ @@ -1515,6 +1515,7 @@ const ParsedAction = (props) => { setcodedata={setcodedata} expansionModalOpen={expansionModalOpen} setExpansionModalOpen={setExpansionModalOpen} + globalUrl={globalUrl} /> ) diff --git a/frontend/src/components/ShuffleCodeEditor.jsx b/frontend/src/components/ShuffleCodeEditor.jsx index 62066735..759e0093 100644 --- a/frontend/src/components/ShuffleCodeEditor.jsx +++ b/frontend/src/components/ShuffleCodeEditor.jsx @@ -1,5 +1,6 @@ import React, {useState, useEffect, useLayoutEffect} from 'react'; import { + CircularProgress, IconButton, Dialog, Modal, @@ -29,6 +30,7 @@ import { SquareFoot as SquareFootIcon, Circle as CircleIcon, Add as AddIcon, + PlayArrow as PlayArrowIcon, } from '@mui/icons-material'; import { @@ -67,7 +69,8 @@ const pythonFilters = [ ] const CodeEditor = (props) => { - const { fieldCount, setFieldCount, actionlist, changeActionParameterCodeMirror, expansionModalOpen, setExpansionModalOpen, codedata, setcodedata, isFileEditor, runUpdateText } = props + const { globalUrl, fieldCount, setFieldCount, actionlist, changeActionParameterCodeMirror, expansionModalOpen, setExpansionModalOpen, codedata, setcodedata, isFileEditor, runUpdateText } = props + const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata); // const {codelang, setcodelang} = props const theme = useTheme(); @@ -92,6 +95,13 @@ const CodeEditor = (props) => { const [menuPosition, setMenuPosition] = useState(null); const [showAutocomplete, setShowAutocomplete] = React.useState(false); + const baseResult = "" + const [executionResult, setExecutionResult] = useState({ + "valid": false, + "result": baseResult, + }) + const [executing, setExecuting] = useState(false) + const liquidOpen = Boolean(anchorEl); const mathOpen = Boolean(anchorEl2); const pythonOpen = Boolean(anchorEl3); @@ -384,7 +394,7 @@ const CodeEditor = (props) => { // return //} - //console.log(found) + //console.log("FOUND: ", found) // Whelp this is inefficient af. Single loop pls // When the found array is empty. @@ -395,6 +405,7 @@ const CodeEditor = (props) => { const fixedVariable = fixVariable(found[i]) //var correctVariable = availableVariables.includes(fixedVariable) + // var valuefound = false for (var j = 0; j < actionlist.length; j++) { if(fixedVariable.slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){ @@ -423,22 +434,31 @@ const CodeEditor = (props) => { // actionlist[k].example = "TMP" //} + var new_input = "" try { - var new_input = FindJsonPath(fullpath, actionlist[k].example) + new_input = FindJsonPath(fullpath, actionlist[k].example) } catch (e) { console.log("ERR IN INPUT: ", e) } + console.log("Got input: ", new_input, actionlist[k].example, typeof new_input) + if (typeof new_input === "object") { new_input = JSON.stringify(new_input) } else { if (typeof new_input === "string") { new_input = new_input } else { - new_input = "" + console.log("NO TYPE? ", typeof new_input) + try { + new_input = new_input.toString() + } catch (e) { + new_input = "" + } } } + //console.log("FOUND2: ", fixedVariable, actionlist[j].example) input = input.replace(fixedVariable, new_input) //} catch (e) { @@ -461,7 +481,6 @@ const CodeEditor = (props) => { } } catch (e) { console.log("Outer replace error: ", e) - } const tmpValidation = validateJson(input.valueOf()) @@ -515,6 +534,67 @@ const CodeEditor = (props) => { setAnchorEl3(null) } + const executeSingleAction = (inputdata) => { + //if (serverside === true) { + // return + //} + + if (validation === true) { + inputdata = JSON.stringify(inputdata) + } + + const appid = "3e2bdf9d5069fe3f4746c29d68785a6a" + const actiondata = {"description":"Repeats the call parameter","id":"","name":"repeat_back_to_me","label":"","node_type":"","environment":"","sharing":false,"private_id":"","public_id":"","app_id":"3e2bdf9d5069fe3f4746c29d68785a6a","tags":null,"authentication":[],"tested":false,"parameters":[{"description":"The message to repeat","id":"","name":"call","example":"REPEATING: Hello world","value":inputdata,"multiline":true,"options":null,"action_field":"","variant":"STATIC_VALUE","required":true,"configuration":false,"tags":null,"schema":{"type":"string"},"skip_multicheck":false,"value_replace":null,"unique_toggled":false,"autocompleted":false}],"execution_variable":{"description":"","id":"","name":"","value":""},"returns":{"description":"","example":"","id":"","schema":{"type":"string"}},"authentication_id":"","example":"","auth_not_required":false,"source_workflow":"","run_magic_output":false,"run_magic_input":false,"execution_delay":0,"app_name":"Shuffle Tools","app_version":"1.2.0","selectedAuthentication":{}} + + setExecutionResult({ + "valid": false, + "result": baseResult, + }) + + setExecuting(true) + + fetch(globalUrl+"/api/v1/apps/"+appid+"/execute", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(actiondata), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!") + } + + return response.json() + }) + .then((responseJson) => { + //console.log("RESPONSE: ", responseJson) + if (responseJson.success === true && responseJson.result !== null && responseJson.result !== undefined && responseJson.result.length > 0) { + const result = responseJson.result.slice(0, 50)+"..." + //alert.info("SUCCESS: "+result) + + const validate = validateJson(responseJson.result) + setExecutionResult(validate) + } else if (responseJson.success === false && responseJson.reason !== undefined && responseJson.reason !== null) { + alert.error(responseJson.reason) + setExecutionResult({"valid": false, "result": responseJson.reason}) + } else if (responseJson.success === true) { + setExecutionResult({"valid": false, "result": "Couldn't finish execution. Please fill all the required fields, and retry the execution."}) + } else { + setExecutionResult({"valid": false, "result": "Couldn't finish execution (2). Please fill all the required fields, and validate the execution."}) + } + + setExecuting(false) + }) + .catch(error => { + //alert.error("Execution error: "+error.toString()) + console.log("error: ", error) + setExecuting(false) + }) + } + return ( {
{/* @@ -1136,129 +1215,158 @@ const CodeEditor = (props) => { */}
- {isFileEditor ? null : -
- {isMobile ? null : - + {isMobile ? null : + + + Expected Output + + { + executeSingleAction(expOutput) + }}> + + {executing ? : } + + + + + } + {isMobile ? null : + validation === true ? + { + //handleReactJsonClipboard(copy); + }} + displayDataTypes={false} + onSelect={(select) => { + //HandleJsonCopy(validate.result, select, "exec"); + }} + name={"JSON autocompletion"} + /> + : +

+ {expOutput} +

+ } + {executionResult.valid === true ? + { + //handleReactJsonClipboard(copy); + }} + displayDataTypes={false} + onSelect={(select) => { + //HandleJsonCopy(validate.result, select, "exec"); + }} + name={"Test result"} + /> + : + + {executionResult.result.length > 0 ? + + Test output: {executionResult.result} + + : null} + + } +
+ ) + } +
+
} + Cancel + + - + setcodedata(localcodedata)} + }} + > + Done +
) } diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 2885f71e..27c123fb 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -4804,6 +4804,11 @@ const Admin = (props) => { { + if (environment.Type === "cloud") { + alert.info("No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.") + return + } + const elementName = "copy_element_shuffle"; const auth = environment.auth === "" ? 'cb5st3d3Z!3X3zaJ*Pc' : environment.auth const commandData = `docker run -d --volume "/var/run/docker.sock:/var/run/docker.sock" -e ENVIRONMENT_NAME="${environment.Name}" -e 'AUTH=${auth}' -e ORG="${props.userdata.active_org.id}" -e DOCKER_API_VERSION=1.40 -e BASE_URL="https://shuffler.io" ghcr.io/frikky/shuffle-orborus:latest` diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index a6a92d3b..89202b7f 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -15,6 +15,9 @@ import theme from '../theme'; import { isMobile } from "react-device-detect" import aa from 'search-insights' +import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; +import algoliasearch from 'algoliasearch/lite'; + import { Zoom, Avatar, @@ -51,6 +54,11 @@ import { SwipeableDrawer, Switch, Chip, + Card, + List, + ListItem, + ListItemText, + ListItemAvatar, } from "@material-ui/core"; import { @@ -58,6 +66,8 @@ import { } from "@mui/material" import { + Folder as FolderIcon, + LibraryBooks as LibraryBooksIcon, OpenInNew as OpenInNewIcon, Undo as UndoIcon, GetApp as GetAppIcon, @@ -228,6 +238,7 @@ const svgSize = 24; //const referenceUrl = "https://shuffler.io/functions/webhooks/" //const referenceUrl = window.location.origin+"/api/v1/hooks/" +const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const AngularWorkflow = (defaultprops) => { const { globalUrl, isLoggedIn, isLoaded, userdata } = defaultprops; const referenceUrl = globalUrl + "/api/v1/hooks/"; @@ -1287,7 +1298,7 @@ const AngularWorkflow = (defaultprops) => { if (responseJson.reason !== undefined && responseJson.reason !== null) { alert.error("Failed to save: " + responseJson.reason); } else { - alert.error("Failed to save. Please contact your admin if this is unexpected.") + alert.error("Failed to save. Please contact your support@shuffler.io or your local admin if this is unexpected.") } } else { if ( @@ -2158,9 +2169,14 @@ const AngularWorkflow = (defaultprops) => { return; } + if (nodedata.parameters === undefined) { + return + } + const workflow_id = nodedata.parameters.find( (param) => param.name === "workflow" ); + if (workflow.id === workflow_id.valu) { return; } @@ -2815,10 +2831,7 @@ const AngularWorkflow = (defaultprops) => { cy.on("free", "node", (e) => onNodeDragStop(e, curaction)); } - console.log("Object: ", environments, curaction.environment) if (environments !== undefined && environments !== null && (typeof environments === "array" || typeof environments === "object")) { - console.log("Type: ", typeof(environments)) - var parsedenv = environments //if (typeof environments === "object") { // parsedenv = [environments] @@ -2826,13 +2839,10 @@ const AngularWorkflow = (defaultprops) => { const envs = parsedenv.find((a) => a.Name === curaction.environment); var env = environments[defaultEnvironmentIndex] - console.log("Inner envs: ", envs, curaction.environment) if (envs !== undefined && envs !== null) { env = envs } - console.log("env: ", env) - setSelectedActionEnvironment(env); } } else if (data.type === "TRIGGER") { @@ -2898,7 +2908,7 @@ const AngularWorkflow = (defaultprops) => { }) } - const activateApp = (appid) => { + const activateApp = (appid, refresh) => { fetch(globalUrl+"/api/v1/apps/"+appid+"/activate", { method: 'GET', headers: { @@ -2918,7 +2928,11 @@ const AngularWorkflow = (defaultprops) => { if (responseJson.success === false) { alert.error("Failed to activate the app") } else { - alert.success("App activated for your organization!") + alert.success("App activated for your organization! Refresh the page to use the app.") + + if (refresh === true) { + getApps() + } } }) .catch(error => { @@ -3925,7 +3939,7 @@ const AngularWorkflow = (defaultprops) => { var found = false; var showEnvCnt = 0; for (var key in responseJson) { - if (responseJson[key].default) { + if (responseJson[key].default && !found) { setDefaultEnvironmentIndex(key); found = true; } @@ -3950,7 +3964,6 @@ const AngularWorkflow = (defaultprops) => { // FIXME: Don't allow multiple in cloud yet. Cloud -> Onprem isn't stable. if (isCloud) { - console.log("Envs: ", responseJson) if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) { setEnvironments(responseJson); } else { @@ -5685,7 +5698,7 @@ const AngularWorkflow = (defaultprops) => { //const activateApp = (appid) => { if (newAppData.activated === false) { console.log("SHOULD ACTIVATE!") - activateApp(newAppData.app_id) + activateApp(newAppData.app_id, false) } // AUTHENTICATION @@ -6161,6 +6174,233 @@ const AngularWorkflow = (defaultprops) => { } }; + const SearchBox = ({currentRefinement, refine, isSearchStalled, } ) => { + + useEffect(() => { + if (document !== undefined) { + const appsearchValue = document.getElementById("appsearch") + if (appsearchValue !== undefined && appsearchValue !== null) { + console.log("Value2: ", appsearchValue.value) + if (appsearchValue.value !== undefined && appsearchValue.value !== null && appsearchValue.value.length > 0) { + refine(appsearchValue.value) + } + } + //} + } + }, []) + + return ( +