From 68a4652da3e50f5792a4c397e84661341faa8a39 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 3 Feb 2025 11:32:37 +0100 Subject: [PATCH] Last sync for onprem --- frontend/src/components/ApiExplorer.jsx | 25 +- .../src/components/ShuffleCodeEditor1.jsx | 335 ++++++++++++++++-- frontend/src/views/ApiExplorerWrapper.jsx | 74 +++- frontend/src/views/AppCreator.jsx | 6 + 4 files changed, 378 insertions(+), 62 deletions(-) diff --git a/frontend/src/components/ApiExplorer.jsx b/frontend/src/components/ApiExplorer.jsx index 7e3092ea..8cdb3f0c 100644 --- a/frontend/src/components/ApiExplorer.jsx +++ b/frontend/src/components/ApiExplorer.jsx @@ -1183,6 +1183,7 @@ const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, se } } } + var prefixCheck = "/v1"; if (parentUrl.includes("/")) { const urlsplit = parentUrl.split("/"); @@ -1242,7 +1243,7 @@ const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, se } } - newActions = newActions2; + newActions = newActions2 // Rearrange them by which has action_label const firstActions = newActions.filter( @@ -1257,9 +1258,9 @@ const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, se data.action_label === null || data.action_label === "No Label" ); - newActions = firstActions.concat(secondActions); - setActions(newActions); - setExampleBody(newActions[0]?.body); + newActions = firstActions.concat(secondActions) + setActions(newActions) + setExampleBody(newActions[0]?.body) }, [openapi]); return ( @@ -1546,7 +1547,7 @@ const ActionsList = memo(({ overflow: "hidden", }} > - {action.name.replaceAll("_", " ")} + {action?.name?.replaceAll("_", " ")} )) @@ -1798,7 +1799,7 @@ const Action = memo(( response.result = JSON.parse(response.result); } catch (parseError) { console.error("Error parsing result:", parseError); - toast.error("Error parsing response result."); + //toast.error("Error parsing response result."); } } @@ -2228,11 +2229,12 @@ const Action = memo(( }} onChange={(e) => { - const newUrl = e.target.value; + const newUrl = e.target.value setActionUrl(newUrl); const params = extractParamsFromText(newUrl); setRequestParams(params); - + + if (newUrl.startsWith("http://") || newUrl.startsWith("https://")) { try { const url = new URL(newUrl); @@ -2241,9 +2243,12 @@ const Action = memo(( setPath(newPath); } catch (error) { - console.error("Invalid URL:", error); + console.error("Invalid URL:", newUrl, error); + toast("The URL is not a valid one. Please check and try again.") } - } + } else { + //toast("The URL needs to start with http:// or https://") + } }} /> diff --git a/frontend/src/components/ShuffleCodeEditor1.jsx b/frontend/src/components/ShuffleCodeEditor1.jsx index deca44a7..8acc23a8 100644 --- a/frontend/src/components/ShuffleCodeEditor1.jsx +++ b/frontend/src/components/ShuffleCodeEditor1.jsx @@ -15,6 +15,7 @@ import { MenuItem, Button, ButtonGroup, + Collapse, } from '@mui/material'; import theme from '../theme.jsx'; @@ -23,6 +24,7 @@ import { isMobile } from "react-device-detect" import { NestedMenuItem } from "mui-nested-menu" import { GetParsedPaths, FindJsonPath } from "../views/Apps.jsx"; import { SetJsonDotnotation } from "../views/AngularWorkflow.jsx"; +import Draggable from "react-draggable"; import { FullscreenExit as FullscreenExitIcon, @@ -152,6 +154,7 @@ const CodeEditor = (props) => { const [anchorEl3, setAnchorEl3] = React.useState(null); const [mainVariables, setMainVariables] = React.useState([]); const [availableVariables, setAvailableVariables] = React.useState([]); + const [sourceDataOpen, setSourceDataOpen] = React.useState(false); const [codeTheme, setcodeTheme] = React.useState("gruvbox-dark"); @@ -1150,20 +1153,23 @@ const CodeEditor = (props) => { // Define a custom completer for the Ace Editor const customCompleter = { getCompletions: function(editor, session, pos, prefix, callback) { - console.log("CUSTOM COMPLETER: ", prefix) - callback(null, availableVariables.map((variable) => { - console.log("CUSTOM VAR: ", variable) + //console.log("CUSTOM VAR: ", variable) return ({ caption: variable, value: variable, - meta: 'custom', + meta: 'var', }) })) } } + const editorLoad = (editor) => { + console.log("EDITOR: ", editor) + editor.completers = [customCompleter] + } + if (fullScreenMode) { return ( { highlightActiveLine={false} enableBasicAutocompletion={true} - completers={[customCompleter]} style={{ wordBreak: "break-word", @@ -1212,12 +1217,146 @@ const CodeEditor = (props) => { ) } + + const ValueBox = (props) => { + const { name, value } = props + + const [dragging, setDragging] = React.useState(false) + const [hovering, setHovering] = React.useState(false) + + if (name === undefined || name === null || name.length === 0) { + return null + } + + if (value === undefined || value === null || value.length === 0) { + return null + } + + return ( + { + e.preventDefault() + // Check if inside div.ace_content + if (e.srcElement.className === "ace_content") { + // Input on the correct line. Each line is: + // Show some tooltip at mouse cursor that shows "Insert Action" + + // Append the text to the DOM + } else { + //console.log("PAGEX: ", e.pageX, e.pageY) + //console.log("OffsetX: ", e.offsetX, e.offsetY) + //console.log("E: ", e) + } + + if (!dragging) { + setDragging(true) + } + }} + onStop={(e) => { + if (e.srcElement.className === "ace_content") { + console.log("DRAG STOP IN CONTENT!", e.srcElement.className) + + const usedposition = e.offsetY + if (usedposition === undefined || usedposition === null) { + toast.info("Error: LayerY is undefined or null. Please contact support@shuffler.io") + return + } + + if (usedposition === 0) { + usedposition = 1 + } + + const lineheight = 15 + const codedatasplit = localcodedata.split('\n') + if (codedatasplit === undefined || codedatasplit === null || codedatasplit.length === 0) { + return + } + + // Int + const lineposition = parseInt(usedposition/lineheight) + + // Find the correct line + if (lineposition > codedatasplit.length) { + codedatasplit[codedatasplit.length-1] += value + } else { + codedatasplit[lineposition] += value + } + + + //e.srcElement.layerY + setlocalcodedata(codedatasplit.join('\n')) + } + + setDragging(false) + }} + dragging={dragging} + position={{ + x: 0, + y: 0, + }} + onMouseHover={() => { + setHovering(true) + }} + onMouseLeave={() => { + setHovering(false) + }} + > +
+ {value} +
+
+ ) + } + + const SourceDataOption = (option) => { + const { innerdata, parsedPaths, defaultExpanded } = option + + const [expanded, setExpanded] = React.useState(defaultExpanded === true ? true : false) + + + return ( +
+
{ + setExpanded(!expanded) + }}> + + + {innerdata?.name} + +
+ + HELO + +
+ ) + } + return ( { @@ -1248,7 +1387,6 @@ const CodeEditor = (props) => { maxHeight: isMobile ? "100%" : 700, border: "3px solid rgba(255,255,255,0.3)", padding: isMobile ? "25px 10px 25px 10px" : 25, - // zoom: 0.8, backgroundColor: "black", }, }} @@ -1308,7 +1446,92 @@ const CodeEditor = (props) => {
-
+ {sourceDataOpen ? +
+ + Source Data + + + Drag the data you want into the text editor + + + {actionlist?.map((innerdata) => { + const icon = + innerdata.type === "action" ? ( + + ) : innerdata.type === "workflow_variable" || + innerdata.type === "execution_variable" ? ( + + ) : ( + + ); + + const handleExecArgumentHover = (inside) => { + var exec_text_field = document.getElementById( + "execution_argument_input_field" + ); + if (exec_text_field !== null) { + if (inside) { + exec_text_field.style.border = "2px solid #f85a3e"; + } else { + exec_text_field.style.border = ""; + } + } + }; + + const handleActionHover = (inside, actionId) => { + }; + + 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 (innerdata.type === "workflow_variable") { + // Try to parse the value if it's a string that could be JSON + if (typeof innerdata.value === "string") { + try { + const parsedValue = JSON.parse(innerdata.value) + if (typeof parsedValue === "object") { + parsedPaths = GetParsedPaths(parsedValue, ""); + } + } catch (e) { + // Not valid JSON, use the value directly + parsedPaths = GetParsedPaths(innerdata.value, ""); + } + } else if (typeof innerdata.value === "object") { + parsedPaths = GetParsedPaths(innerdata.value, ""); + } + } else if (typeof innerdata.example === "object") { + parsedPaths = GetParsedPaths(innerdata.example, ""); + } + + const coverColor = "#82ccc3" + + if (innerdata?.name === "Execution Argument") { + innerdata.name = "Runtime Argument" + } + + return ( + + ) + })} +
+ : null} + +
{isFileEditor ?
{ {isFileEditor ? null :
+ {/* + + */} + + { })} - {
} -
+
{ + console.log("DRAGGING OVER: ", e) + }} + onDrop={(e) => { + console.log("DROP: ", e) + }} + > {(availableVariables !== undefined && availableVariables !== null && availableVariables.length > 0) || isFileEditor ? ( { wordWrap: "break-word", backgroundColor: "rgba(40,40,40,1)", zIndex: activeDialog === "codeeditor" ? 1200 : 1100, + + }} onLoad={(editor) => { highlight_variables(localcodedata) + editorLoad(editor) }} onCursorChange={(cursorPosition, editor, value) => { setCurrentCharacter(cursorPosition.cursor.column) @@ -1847,8 +2099,15 @@ const CodeEditor = (props) => {
- {isFileEditor ? null : -
+ {isFileEditor ? null : +
{isMobile ? null : { >
- {selectedAction === undefined ? "" : selectedAction.name === "execute_python" || selectedAction.name === "execute_bash" ? "Code to run" : `Expected Output'`} + {selectedAction === undefined ? "" : selectedAction.name === "execute_python" || selectedAction.name === "execute_bash" ? "Code to run" : `Output`}
diff --git a/frontend/src/views/ApiExplorerWrapper.jsx b/frontend/src/views/ApiExplorerWrapper.jsx index 57070c8e..dec13a5b 100644 --- a/frontend/src/views/ApiExplorerWrapper.jsx +++ b/frontend/src/views/ApiExplorerWrapper.jsx @@ -95,6 +95,10 @@ const ApiExplorerWrapper = (props) => { }; useEffect(() => { + if (openapi?.id === "HTTP") { + selectedAppData.name = "HTTP" + } + if (selectedAppData !== undefined && selectedAppData !== null && Object.getOwnPropertyNames(selectedAppData).length > 0) { HandleAppAuthentication(selectedAppData?.name) } @@ -136,7 +140,10 @@ const ApiExplorerWrapper = (props) => { const runAlgoliaAppSearch = (appname) => { const index = searchClient.initIndex("appsearch"); - console.log("Running appsearch for: ", appname); + if (appname === "HTTP" || appname === "http") { + navigate("/apis") + return + } index .search(appname) @@ -151,6 +158,7 @@ const ApiExplorerWrapper = (props) => { if (newname?.includes(appsearchname)) { found = true + getAppData(hit.objectID) break } @@ -160,6 +168,7 @@ const ApiExplorerWrapper = (props) => { toast.error(`Failed to get API data for '${appname}' (1). Contact support@shuffler.io if this persists.`, { "autoClose": 10000, }) + setTimeout(()=>{ navigate("/search?tab=apps"); },3000) @@ -181,11 +190,29 @@ const ApiExplorerWrapper = (props) => { // Fetch data when appid is available const getAppData = useCallback((appid) => { if (appid === undefined || appid === null || appid.length === 0) { - toast.error("No API data to load (4). Please contact support@shuffler.io if this persists.") + toast.warning("No app ID loaded. Showing default API testing window. ") + setOpenapi({ + "id": "HTTP", + "servers": [ + {"url": "https://shuffler.io"}, + ], + "info": { + "title": "HTTP", + "x-logo": theme.palette?.defaultImage, + }, + "paths": { + "/api/v1/workflows/usecases": { + "get": { + "summary": "Custom Action", + } + } + } + }) + setAppLoaded(true) - setTimeout(() => { - navigate("/search?tab=apps") - }, 3000) + //setTimeout(() => { + // navigate("/search?tab=apps") + //}, 3000) return } @@ -413,6 +440,13 @@ const ApiExplorerWrapper = (props) => { selectedAppData.name = appname } + console.log("APPNAME: ", appname, openapi.id) + if (openapi?.id === "HTTP" || appname === "HTTP" || appname === "http") { + setAppAuthentication(data) + setSelectedAuthentication({}) + return + } + const filteredData = data.filter((appAuth) => appAuth?.app?.id === appid || appAuth?.app?.name?.replaceAll(" ", "_").toLowerCase() === selectedAppData?.name?.replaceAll(" ", "_").toLowerCase()); if (filteredData.length === 0) { setAppAuthentication([]) @@ -505,7 +539,7 @@ const ApiExplorerWrapper = (props) => { }else if (openapi?.id?.length > 0) { appid = openapi?.id; }else{ - toast.error("App id is missing. Please try again."); + toast.error("App id is missing and we can't run the API. Please contact support@shuffler.io if this persists."); return; } @@ -692,6 +726,7 @@ const ApiExplorerWrapper = (props) => { '& .MuiList-root': { backgroundColor: "#1f1f1f", }, + maxWidth: 500, }, } }} @@ -723,6 +758,7 @@ const ApiExplorerWrapper = (props) => { No Selection + {appAuthentication?.length > 0 ? appAuthentication.map((appAuth) => (
{ backgroundColor: '#1f1f1f', color: 'white', padding: '5px', + textAlign: "left", }} > { @@ -746,6 +782,11 @@ const ApiExplorerWrapper = (props) => { setAuthenticationName(appAuth.app?.name) }} > + {appAuth?.app?.large_image !== undefined && appAuth?.app?.large_image !== null && appAuth?.app?.large_image.length > 0 ? + + {appAuth?.app?.name} + + : null} {appAuth?.validation?.valid === true ? @@ -1645,7 +1686,9 @@ const ApiExplorerWrapper = (props) => { >
- {isLoggedIn === true ? + {openapi?.id === "HTTP" ? + null + : isLoggedIn === true ?