{
};
export default AppModal;
+
+
+const WorkflowCard = memo(({ app }) => {
+ const [name, setName] = useState("");
+ const [relatedWorkflows, setRelatedWorkflows] = useState(0);
+
+ const WorkflowHits = ({ hits }) => {
+ useEffect(() => {
+ setRelatedWorkflows(hits?.length || 0);
+ }, [hits]);
+
+ return null;
+ };
+
+ const CustomWorkflowHits = connectHits(WorkflowHits);
+
+ const SearchBox = ({ refine, currentRefinement }) => {
+ useEffect(() => {
+ if (name.length > 0 && currentRefinement !== name) {
+ refine(name?.split(" ")[0]);
+ }
+ }, [name, refine, currentRefinement]);
+
+ return null;
+ };
+
+ const CustomSearchBox = connectSearchBox(SearchBox);
+
+ useEffect(() => {
+ if (app?.name && app.name.trim().length > 0 && name !== app.name) {
+ const formattedName = app.name
+ .charAt(0)
+ .toUpperCase() + app.name.substring(1)
+ .replaceAll("_", " ");
+ setName(formattedName);
+ }
+ }, [app?.name]);
+
+ return (
+
+ {name.length > 0 ? (
+
+
+
+ {relatedWorkflows}
+
+ ) : (
+ relatedWorkflows
+ )}
+
+ );
+});
diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx
index 4732efc2..75cfc743 100755
--- a/frontend/src/components/ParsedAction.jsx
+++ b/frontend/src/components/ParsedAction.jsx
@@ -1160,6 +1160,12 @@ const ParsedAction = (props) => {
selectedActionParameters[1].value = splitparsed[1]
selectedAction.parameters[1].value = splitparsed[1]
+
+ if (splitparsed.length > 2) {
+ toast.warn("Filter list only supports filtering at the first level. If you want multi-level filtering, please use the 'execute python' action with the 'filter a list' function in the code editor.")
+ } else if (selectedAction.parameters[1].value.includes(".#")) {
+ toast.warn("This filter may not work due to using .# indexing. Please use the 'execute python' action and try the 'filter a list' function in the code editor.")
+ }
} else {
// Remove .# and after
const splitparsed = parsedvalue.split(".#")
diff --git a/frontend/src/components/ShuffleCodeEditor1.jsx b/frontend/src/components/ShuffleCodeEditor1.jsx
index 1f713c5a..2601af05 100644
--- a/frontend/src/components/ShuffleCodeEditor1.jsx
+++ b/frontend/src/components/ShuffleCodeEditor1.jsx
@@ -89,6 +89,7 @@ const liquidFilters = [
const pythonFilters = [
{ "name": "Hello World", "value": `print("hello world")`, "example": `` },
{ "name": "Using Shuffle variables", "value": `import json\nnodevalue = r\"\"\"$exec\"\"\"\nif not nodevalue:\n nodevalue = r\"\"\"{\"sample\": \"string\", \"int\": 1}\"\"\"\n \njsondata = json.loads(nodevalue)\nprint(jsondata)`, "example": `` },
+ { "name": "Filter a list", "value": `import json\nnodevalue = r\"\"\"$exec\"\"\"\nif not nodevalue:\n nodevalue = r\"\"\"[{\"sample\": \"string\", \"int\": 1, "malicious": "no"}, {\"sample\": \"string2\", \"int\": 1, "malicious": "yes"}]\"\"\"\n \njsondata = json.loads(nodevalue)\nfiltered = []\nfor item in jsondata:\n try:\n if item[\"malicious\"] == \"yes\":\n filtered.append(item)\n except:\n pass\nprint(json.dumps(filtered))`, "example": `` },
{ "name": "Print Execution ID", "value": `print(self.current_execution_id)`, "example": `` },
{ "name": "Get full execution details", "value": `print(self.full_execution)`, "example": `` },
{ "name": "Use files", "value": `# Create a sample file\nfiles = [{\n \"filename\": \"test.txt\",\n \"data\": \"Testdata\"\n}]\nret = self.set_files(files)\n\n# Get the content of the file from Shuffle storage\n# Originally a byte string in the \"data\" key\nfile_content = (self.get_file(ret[0])[\"data\"]).decode()\nprint(file_content)`, "example": `` },
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx
index bdbaf1c2..16d5e9d0 100755
--- a/frontend/src/views/AngularWorkflow.jsx
+++ b/frontend/src/views/AngularWorkflow.jsx
@@ -74,6 +74,7 @@ import {
import {
+ Storage as StorageIcon,
Code as CodeIcon,
Folder as FolderIcon,
VerifiedUser as VerifiedUserIcon,
@@ -503,7 +504,7 @@ const AngularWorkflow = (defaultprops) => {
const [allRevisions, setAllRevisions] = useState([])
const [menuPosition, setMenuPosition] = useState(null);
const [showDropdown, setShowDropdown] = React.useState(false);
- const [subflowActionList, setSubflowActionList] = React.useState([]);
+ const [triggerActionList, setTriggerActionList] = React.useState([]);
const [apps, setApps] = React.useState([]);
const [filteredApps, setFilteredApps] = React.useState([]);
@@ -574,11 +575,12 @@ const AngularWorkflow = (defaultprops) => {
}, [editWorkflowModalOpen])
useEffect(() => {
- if (selectedTrigger !== undefined && selectedTrigger?.parameters !== undefined && selectedTrigger?.parameters !== null && selectedTrigger?.parameters?.length > 1) {
- // Right now just setting for the subflow
- setSelectedTriggerValue(selectedTrigger?.parameters[1]?.value)
- }
- }, [selectedTrigger])
+ if(selectedTrigger?.trigger_type === "SUBFLOW"){
+ setSelectedTriggerValue(workflow.triggers[selectedTriggerIndex].parameters[1].value || "")
+ } else if(selectedTrigger?.trigger_type === "USERINPUT"){
+ setSelectedTriggerValue(workflow.triggers[selectedTriggerIndex].parameters[0].value || "Do you want to continue the workflow? Start parameters: $exec")
+ }
+ }, [selectedTriggerIndex, selectedTrigger, workflow])
useEffect(() => {
if(selectedEdge && Object.keys(selectedEdge).length > 0){
@@ -1136,23 +1138,66 @@ const AngularWorkflow = (defaultprops) => {
useEffect(() => {
// if (subflowActionList.length === 0) {
const newActionList = [];
- // FIXME: Have previous execution values in here
- newActionList.push({
- type: "Runtime Argument",
- name: "Runtime Argument",
- value: "$exec",
- highlight: "exec",
- autocomplete: "exec",
- example: "hello",
- })
- newActionList.push({
- type: "Shuffle Database",
- name: "Shuffle Database",
+
+
+ if (workflowExecutions.length > 0) {
+ for (let execution of workflowExecutions) {
+ const execArg = execution.execution_argument;
+ if (execArg && execArg.length > 0) {
+ const valid = validateJson(execArg);
+ if (valid.valid) {
+ newActionList.push({
+ type: "Runtime Argument",
+ name: "Runtime Argument",
+ value: "$exec",
+ highlight: "exec",
+ autocomplete: "exec",
+ example: valid.result,
+ })
+
+ break
+ }
+ }
+ }
+ }
+
+ // Add default Runtime Argument if none were added
+ if (newActionList.length === 0) {
+ newActionList.push({
+ type: "Runtime Argument",
+ name: "Runtime Argument",
+ value: "$exec",
+ highlight: "exec",
+ autocomplete: "exec",
+ example: "",
+ })
+ }
+ // Add Shuffle DB with cache keys if available
+ let cacheKey = {
+ type: "Shuffle DB",
+ name: "Shuffle DB",
value: "$shuffle_cache",
- highlight: "shuffle_db",
+ highlight: "shuffle_cache",
autocomplete: "shuffle_cache",
- example: "hello",
- })
+ example: "Hello",
+ }
+
+ if (listCache?.keys?.length > 0) {
+ cacheKey.example = {};
+ for (let item of listCache.keys) {
+ if (item.key) {
+ let itemValue = item.value ?? "";
+ if (itemValue.length > 10000) {
+ itemValue = "";
+ }
+ cacheKey.example[item.key.split(" ").join("_")] = { value: itemValue };
+ }
+ }
+ }
+
+ newActionList.push(cacheKey);
+ // FIXME: Have previous execution values in here
+
if (
workflow.workflow_variables !== null &&
workflow.workflow_variables !== undefined &&
@@ -1216,7 +1261,7 @@ const AngularWorkflow = (defaultprops) => {
const validated = validateJson(foundResult.result)
if (validated.valid) {
- exampledata = validateJson.result
+ exampledata = validated.result
break
}
}
@@ -1234,7 +1279,7 @@ const AngularWorkflow = (defaultprops) => {
}
}
- setSubflowActionList(newActionList);
+ setTriggerActionList(newActionList);
}, [workflow.workflow_variables, workflow.execution_variables, workflow, selectedTrigger]);
@@ -13779,6 +13824,7 @@ const AngularWorkflow = (defaultprops) => {
setSourceValue({});
setConditionValue({});
setDestinationValue({});
+ navigate("")
}}
color="secondary"
>
@@ -13789,6 +13835,7 @@ const AngularWorkflow = (defaultprops) => {
variant="contained"
onClick={() => {
setSelectedEdge({});
+ navigate("")
var data = {
condition: conditionValue,
@@ -14613,43 +14660,62 @@ const AngularWorkflow = (defaultprops) => {
};
const handleItemClick = (values) => {
- console.log("VALUES: ", values)
- if (values === undefined || values === null || values.length === 0) {
- return;
+ console.log("VALUES: ", values)
+ if (values === undefined || values === null || values.length === 0) {
+ return;
+ }
+
+ // Get the base autocomplete value and normalize it
+ let toComplete = values[0].autocomplete.toLowerCase().replaceAll(" ", "_");
+
+ // Add $ prefix if not already present
+ if (!toComplete.startsWith("$")) {
+ toComplete = "$" + toComplete;
+ }
+
+ // Add any additional path components
+ for (let key in values) {
+ if (key === "0" || !values[key].autocomplete) {
+ continue;
}
+ toComplete += values[key].autocomplete;
+ }
- /*
- workflow.triggers[selectedTriggerIndex].parameters[1].value
- .trim()
- .endsWith("$")
- ? values[0].autocomplete
- : "$" + values[0].autocomplete;
+ var foundField;
+ // Update the field value
+ if(selectedTrigger?.trigger_type === "SUBFLOW"){
+ foundField = document.getElementById("subflow_exec_field");
+ }else if(selectedTrigger?.trigger_type === "USERINPUT"){
+ foundField = document.getElementById("userinput_info_field");
+ }
- for (var key in values) {
- if (key === 0 || values[key].autocomplete.length === 0) {
- continue;
- }
+ if (foundField) {
+ // Get current cursor position
+ const cursorPos = foundField.selectionStart;
+ const currentValue = foundField.value;
- toComplete += values[key].autocomplete
- }
- */
-
- if (selectedTrigger.name === "Shuffle Workflow") {
- const toComplete = workflow?.triggers?.[selectedTriggerIndex]?.parameters?.[1]?.value + "$" + values[0]?.autocomplete
- // selectedTrigger.parameters[1].value = toComplete
- workflow.triggers[selectedTriggerIndex].parameters[1].value = toComplete
- const foundfield = document.getElementById("subflow_exec_field")
- if (foundfield !== undefined && foundfield !== null) {
- foundfield.value = toComplete
- }
- setWorkflow(workflow)
+ // Insert the new value at cursor position
+ const newValue = currentValue.slice(0, cursorPos) + toComplete + currentValue.slice(cursorPos);
+ foundField.value = newValue;
+
+ // Update state
+ setSelectedTriggerValue(newValue);
+ if(selectedTrigger?.trigger_type === "SUBFLOW"){
+ workflow.triggers[selectedTriggerIndex].parameters[1].value = newValue
+ }else if(selectedTrigger?.trigger_type === "USERINPUT"){
+ workflow.triggers[selectedTriggerIndex].parameters[0].value = newValue
}
- setUpdate(Math.random());
- setShowDropdown(false);
- setMenuPosition(null);
- };
+ // Set cursor position after inserted text
+ foundField.setSelectionRange(cursorPos + toComplete.length, cursorPos + toComplete.length);
+ }
+
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ setShowDropdown(false);
+ setMenuPosition(null);
+};
const subflowtypes = [
{
@@ -14676,7 +14742,7 @@ const AngularWorkflow = (defaultprops) => {
workflow.triggers[triggerIndex].parameters[paramIndex].value = newData;
// Update workflow state to trigger re-render
- setWorkflow({...workflow});
+ setWorkflow(workflow);
setSelectedTriggerValue(newData)
setLastSaved(false);
}
@@ -15166,7 +15232,7 @@ const AngularWorkflow = (defaultprops) => {
"name": workflow.triggers[selectedTriggerIndex].parameters[1].name,
"value": parsedvalue,
"field_number": 1,
- "actionlist": subflowActionList,
+ "actionlist": triggerActionList,
"field_id": "subflow_exec_field",
})
}}
@@ -15211,15 +15277,13 @@ const AngularWorkflow = (defaultprops) => {
fullWidth
color="primary"
placeholder="Some execution data"
- value={
- selectedTriggerValue
- }
+ value={selectedTriggerValue || ""}
onChange={(e) => {
setLastSaved(false)
setSelectedTriggerValue(e.target.value)
}}
- onBlur={() => {
- workflow.triggers[selectedTriggerIndex].parameters[1].value = selectedTriggerValue
+ onBlur={(e) => {
+ workflow.triggers[selectedTriggerIndex].parameters[1].value = e.target.value
setWorkflow(workflow)
}}
/>
@@ -15237,16 +15301,17 @@ const AngularWorkflow = (defaultprops) => {
marginTop: 2,
}}
>
- {subflowActionList.map((innerdata) => {
+ {triggerActionList.map((innerdata) => {
const icon =
innerdata.type === "action" ? (
) : innerdata.type === "workflow_variable" ||
innerdata.type === "execution_variable" ? (
- ) : (
-
- );
+ ) : innerdata.type === "Shuffle DB" ?
+
+ :
+
const handleExecArgumentHover = (inside) => {
var exec_text_field = document.getElementById(
@@ -16682,21 +16747,43 @@ const AngularWorkflow = (defaultprops) => {
*/}
-
-
- Information
-
- The information you want to show the user. Supports variables. Supports Markdown & HTML.
-
-
+
+
+ Information
+
+ {
+ event.preventDefault()
+ setCodeEditorModalOpen(true)
+ setActiveDialog("codeeditor")
+ var parsedvalue = workflow?.triggers[selectedTriggerIndex]?.parameters[0]?.value
+
+ navigate(`?trigger_id=${selectedTrigger.id}&trigger_field=${"alertinfo"}&trigger_name=${selectedTrigger.label}`)
+ setEditorData({
+ "name": workflow.triggers[selectedTriggerIndex].parameters[0].name,
+ "value": parsedvalue,
+ "field_number": 0,
+ "actionlist": triggerActionList,
+ "field_id": "information_field",
+ })
+ }}
+ >
+
+
+
+
+
+ The information you want to show the user. Supports variables. Supports Markdown & HTML.
+
{
InputProps={{
style: {
},
+ endAdornment: (
+
+
+ {
+ setMenuPosition({
+ top: event.pageY + 10,
+ left: event.pageX + 10,
+ });
+ //setShowDropdownNumber(3)
+ setShowDropdown(true);
+ }}
+ />
+
+
+ ),
}}
fullWidth
rows="4"
multiline
- defaultValue={
- workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers[selectedTriggerIndex].parameters !== undefined && workflow.triggers[selectedTriggerIndex].parameters.length > 0 && workflow.triggers[selectedTriggerIndex].parameters[0] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[0].value !== undefined ? workflow.triggers[selectedTriggerIndex].parameters[0].value : ""
- }
+ value={selectedTriggerValue}
+ onChange={(e) => {
+ setLastSaved(false)
+ setSelectedTriggerValue(e.target.value)
+ }}
color="primary"
placeholder=""
onBlur={(e) => {
setTriggerTextInformationWrapper(e.target.value);
}}
/>
+ {!showDropdown ? null :
+
+ }
+ parentMenuOpen={!!menuPosition}
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ color: "white",
+ minWidth: 250,
+ }}
+ onClick={() => {
+ handleItemClick([innerdata]);
+ }}
+ >
+ {parsedPaths.map((pathdata, index) => {
+ // FIXME: Should be recursive in here
+ const icon =
+ pathdata.type === "value" ? (
+
+ ) : pathdata.type === "list" ? (
+
+ ) : (
+
+ )
+
+ return (
+
+ );
+ })}
+
+ */}
+
+
+ {icon} {innerdata.name}
+
+ }
+ parentMenuOpen={!!menuPosition}
+ style={{
+ color: "white",
+ minWidth: 250,
+ maxWidth: 250,
+ maxHeight: 50,
+ overflow: "hidden",
+ }}
+ onClick={() => {
+ console.log(innerdata.example)
+ handleItemClick([innerdata]);
+ }}
+ >
+
+
+
+
+ {parsedPaths.map((pathdata, index) => {
+ // FIXME: Should be recursive in here
+ //
+ const icon =
+ pathdata.type === "value" ? (
+
+ ) : pathdata.type === "list" ? (
+
+ ) : (
+
+ );
+ //
+
+ const indentation_count = (pathdata.name.match(/\./g) || []).length + 1
+ const baseIndent =
+ //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 (
+
+ );
+ })}
+
+
+
+ ) : (
+
+ );
+ })}
+
+ }
{
src={validate.result}
theme={theme.palette.jsonTheme}
style={theme.palette.reactJsonStyle}
- collapsed={false}
shouldCollapse={(jsonField) => {
return collapseField(jsonField)
}}
@@ -20186,7 +20600,6 @@ const AngularWorkflow = (defaultprops) => {
src={validate.result}
theme={theme.palette.jsonTheme}
style={theme.palette.reactJsonStyle}
- collapsed={parsedCollapse}
shouldCollapse={(jsonField) => {
return collapseField(jsonField)
}}
@@ -21562,7 +21975,6 @@ const AngularWorkflow = (defaultprops) => {
src={validate.result}
theme={theme.palette.jsonTheme}
style={theme.palette.reactJsonStyle}
- collapsed={false}
shouldCollapse={(jsonField) => {
return collapseField(jsonField)
}}
@@ -21724,7 +22136,6 @@ const AngularWorkflow = (defaultprops) => {
src={checked.result}
theme={theme.palette.jsonTheme}
style={theme.palette.reactJsonStyle}
- collapsed={data.value.length < 10000 ? false : true}
shouldCollapse={(jsonField) => {
return collapseField(jsonField)
}}
@@ -22150,7 +22561,6 @@ const AngularWorkflow = (defaultprops) => {
src={validate.result}
theme={theme.palette.jsonTheme}
style={theme.palette.reactJsonStyle}
- collapsed={selectedResult.result.length < 10000 ? false : true}
shouldCollapse={(jsonField) => {
return collapseField(jsonField)
}}
@@ -22211,6 +22621,7 @@ const AngularWorkflow = (defaultprops) => {
)}
+
{selectedResult.action.parameters !== null &&
selectedResult.action.parameters !== undefined ? (
diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx
index e910417c..9b347eae 100644
--- a/frontend/src/views/Apps2.jsx
+++ b/frontend/src/views/Apps2.jsx
@@ -510,7 +510,6 @@ const Hits = ({
fontFamily: theme?.typography?.fontFamily
}}
onClick={() => {
- console.log("App modal", data)
handleAppClick(data);
}}
>
@@ -1299,6 +1298,8 @@ const Apps2 = (props) => {
//console.log("Failed to get apps from localstorage: ", e)
}
+ setIsLoading(true);
+
fetch(globalUrl + "/api/v1/apps", {
method: "GET",
headers: {