Synced 2.0 fixes for RC6

This commit is contained in:
Frikky
2025-02-13 03:46:14 +01:00
parent a8c13fe185
commit b367b4cfb7
10 changed files with 1107 additions and 402 deletions
+95 -114
View File
@@ -71,8 +71,7 @@ const EditWorkflow = (props) => {
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
const [submitLoading, setSubmitLoading] = React.useState(false); const [submitLoading, setSubmitLoading] = React.useState(false);
//const [showMoreClicked, setShowMoreClicked] = React.useState(expanded === true ? true : false); const [showMoreClicked, setShowMoreClicked] = React.useState(isEditing !== false ? true : false);
const [showMoreClicked, setShowMoreClicked] = React.useState(true);
const [innerWorkflow, setInnerWorkflow] = React.useState(workflow) const [innerWorkflow, setInnerWorkflow] = React.useState(workflow)
@@ -569,12 +568,13 @@ const EditWorkflow = (props) => {
<Divider id="mssp_control" style={{ marginTop: 20, marginBottom: 20, }} /> <Divider id="mssp_control" style={{ marginTop: 20, marginBottom: 20, }} />
<Typography variant="h4" style={{ marginTop: 50, }}> <Typography variant="h4" style={{ marginTop: 50, }}>
MSSP controls MSSP & Distribution controls
</Typography> </Typography>
<Typography variant="body1" style={{ marginTop: 50, }}> <Typography variant="body2" color="textSecondary" style={{ marginTop: 30, marginBottom: 10, }}>
MSSP Suborg Distribution (<b>beta</b> - contact support@shuffler.io for more info) Multi-Tenant Workflows. Make one workflow, and keep a separate, synced copy in all your tenants. Control distributed auth, runtime locations, files, datastore keys etc. (<b>late beta</b> - contact support@shuffler.io if you want a demo. Please try it!)
</Typography> </Typography>
{userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ? {userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ?
userdata.orgs.filter(org => org.creator_org === userdata.active_org.id).length === 0 ? userdata.orgs.filter(org => org.creator_org === userdata.active_org.id).length === 0 ?
userdata.active_org.creator_org === undefined || userdata.active_org.creator_org === null || userdata.active_org.creator_org === "" ? userdata.active_org.creator_org === undefined || userdata.active_org.creator_org === null || userdata.active_org.creator_org === "" ?
@@ -677,15 +677,12 @@ const EditWorkflow = (props) => {
</Link> </Link>
} }
{/*<Divider style={{marginTop: 20, marginBottom: 20, }} />*/}
<Typography variant="body1" style={{ marginTop: 75, }}>
<Typography variant="body1" style={{ marginTop: 100, }}>
Git Backup Repository Git Backup Repository
</Typography> </Typography>
<Typography variant="body2" style={{ textAlign: "left", marginTop: 5, }} color="textSecondary"> <Typography variant="body2" style={{ textAlign: "left", marginTop: 5, }} color="textSecondary">
Decide where this workflow is backed up in a Git repository. Will create logs and notifications if upload fails. <b>The repository and branch must already have been initialized</b>. Files will show up in the root folder in the format 'orgid/workflow status/workflow id.json' without images. Overrides your <a href="/admin?admin_tab=org_config" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">default backup repository</a>. <a href="/docs/configuration#environment-variables" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">Credentials are encrypted.</a> Creates <a href="/admin?admin_tab=notifications" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">notifications</a> if it fails. Decide where this workflow is backed up in a Git repository. Will create logs and notifications if upload fails. <b>The repository and branch must already have been initialized</b>. Files will show up in the root folder in the format 'orgid/workflow status/workflow id.json' and be formatted, with removed images, to make diffing easy. Overrides your <a href="/admin?admin_tab=org_config" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">default backup repository</a>. <a href="/docs/configuration#environment-variables" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">Credentials are encrypted.</a> Creates <a href="/admin?admin_tab=notifications" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">notifications</a> if it fails.
</Typography> </Typography>
<Grid container style={{ marginTop: 10, }} spacing={2}> <Grid container style={{ marginTop: 10, }} spacing={2}>
<Grid item xs={6} style={{}}> <Grid item xs={6} style={{}}>
@@ -1078,122 +1075,106 @@ const EditWorkflow = (props) => {
</Select> </Select>
</FormControl> </FormControl>
</div> </div>
</div>
: null}
{/*!isEditing ? <>
<div style={{ marginTop: 20, }}> <Typography variant="h4" style={{ marginTop: 100, }}>
<FormControlLabel Publishing
control={<Checkbox />} </Typography>
label="Create workflow as code" <Typography variant="body2" color="textSecondary" style={{ marginTop: 10, }}>
style={{ marginTop: '12px' }} Publishing is related to making the workflow itself public. When publishing a workflow, all the details (except sensitive info) become available to everyone. The details below will help a user understand this better. When a workflow is published, you keep the original, and a copy enters the workflow search, and is associated with your <a href="/creators" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">creator account</a>, if you have one. You can always unpublish the workflow after. When ready to publish, click the three dots next to a workflow on the main workflow screen. After publishing, you can find it in the Shuffle search engine.
onChange={(e) => { </Typography>
setWorkflowAsCode(e.target.checked)
workflow.workflow_as_code = e.target.checked <LocalizationProvider style={{marginLeft: 0, }} dateAdapter={AdapterDayjs}>
setWorkflow(workflow) <DatePicker
setInnerWorkflow(workflow) sx={{
marginTop: 3,
marginLeft: 3,
}}
value={dueDate}
label="Due Date"
format="YYYY-MM-DD"
onChange={(newValue) => {
setDueDate(newValue)
}} }}
/> />
</LocalizationProvider>
</div> <FormControl style={{ marginTop: 15, }}>
</> : null*/} <FormLabel id="demo-row-radio-buttons-group-label">Type</FormLabel>
<RadioGroup
row
aria-labelledby="demo-row-radio-buttons-group-label"
name="row-radio-buttons-group"
defaultValue={innerWorkflow.workflow_type}
onChange={(e) => {
console.log("Data: ", e.target.value)
<Typography variant="h4" style={{ marginTop: 100, }}> innerWorkflow.workflow_type = e.target.value
Publishing setInnerWorkflow(innerWorkflow)
</Typography> }}
<Typography variant="body1" color="textSecondary" style={{ marginTop: 10, }}> >
Publishing is related to making the workflow itself public. When publishing a workflow, all the details (except sensitive info) become available to everyone. The details below will help a user understand this better. When a workflow is published, you keep the original, and a copy enters the workflow search, and is associated with your <a href="/creators" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">creator account</a>, if you have one. You can always unpublish the workflow after. To publish it, click the three dots next to the workflow. <FormControlLabel value="trigger" control={<Radio />} label="Trigger" />
</Typography> <FormControlLabel value="subflow" control={<Radio />} label="Subflow" />
<FormControlLabel value="standalone" control={<Radio />} label="Standalone" />
<LocalizationProvider style={{marginLeft: 0, }} dateAdapter={AdapterDayjs}> </RadioGroup>
<DatePicker </FormControl>
sx={{
marginTop: 3,
marginLeft: 3,
}}
value={dueDate}
label="Due Date"
format="YYYY-MM-DD"
onChange={(newValue) => {
setDueDate(newValue)
}}
/>
</LocalizationProvider>
<FormControl style={{ marginTop: 15, }}>
<FormLabel id="demo-row-radio-buttons-group-label">Type</FormLabel>
<RadioGroup
row
aria-labelledby="demo-row-radio-buttons-group-label"
name="row-radio-buttons-group"
defaultValue={innerWorkflow.workflow_type}
onChange={(e) => {
console.log("Data: ", e.target.value)
innerWorkflow.workflow_type = e.target.value <TextField
onBlur={(event) => {
innerWorkflow.blogpost = event.target.value
setInnerWorkflow(innerWorkflow) setInnerWorkflow(innerWorkflow)
}} }}
> InputProps={{
<FormControlLabel value="trigger" control={<Radio />} label="Trigger" /> style: {
<FormControlLabel value="subflow" control={<Radio />} label="Subflow" /> color: "white",
<FormControlLabel value="standalone" control={<Radio />} label="Standalone" /> },
</RadioGroup>
</FormControl>
<TextField
onBlur={(event) => {
innerWorkflow.blogpost = event.target.value
setInnerWorkflow(innerWorkflow)
}}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
defaultValue={innerWorkflow.blogpost}
placeholder="A blogpost or other reference for how this work workflow was built, and what it's for."
rows="1"
label="blogpost"
margin="dense"
fullWidth
/>
<TextField
onBlur={(event) => {
innerWorkflow.video = event.target.value
setInnerWorkflow(innerWorkflow)
}}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
defaultValue={innerWorkflow.video}
placeholder="A youtube or loom link to the video"
rows="1"
label="Video"
margin="dense"
fullWidth
/>
<Tooltip color="primary" title={"Add more details"} placement="top">
<Button
style={{ margin: "auto", marginTop: 50, marginBottom: 10, textAlign: "center", textTransform: "none", }}
variant="outlined"
disabled={newWorkflow === true}
color="secondary"
onClick={() => {
setShowMoreClicked(!showMoreClicked);
}} }}
> color="primary"
{showMoreClicked ? <ExpandLessIcon style={{ marginRight: 10, }} /> : <ExpandMoreIcon style={{ marginRight: 10, }} />} defaultValue={innerWorkflow.blogpost}
{showMoreClicked ? "Less Options" : "More Options"} placeholder="A blogpost or other reference for how this work workflow was built, and what it's for."
rows="1"
label="blogpost"
margin="dense"
fullWidth
/>
<TextField
onBlur={(event) => {
innerWorkflow.video = event.target.value
setInnerWorkflow(innerWorkflow)
}}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
defaultValue={innerWorkflow.video}
placeholder="A youtube or loom link to the video"
rows="1"
label="Video"
margin="dense"
fullWidth
/>
</Button> <Tooltip color="primary" title={"Add more details"} placement="top">
</Tooltip> <Button
style={{ margin: "auto", marginTop: 50, marginBottom: 10, textAlign: "center", textTransform: "none", }}
variant="outlined"
disabled={newWorkflow === true}
color="secondary"
onClick={() => {
setShowMoreClicked(!showMoreClicked);
}}
>
{showMoreClicked ? <ExpandLessIcon style={{ marginRight: 10, }} /> : <ExpandMoreIcon style={{ marginRight: 10, }} />}
{showMoreClicked ? "Less Options" : "More Options"}
</Button>
</Tooltip>
</div>
: null}
</div> </div>
+2 -2
View File
@@ -524,7 +524,7 @@ useEffect(() => {
<Divider style={{ marginBottom: 10, }} /> <Divider style={{ marginBottom: 10, }} />
<Typography color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, fontSize: 18 }}> <Typography color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, fontSize: 18 }}>
Version: 2.0.0-rc5 Version: 2.0.0-rc6
</Typography> </Typography>
</Menu> </Menu>
</span> </span>
@@ -1907,4 +1907,4 @@ useEffect(() => {
); );
}; };
export default LeftSideBar; export default LeftSideBar;
+40 -18
View File
@@ -222,6 +222,10 @@ const ParsedAction = (props) => {
return return
} }
if (selectedApp !== undefined && selectedApp !== null && selectedApp.generated !== true) {
return
}
// Fixing required fields with a shitty structure :) // Fixing required fields with a shitty structure :)
if (selectedApp !== undefined && selectedApp !== null && selectedApp.generated === true && selectedAction !== undefined && selectedAction !== null && selectedAction.name !== undefined && selectedAction.name !== null && selectedApp.actions !== undefined && selectedApp.actions !== null && selectedApp.actions.length > 0 && (selectedAction.required_body_fields === undefined || selectedAction.required_body_fields === null || selectedAction.required_body_fields.length === 0)) { if (selectedApp !== undefined && selectedApp !== null && selectedApp.generated === true && selectedAction !== undefined && selectedAction !== null && selectedAction.name !== undefined && selectedAction.name !== null && selectedApp.actions !== undefined && selectedApp.actions !== null && selectedApp.actions.length > 0 && (selectedAction.required_body_fields === undefined || selectedAction.required_body_fields === null || selectedAction.required_body_fields.length === 0)) {
// Check for required fields // Check for required fields
@@ -654,8 +658,8 @@ const ParsedAction = (props) => {
const valid = validateJson(execArg); const valid = validateJson(execArg);
if (valid.valid) { if (valid.valid) {
newActionList.push({ newActionList.push({
type: "Execution Argument", type: "Runtime Argument",
name: "Execution Argument", name: "Runtime Argument",
value: "$exec", value: "$exec",
highlight: "exec", highlight: "exec",
autocomplete: "exec", autocomplete: "exec",
@@ -668,11 +672,11 @@ const ParsedAction = (props) => {
} }
} }
// Add default Execution Argument if none were added // Add default Runtime Argument if none were added
if (newActionList.length === 0) { if (newActionList.length === 0) {
newActionList.push({ newActionList.push({
type: "Execution Argument", type: "Runtime Argument",
name: "Execution Argument", name: "Runtime Argument",
value: "$exec", value: "$exec",
highlight: "exec", highlight: "exec",
autocomplete: "exec", autocomplete: "exec",
@@ -749,7 +753,7 @@ const ParsedAction = (props) => {
if (parents.length > 1) { if (parents.length > 1) {
const labels = []; const labels = [];
for (let parentNode of parents) { for (let parentNode of parents) {
if (parentNode.label !== "Execution Argument" && !labels.includes(parentNode.label)) { if (parentNode.label !== "Runtime Argument" && !labels.includes(parentNode.label)) {
labels.push(parentNode.label); labels.push(parentNode.label);
let exampleData = parentNode.example ?? ""; let exampleData = parentNode.example ?? "";
if (!exampleData && workflowExecutions.length > 0) { if (!exampleData && workflowExecutions.length > 0) {
@@ -1625,15 +1629,17 @@ const ParsedAction = (props) => {
//window.open("/apps/${selectedAction.app_id}", "_blank") //window.open("/apps/${selectedAction.app_id}", "_blank")
}} }}
> >
<Tooltip title={"App: " + selectedAction.app_name} placement="top"> <Tooltip title={"App: " + selectedAction.app_name + ". Click to open in new tab"} placement="top">
<img src={selectedAppIcon} style={{ <a href={"/apps/" + selectedAction?.app_id} target="_blank" style={{ textDecoration: "none", color: "white", }}>
width: 30, <img src={selectedAppIcon} style={{
height: 30, width: 30,
marginRight: 10, height: 30,
borderRadius: 5, marginRight: 10,
marginTop: 13, borderRadius: 5,
border: "2px solid rgba(255,255,255,0.3)", marginTop: 13,
}} /> border: "2px solid rgba(255,255,255,0.3)",
}} />
</a>
</Tooltip> </Tooltip>
<h3 style={{}}> <h3 style={{}}>
@@ -1754,7 +1760,7 @@ const ParsedAction = (props) => {
</Tooltip> </Tooltip>
</IconButton> </IconButton>
{selectedAction?.generated === true && selectedAction?.app_version === "1.0.0" ? {(selectedAction?.generated === true && selectedAction?.app_version === "1.0.0") || (selectedAction?.app_name === "Shuffle Tools" && selectedAction?.app_version !== "1.2.0") ?
<Button <Button
variant="contained" variant="contained"
color="secondary" color="secondary"
@@ -1857,6 +1863,7 @@ const ParsedAction = (props) => {
}} }}
fullWidth fullWidth
color="primary" color="primary"
disabled={selectedAction?.parent_controlled === true && workflow?.parentorg_workflow?.length > 0}
placeholder={selectedAction.label} placeholder={selectedAction.label}
value={appActionName} value={appActionName}
onChange={(event) => { onChange={(event) => {
@@ -2077,6 +2084,7 @@ const ParsedAction = (props) => {
style: theme.palette.innerTextfieldStyle, style: theme.palette.innerTextfieldStyle,
disableUnderline: true, disableUnderline: true,
}} }}
disabled={selectedAction?.parent_controlled === true && workflow?.parentorg_workflow?.length > 0}
placeholder={selectedAction.execution_delay} placeholder={selectedAction.execution_delay}
value={delay} value={delay}
onChange={(event) => { onChange={(event) => {
@@ -2779,6 +2787,20 @@ const ParsedAction = (props) => {
/> />
) : null} ) : null}
{selectedAction?.app_name === "Shuffle AI" && selectedAction?.name === "run_llm" ?
selectedAction?.environment === "Cloud" && isCloud ? (
<Typography color="textSecondary" variant="body2" style={{ paddingTop: 25, }}>
Info: Cloud Inference processing runs with Shuffle's GPUs in EU, Netherlands, and may be unstable. Your data is NOT stored there.
</Typography>
)
:
<Typography color="textSecondary" variant="body2" style={{ paddingTop: 25, color: red, }}>
This action is slow without GPU's. Use Shuffle's Cloud Runtime location for faster processing.
</Typography>
:
null
}
<div <div
style={{ style={{
marginTop: "10px", marginTop: "10px",
@@ -4251,7 +4273,7 @@ const ParsedAction = (props) => {
}; };
const handleMouseover = () => { const handleMouseover = () => {
if (innerdata.type === "Execution Argument") { if (innerdata.type === "Runtime Argument") {
handleExecArgumentHover(true); handleExecArgumentHover(true);
} else if (innerdata.type === "action") { } else if (innerdata.type === "action") {
handleActionHover(true, innerdata.id); handleActionHover(true, innerdata.id);
@@ -4259,7 +4281,7 @@ const ParsedAction = (props) => {
}; };
const handleMouseOut = () => { const handleMouseOut = () => {
if (innerdata.type === "Execution Argument") { if (innerdata.type === "Runtime Argument") {
handleExecArgumentHover(false); handleExecArgumentHover(false);
} else if (innerdata.type === "action") { } else if (innerdata.type === "action") {
handleActionHover(false, innerdata.id); handleActionHover(false, innerdata.id);
+61
View File
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useContext, memo } from "react";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import theme from "../theme.jsx"; import theme from "../theme.jsx";
import { v4 as uuidv4, v5 as uuidv5, validate as isUUID, } from "uuid";
import { import {
Paper, Paper,
Tooltip, Tooltip,
@@ -16,7 +17,13 @@ import {
Autocomplete, Autocomplete,
TextField, TextField,
MenuItem, MenuItem,
IconButton,
} from "@mui/material"; } from "@mui/material";
import {
OpenInNew as OpenInNewIcon,
} from "@mui/icons-material";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { Context } from "../context/ContextApi.jsx"; import { Context } from "../context/ContextApi.jsx";
@@ -355,6 +362,7 @@ const Priorities = memo((props) => {
<Typography style={{ color: "rgba(158, 158, 158, 1)", fontSize: 16, fontWeight: 400, marginTop: 5, }}> <Typography style={{ color: "rgba(158, 158, 158, 1)", fontSize: 16, fontWeight: 400, marginTop: 5, }}>
The notification workflow triggers when an error occurs in one of your workflows. Each individual one will only start a workflow once every 2 minutes. <b>You can point child org notifications into the parent org notification by choosing it in the list.</b> The notification workflow triggers when an error occurs in one of your workflows. Each individual one will only start a workflow once every 2 minutes. <b>You can point child org notifications into the parent org notification by choosing it in the list.</b>
</Typography> </Typography>
<div style={{ display: "flex", flexDirection: "row", alignItems: "center", }}> <div style={{ display: "flex", flexDirection: "row", alignItems: "center", }}>
{workflows !== undefined && workflows !== null && workflows.length > 0 ? {workflows !== undefined && workflows !== null && workflows.length > 0 ?
@@ -538,6 +546,59 @@ const Priorities = memo((props) => {
</div> */} </div> */}
</div> </div>
{notificationWorkflow === undefined || notificationWorkflow === null || notificationWorkflow.length === 0 ? null :
<div>
<Button variant="outlined" color="secondary" style={{marginTop: 5, textTransform: "none", }} onClick={() => {
if (notificationWorkflow === "parent") {
toast.error("Can't send test notifications to the parent org's notification workflow.")
return
}
fetch(`${globalUrl}/api/v1/workflows/${notificationWorkflow}/execute`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
credentials: "include",
body: JSON.stringify({
"title": "Test Notification",
"description": "This is a test notification to check if the notification workflow is working correctly.",
"org_id": selectedOrganization.id,
"id": uuidv4(),
"reference_url": "/admin?type=test&admin_tab=notifications",
"created_at": Math.floor(new Date().getTime() / 1000),
"updated_at": Math.floor(new Date().getTime() / 1000),
})
})
.then((response) => {
if (response.status === 200) {
toast.success("Test notification sent successfully.")
} else {
toast.error("Failed to send test notification. Please contact support if this persists")
}
}).catch((error) => {
toast.error("Failed to send test notification (2). Please contact support if this persists")
})
}}>
Send test notification
</Button>
<IconButton
style={{marginLeft: 10, }}
onClick={() => {
if (notificationWorkflow === "parent") {
toast.error("Can't open parent org's notification workflow from here.")
return
}
window.open(`/workflows/${notificationWorkflow}?view=executions`, "_blank")
}}
>
<OpenInNewIcon color="primary" />
</IconButton>
</div>
}
<Typography style={{marginTop: 50, fontSize: 24, fontWeight: 'bold', display: clickedFromOrgTab?null:"inline", marginBottom: clickedFromOrgTab? 8:null, color: clickedFromOrgTab?"#ffffff":null }}>Notifications ({ <Typography style={{marginTop: 50, fontSize: 24, fontWeight: 'bold', display: clickedFromOrgTab?null:"inline", marginBottom: clickedFromOrgTab? 8:null, color: clickedFromOrgTab?"#ffffff":null }}>Notifications ({
notifications?.filter((notification) => showRead === true || notification.read === false).length notifications?.filter((notification) => showRead === true || notification.read === false).length
})</Typography> })</Typography>
+133 -46
View File
@@ -44,6 +44,7 @@ import {
Close as CloseIcon, Close as CloseIcon,
DragIndicator as DragIndicatorIcon, DragIndicator as DragIndicatorIcon,
RestartAlt as RestartAltIcon, RestartAlt as RestartAltIcon,
ArrowForward as ArrowForwardIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
@@ -100,6 +101,7 @@ const pythonFilters = [
const extensions = [] const extensions = []
const CodeEditor = (props) => { const CodeEditor = (props) => {
const { const {
cy,
workflow, workflow,
globalUrl, globalUrl,
fieldCount, fieldCount,
@@ -116,6 +118,7 @@ const CodeEditor = (props) => {
parameterName, parameterName,
// selectedAction, // selectedAction,
// selectedTrigger, // selectedTrigger,
selectedEdge,
workflowExecutions, workflowExecutions,
getParents, getParents,
activeDialog, activeDialog,
@@ -128,6 +131,8 @@ const CodeEditor = (props) => {
fullScreenMode, fullScreenMode,
environment, environment,
fixExample, fixExample,
userdata,
handleConditionFieldChange,
} = props } = props
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata); const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
@@ -149,6 +154,7 @@ const CodeEditor = (props) => {
const [currentLine, setCurrentLine] = React.useState(-1); const [currentLine, setCurrentLine] = React.useState(-1);
const [selectedAction, setSelectedAction] = React.useState({}); const [selectedAction, setSelectedAction] = React.useState({});
const [selectedTrigger, setSelectedTrigger] = React.useState({}); const [selectedTrigger, setSelectedTrigger] = React.useState({});
const [selectedCondition, setSelectedCondition] = React.useState({});
const [variableOccurences, setVariableOccurences] = React.useState([]); const [variableOccurences, setVariableOccurences] = React.useState([]);
const [currentLocation, setCurrentLocation] = React.useState([]); const [currentLocation, setCurrentLocation] = React.useState([]);
const [currentVariable, setCurrentVariable] = React.useState(""); const [currentVariable, setCurrentVariable] = React.useState("");
@@ -197,6 +203,8 @@ const CodeEditor = (props) => {
const triggerId = searchParams.get('trigger_id'); const triggerId = searchParams.get('trigger_id');
const triggerField = searchParams.get('trigger_field'); const triggerField = searchParams.get('trigger_field');
const triggerName = searchParams.get('trigger_name'); const triggerName = searchParams.get('trigger_name');
const conditionId = searchParams.get('condition_id');
const conditionField = searchParams.get('field');
useEffect(() => { useEffect(() => {
if (actionId === undefined || actionId === null) { if (actionId === undefined || actionId === null) {
@@ -206,6 +214,9 @@ const CodeEditor = (props) => {
const action = workflow?.actions?.find(action => action.id === actionId); const action = workflow?.actions?.find(action => action.id === actionId);
setlocalcodedata(editorData?.value); setlocalcodedata(editorData?.value);
setSelectedAction(action); setSelectedAction(action);
// Update available variables when action changes
updateAvailableVariables(actionlist);
}, [actionId, fieldName]) }, [actionId, fieldName])
useEffect(() => { useEffect(() => {
@@ -216,16 +227,33 @@ const CodeEditor = (props) => {
const trigger = workflow?.triggers?.find(trigger => trigger.id === triggerId); const trigger = workflow?.triggers?.find(trigger => trigger.id === triggerId);
setlocalcodedata(editorData?.value); setlocalcodedata(editorData?.value);
setSelectedTrigger(trigger); setSelectedTrigger(trigger);
// Update available variables when trigger changes
updateAvailableVariables(actionlist);
}, [triggerId]) }, [triggerId])
useEffect(() => {
var allVariables = []
var tmpVariables = []
useEffect(() => {
if (conditionId === undefined || conditionId === null) {
return;
}
const condition = selectedEdge?.conditions?.find(condition => condition.id === conditionId);
setlocalcodedata(editorData?.value);
setSelectedCondition(condition);
// Update available variables when condition changes
updateAvailableVariables(actionlist);
}, [conditionId, fieldName])
// Extract variable updating logic into a separate function
const updateAvailableVariables = (actionlist) => {
if (actionlist === undefined || actionlist === null) { if (actionlist === undefined || actionlist === null) {
return return
} }
var allVariables = []
var tmpVariables = []
for (var i = 0; i < actionlist.length; i++) { for (var i = 0; i < actionlist.length; i++) {
allVariables.push('$' + actionlist[i].autocomplete.toLowerCase()) allVariables.push('$' + actionlist[i].autocomplete.toLowerCase())
tmpVariables.push('$' + actionlist[i].autocomplete.toLowerCase()) tmpVariables.push('$' + actionlist[i].autocomplete.toLowerCase())
@@ -265,7 +293,11 @@ const CodeEditor = (props) => {
setAvailableVariables(allVariables) setAvailableVariables(allVariables)
setMainVariables(tmpVariables) setMainVariables(tmpVariables)
expectedOutput(localcodedata) }
// Remove the original useEffect for actionlist since we'll update on action/trigger changes
useEffect(() => {
updateAvailableVariables(actionlist)
}, []) }, [])
useEffect(() => { useEffect(() => {
@@ -1378,6 +1410,18 @@ const CodeEditor = (props) => {
) )
} }
var sourceAction = ""
var targetAction = ""
var sourceImage = ""
var targetImage = ""
if (cy !== undefined && cy !== null) {
sourceAction = cy.getElementById(selectedEdge?.source)
targetAction = cy.getElementById(selectedEdge?.target)
sourceImage = sourceAction?.data()?.large_image
targetImage = targetAction?.data()?.large_image
}
return ( return (
<Dialog <Dialog
aria-labelledby="draggable-dialog-title" aria-labelledby="draggable-dialog-title"
@@ -1601,25 +1645,25 @@ const CodeEditor = (props) => {
{isFileEditor ? null : {isFileEditor ? null :
<div style={{ display: "flex", maxHeight: 40, }}> <div style={{ display: "flex", maxHeight: 40, }}>
<ButtonGroup style={{ borderRadius: theme.palette.borderRadius, }}> <ButtonGroup style={{ borderRadius: theme.palette.borderRadius, }}>
{/* {userdata !== undefined && userdata !== null && userdata.support === true ?
<Button <Button
id="basic-button" id="basic-button"
aria-haspopup="true" aria-haspopup="true"
aria-controls={!!menuPosition ? 'basic-menu' : undefined} aria-controls={!!menuPosition ? 'basic-menu' : undefined}
aria-expanded={!!menuPosition ? 'true' : undefined} aria-expanded={!!menuPosition ? 'true' : undefined}
variant={!sourceDataOpen ? "contained" : "outlined"} variant={!sourceDataOpen ? "contained" : "outlined"}
color="secondary" color="secondary"
style={{ style={{
textTransform: "none", textTransform: "none",
width: 175, width: 175,
}} }}
onClick={(event) => { onClick={(event) => {
setSourceDataOpen(!sourceDataOpen) setSourceDataOpen(!sourceDataOpen)
}} }}
> >
<AddIcon /> Show Source Data <AddIcon /> Show Source Data
</Button> </Button>
*/} : null}
<Button <Button
id="basic-button" id="basic-button"
@@ -2149,9 +2193,64 @@ const CodeEditor = (props) => {
}} }}
> >
<div> <div>
<span style={{ color: "white" }}> {actionId === null && triggerId === null ?
{actionId === null ? `Output : ${triggerName?.replaceAll("_", " ").slice(0, 1).toUpperCase() + triggerName?.replaceAll("_", " ").slice(1)}` : selectedAction.name === "execute_python" || selectedAction.name === "execute_bash" ? "Code to run" : `Output : ${appName?.replaceAll("_", " ").slice(0, 1).toUpperCase() + appName?.replaceAll("_", " ").slice(1)}(${fieldName})`} <div style={{display: "flex", alignItems: "center"}}>
</span> {`Condition ${selectedEdge?.conditions?.findIndex(cond => cond.condition.id === conditionId) + 1 || "0"}`}
{/* Source node image */}
{selectedEdge?.source ?
<img
src={sourceImage || ""}
alt="Source"
style={{
width: 30,
height: 30,
marginRight: 10,
borderRadius: "50%",
marginLeft: 10,
border: conditionField === "source" ? `3px solid #FF8544` : null,
}}
/>
: null
}
{/* Add arrow icon */}
{
selectedEdge && Object.keys(selectedEdge).length > 0 ?
<ArrowForwardIcon style={{
color: "rgba(255,255,255,0.7)",
fontSize: 18,
marginLeft: -5,
marginRight: -5,
}} />
: null
}
{/* Destination node image */}
{selectedEdge?.target ?
<img
src={targetImage || ""}
alt="Destination"
style={{
width: 30,
height: 30,
marginLeft: 10,
borderRadius: "50%",
border: conditionField === "destination" ? `3px solid #FF8544` : null,
}}
/>
: null
}
</div>
:
<span style={{ color: "white" }}>
{selectedAction.name === "execute_python" || selectedAction.name === "execute_bash" ?
"Code to run" :
triggerId ?
`Output : ${triggerName?.replaceAll("_", " ").slice(0, 1).toUpperCase() + triggerName?.replaceAll("_", " ").slice(1)}(${triggerField})` :
`Output : ${appName?.replaceAll("_", " ").slice(0, 1).toUpperCase() + appName?.replaceAll("_", " ").slice(1)}(${fieldName})`
}
</span>
}
</div> </div>
</DialogTitle> </DialogTitle>
@@ -2369,34 +2468,22 @@ const CodeEditor = (props) => {
if (isFileEditor === true) { if (isFileEditor === true) {
runUpdateText(fixedcodedata); runUpdateText(fixedcodedata);
setcodedata(fixedcodedata); setcodedata(fixedcodedata);
setExpansionModalOpen(false)
} else if (changeActionParameterCodeMirror !== undefined) { } else if (changeActionParameterCodeMirror !== undefined) {
//changeActionParameterCodeMirror(event, fieldCount, fixedcodedata) //changeActionParameterCodeMirror(event, fieldCount, fixedcodedata)
changeActionParameterCodeMirror(event, fieldCount, fixedcodedata, actionlist, parameterName, selectedAction, setSelectedAction) changeActionParameterCodeMirror(event, fieldCount, fixedcodedata, actionlist, parameterName, selectedAction, setSelectedAction)
setExpansionModalOpen(false)
setcodedata(fixedcodedata) setcodedata(fixedcodedata)
} }
// console.log("fieldname", fieldname) // Handle condition fields
// // Check if fieldname is set, and try to find and inject the text if (conditionField !== null && handleConditionFieldChange !== undefined) {
// if (fieldname !== undefined && fieldname !== null && fieldname.length > 0) { handleConditionFieldChange(conditionField, fieldName, fixedcodedata);
// const foundfield = document.getElementById(fieldname) }
// console.log("foundfield", foundfield) // Handle action fields
// if (foundfield !== undefined && foundfield !== null) { else if (actionId !== undefined && actionId !== null && actionId.length > 0) {
// foundfield.value = fixedcodedata
// if(selectedTrigger !== undefined && selectedTrigger !== null && selectedTrigger.name === "Shuffle Workflow") {
// handleSubflowParamChange(fixedcodedata)
// }
// }
// } else {
// //toast("No fieldname found: " + fieldname)
// }
if(actionId !== undefined && actionId !== null && actionId.length > 0) {
handleActionParamChange(actionId, fieldName, fixedcodedata) handleActionParamChange(actionId, fieldName, fixedcodedata)
} }
// Handle trigger fields
if(triggerId !== undefined && triggerId !== null && triggerId.length > 0) { else if (triggerId !== undefined && triggerId !== null && triggerId.length > 0) {
handleSubflowParamChange(triggerId, triggerField, fixedcodedata) handleSubflowParamChange(triggerId, triggerField, fixedcodedata)
} }
+1 -1
View File
@@ -14,7 +14,7 @@ const theme = createTheme(adaptV4Theme({
contrastText: "#000000", contrastText: "#000000",
}, },
text: { text: {
secondary: "rgba(255,255,255,0.7)", secondary: "rgba(255,255,255,0.8)",
}, },
type: "dark", type: "dark",
//inputColor: "#383B40", //inputColor: "#383B40",
File diff suppressed because it is too large Load Diff
-1
View File
@@ -59,7 +59,6 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
(userdata?.admin === "true" && userdata?.active_org?.id === data?.reference_org) || (userdata?.admin === "true" && userdata?.active_org?.id === data?.reference_org) ||
data?.contributors?.includes(userdata?.id) data?.contributors?.includes(userdata?.id)
console.log("appdata", data)
const paperStyle = { const paperStyle = {
backgroundColor: mouseHoverIndex === index ? "rgba(26, 26, 26, 1)" : "#212121", backgroundColor: mouseHoverIndex === index ? "rgba(26, 26, 26, 1)" : "#212121",
color: "rgba(241, 241, 241, 1)", color: "rgba(241, 241, 241, 1)",
+8 -8
View File
@@ -184,15 +184,15 @@ export const CodeHandler = (props) => {
if (validate.valid === false) { if (validate.valid === false) {
// Check if https://shuffler.io in the url // Check if https://shuffler.io in the url
// if so, then we change it for the current url // if so, then we change it for the current url
if (propvalue.includes("https://shuffler.io")) { if (propvalue.includes("https://shuffler.io/api")) {
newprop = propvalue.replace("https://shuffler.io", window.location.origin) newprop = propvalue.replace("https://shuffler.io/api", window.location.origin+"/api")
}
// Check if it contains Bearer APIKEY
// If so, replace apikey const foundurl = localStorage.getItem("globalUrl")
//if (newprop.includes("Bearer APIKEY")) { if (foundurl !== undefined && foundurl !== null && foundurl !== "") {
// newprop = newprop.replace("Bearer APIKEY", "Bearer API newprop = propvalue.replace("https://shuffler.io/api", foundurl+"/api")
//} }
}
} }
// Need to check if it's singletick or multi // Need to check if it's singletick or multi
+26 -25
View File
@@ -1187,32 +1187,32 @@ const Workflows2 = (props) => {
"", "",
data.status, data.status,
) )
.then((response) => { .then((response) => {
if (response !== undefined) { if (response !== undefined) {
// SET THE FULL THING // SET THE FULL THING
data.id = response.id; data.id = response.id;
// Actually create it // Actually create it
setNewWorkflow( setNewWorkflow(
data.name, data.name,
data.description, data.description,
data.tags, data.tags,
data.default_return_value, data.default_return_value,
data, data,
false, false,
[], [],
"", "",
data.status data.status
).then((response) => { ).then((response) => {
if (response !== undefined) { if (response !== undefined) {
toast(`Successfully imported ${data.name}`); toast(`Successfully imported ${data.name}`);
} }
}); });
} }
}) })
.catch((error) => { .catch((error) => {
toast("Import error: " + error.toString()); toast("Import error: " + error.toString());
}); });
}); });
} catch (e) { } catch (e) {
console.log("Error in dropzone: ", e); console.log("Error in dropzone: ", e);
@@ -2875,6 +2875,7 @@ const Workflows2 = (props) => {
if (editingWorkflow.id !== undefined) { if (editingWorkflow.id !== undefined) {
console.log("Building original workflow"); console.log("Building original workflow");
method = "PUT"; method = "PUT";
//extraData = "/" + editingWorkflow.id + "?skip_save=true";
extraData = "/" + editingWorkflow.id + "?skip_save=true"; extraData = "/" + editingWorkflow.id + "?skip_save=true";
workflowdata = editingWorkflow; workflowdata = editingWorkflow;