Fixed a bunch of minor issues with notification workflows and the like
This commit is contained in:
@@ -22,8 +22,7 @@ import dateutil
|
|||||||
import threading
|
import threading
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
|
|
||||||
from io import StringIO as StringBuffer
|
from io import StringIO as StringBuffer, BytesIO
|
||||||
from io import BytesIO
|
|
||||||
from liquid import Liquid, defaults
|
from liquid import Liquid, defaults
|
||||||
|
|
||||||
runtime = os.getenv("SHUFFLE_SWARM_CONFIG", "")
|
runtime = os.getenv("SHUFFLE_SWARM_CONFIG", "")
|
||||||
@@ -1296,7 +1295,7 @@ class AppBase:
|
|||||||
|
|
||||||
def delete_cache(self, key):
|
def delete_cache(self, key):
|
||||||
org_id = self.full_execution["workflow"]["execution_org"]["id"]
|
org_id = self.full_execution["workflow"]["execution_org"]["id"]
|
||||||
url = "%s/api/v1/orgs/%s/delete_cache" % (self.url, org_id, key)
|
url = "%s/api/v1/orgs/%s/delete_cache" % (self.url, org_id)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"workflow_id": self.full_execution["workflow"]["id"],
|
"workflow_id": self.full_execution["workflow"]["id"],
|
||||||
@@ -1309,11 +1308,11 @@ class AppBase:
|
|||||||
response = requests.post(url, json=data, verify=False)
|
response = requests.post(url, json=data, verify=False)
|
||||||
try:
|
try:
|
||||||
allvalues = response.json()
|
allvalues = response.json()
|
||||||
return allvalues
|
return json.dumps(allvalues)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.info("[ERROR} Failed to parse response from delete_cache: %s" % e)
|
self.logger.info("[ERROR} Failed to parse response from delete_cache: %s" % e)
|
||||||
#return response.json()
|
#return response.json()
|
||||||
return {"success": False, "reason": f"Failed to delete cache for key {key}"}
|
return json.dumps({"success": False, "reason": f"Failed to delete cache for key '{key}'"})
|
||||||
|
|
||||||
def set_cache(self, key, value):
|
def set_cache(self, key, value):
|
||||||
org_id = self.full_execution["workflow"]["execution_org"]["id"]
|
org_id = self.full_execution["workflow"]["execution_org"]["id"]
|
||||||
@@ -2301,13 +2300,13 @@ class AppBase:
|
|||||||
#if len(template) > 100:
|
#if len(template) > 100:
|
||||||
# self.logger.info("[DEBUG] Running liquid with data of length %d" % len(template))
|
# self.logger.info("[DEBUG] Running liquid with data of length %d" % len(template))
|
||||||
#self.logger.info(f"[DEBUG] Data: {template}")
|
#self.logger.info(f"[DEBUG] Data: {template}")
|
||||||
run = Liquid(template, mode="wild", from_file=False, filters=shuffle_filters.filters)
|
|
||||||
|
|
||||||
# Can't handle self yet (?)
|
|
||||||
all_globals = globals()
|
all_globals = globals()
|
||||||
all_globals["self"] = self
|
all_globals["self"] = self
|
||||||
|
run = Liquid(template, mode="wild", from_file=False, filters=shuffle_filters.filters, globals=all_globals)
|
||||||
|
|
||||||
ret = run.render(**all_globals)
|
# Add locals that are missing to globals
|
||||||
|
ret = run.render()
|
||||||
return ret
|
return ret
|
||||||
except jinja2.exceptions.TemplateNotFound as e:
|
except jinja2.exceptions.TemplateNotFound as e:
|
||||||
self.logger.info(f"[ERROR] Liquid Template error: {e}")
|
self.logger.info(f"[ERROR] Liquid Template error: {e}")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
urllib3==1.26.5
|
urllib3==1.26.5
|
||||||
requests==2.25.1
|
requests==2.25.1
|
||||||
MarkupSafe==2.0.1
|
MarkupSafe==2.0.1
|
||||||
liquidpy==0.7.6
|
liquidpy==0.8.1
|
||||||
flask[async]==2.0.2
|
flask[async]==2.0.2
|
||||||
waitress==2.1.0
|
waitress==2.1.0
|
||||||
#flask==1.1.2
|
#flask==1.1.2
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ import AlertTemplate from "./components/AlertTemplate";
|
|||||||
import { useAlert, positions, Provider } from "react-alert";
|
import { useAlert, positions, Provider } from "react-alert";
|
||||||
import { isMobile } from "react-device-detect";
|
import { isMobile } from "react-device-detect";
|
||||||
|
|
||||||
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
|
|
||||||
import Drift from "react-driftjs";
|
import Drift from "react-driftjs";
|
||||||
|
|
||||||
// Production - backend proxy forwarding in nginx
|
// Production - backend proxy forwarding in nginx
|
||||||
@@ -561,6 +564,18 @@ const App = (message, props) => {
|
|||||||
{includedData}
|
{includedData}
|
||||||
</Provider>
|
</Provider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
<ToastContainer
|
||||||
|
position="bottom-center"
|
||||||
|
autoClose={5000}
|
||||||
|
hideProgressBar={false}
|
||||||
|
newestOnTop={false}
|
||||||
|
closeOnClick
|
||||||
|
rtl={false}
|
||||||
|
pauseOnFocusLoss
|
||||||
|
draggable
|
||||||
|
pauseOnHover
|
||||||
|
theme="dark"
|
||||||
|
/>
|
||||||
</CookiesProvider>
|
</CookiesProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ const OrgHeader = (props) => {
|
|||||||
handleEditOrg,
|
handleEditOrg,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
//const alert = useAlert();
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
var upload = "";
|
var upload = "";
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import { useAlert } from "react-alert";
|
|
||||||
import theme from '../theme.jsx';
|
import theme from '../theme.jsx';
|
||||||
|
import { toast } from "react-toastify"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
Tab,
|
Tab,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Autocomplete,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -48,7 +49,6 @@ const OrgHeaderexpanded = (props) => {
|
|||||||
adminTab,
|
adminTab,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const alert = useAlert();
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const defaultBranch = "master";
|
const defaultBranch = "master";
|
||||||
|
|
||||||
@@ -155,6 +155,47 @@ const OrgHeaderexpanded = (props) => {
|
|||||||
: selectedOrganization.sso_config.openid_token
|
: selectedOrganization.sso_config.openid_token
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [workflows, setWorkflows] = React.useState([])
|
||||||
|
const [workflow, setWorkflow] = React.useState({})
|
||||||
|
|
||||||
|
const getAvailableWorkflows = (trigger_index) => {
|
||||||
|
fetch(globalUrl + "/api/v1/workflows", {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Status not 200 for workflows :O!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson !== undefined) {
|
||||||
|
setWorkflows(responseJson)
|
||||||
|
|
||||||
|
if (selectedOrganization.defaults !== undefined && selectedOrganization.defaults.notification_workflow !== undefined) {
|
||||||
|
|
||||||
|
const workflow = responseJson.find((workflow) => workflow.id === selectedOrganization.defaults.notification_workflow)
|
||||||
|
if (workflow !== undefined && workflow !== null) {
|
||||||
|
setWorkflow(workflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error getting workflows: " + error);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getAvailableWorkflows()
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleEditOrg = (
|
const handleEditOrg = (
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
@@ -188,17 +229,29 @@ const OrgHeaderexpanded = (props) => {
|
|||||||
.then((response) =>
|
.then((response) =>
|
||||||
response.json().then((responseJson) => {
|
response.json().then((responseJson) => {
|
||||||
if (responseJson["success"] === false) {
|
if (responseJson["success"] === false) {
|
||||||
alert.error("Failed updating org: ", responseJson.reason);
|
toast("Failed updating org: ", responseJson.reason);
|
||||||
} else {
|
} else {
|
||||||
alert.success("Successfully edited org!");
|
toast("Successfully edited org!");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
alert.error("Err: " + error.toString());
|
toast("Err: " + error.toString());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleWorkflowSelectionUpdate = (e, isUserinput) => {
|
||||||
|
if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) {
|
||||||
|
console.log("Returning as there's no id")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorkflow(e.target.value)
|
||||||
|
setNotificationWorkflow(e.target.value.id)
|
||||||
|
toast("Updated notification workflow. Don't forget to save!")
|
||||||
|
}
|
||||||
|
|
||||||
const orgSaveButton = (
|
const orgSaveButton = (
|
||||||
<Tooltip title="Save any unsaved data" placement="bottom">
|
<Tooltip title="Save any unsaved data" placement="bottom">
|
||||||
<div>
|
<div>
|
||||||
@@ -247,34 +300,149 @@ const OrgHeaderexpanded = (props) => {
|
|||||||
<Grid container spacing={3} style={{ textAlign: "left" }}>
|
<Grid container spacing={3} style={{ textAlign: "left" }}>
|
||||||
<Grid item xs={12} style={{}}>
|
<Grid item xs={12} style={{}}>
|
||||||
<span>
|
<span>
|
||||||
<Typography>Notification Workflow ID</Typography>
|
<Typography>Notification Workflow</Typography>
|
||||||
<TextField
|
{/*
|
||||||
required
|
<Typography variant="body2" color="textSecondary">
|
||||||
style={{
|
Add a Workflow that receives notifications from Shuffle when an error occurs in one of your workflows
|
||||||
flex: "1",
|
</Typography>
|
||||||
marginTop: "5px",
|
*/}
|
||||||
marginRight: "15px",
|
<div style={{display: "flex", flexDirection: "row", alignItems: "center"}}>
|
||||||
backgroundColor: theme.palette.inputColor,
|
{workflows !== undefined && workflows !== null && workflows.length > 0 ?
|
||||||
}}
|
<Autocomplete
|
||||||
fullWidth={true}
|
id="notification_workflow_search"
|
||||||
type="name"
|
autoHighlight
|
||||||
id="outlined-with-placeholder"
|
freeSolo
|
||||||
margin="normal"
|
//autoSelect
|
||||||
variant="outlined"
|
value={workflow}
|
||||||
placeholder="ID of the workflow to receive notifications"
|
classes={{ inputRoot: classes.inputRoot }}
|
||||||
value={notificationWorkflow}
|
ListboxProps={{
|
||||||
onChange={(e) => {
|
style: {
|
||||||
setNotificationWorkflow(e.target.value);
|
backgroundColor: theme.palette.inputColor,
|
||||||
}}
|
color: "white",
|
||||||
InputProps={{
|
},
|
||||||
classes: {
|
}}
|
||||||
notchedOutline: classes.notchedOutline,
|
getOptionLabel={(option) => {
|
||||||
},
|
if (
|
||||||
style: {
|
option === undefined ||
|
||||||
color: "white",
|
option === null ||
|
||||||
},
|
option.name === undefined ||
|
||||||
}}
|
option.name === null
|
||||||
/>
|
) {
|
||||||
|
return "No Workflow Selected";
|
||||||
|
}
|
||||||
|
|
||||||
|
const newname = (
|
||||||
|
option.name.charAt(0).toUpperCase() + option.name.substring(1)
|
||||||
|
).replaceAll("_", " ");
|
||||||
|
return newname;
|
||||||
|
}}
|
||||||
|
options={workflows}
|
||||||
|
fullWidth
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
height: 50,
|
||||||
|
borderRadius: theme.palette.borderRadius,
|
||||||
|
}}
|
||||||
|
onChange={(event, newValue) => {
|
||||||
|
console.log("Found value: ", newValue)
|
||||||
|
|
||||||
|
var parsedinput = { target: { value: newValue } }
|
||||||
|
|
||||||
|
// For variables
|
||||||
|
if (typeof newValue === 'string' && newValue.startsWith("$")) {
|
||||||
|
parsedinput = {
|
||||||
|
target: {
|
||||||
|
value: {
|
||||||
|
"name": newValue,
|
||||||
|
"id": newValue,
|
||||||
|
"actions": [],
|
||||||
|
"triggers": [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWorkflowSelectionUpdate(parsedinput)
|
||||||
|
}}
|
||||||
|
renderOption={(props, data, state) => {
|
||||||
|
if (data.id === workflow.id) {
|
||||||
|
data = workflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip arrow placement="left" title={
|
||||||
|
<span style={{}}>
|
||||||
|
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
|
||||||
|
<img src={data.image} alt={data.name} style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }} />
|
||||||
|
: null}
|
||||||
|
<Typography>
|
||||||
|
Choose {data.name}
|
||||||
|
</Typography>
|
||||||
|
</span>
|
||||||
|
} placement="bottom">
|
||||||
|
<MenuItem
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
color: data.id === workflow.id ? "red" : "white",
|
||||||
|
}}
|
||||||
|
value={data}
|
||||||
|
onClick={(e) => {
|
||||||
|
var parsedinput = { target: { value: data } }
|
||||||
|
handleWorkflowSelectionUpdate(parsedinput)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.name}
|
||||||
|
</MenuItem>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
renderInput={(params) => {
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
borderRadius: theme.palette.borderRadius,
|
||||||
|
}}
|
||||||
|
{...params}
|
||||||
|
label="Find a notification workflow"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
style={{
|
||||||
|
flex: "1",
|
||||||
|
marginTop: "5px",
|
||||||
|
marginRight: "15px",
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
}}
|
||||||
|
fullWidth={true}
|
||||||
|
type="name"
|
||||||
|
id="outlined-with-placeholder"
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
placeholder="ID of the workflow to receive notifications"
|
||||||
|
value={notificationWorkflow}
|
||||||
|
onChange={(e) => {
|
||||||
|
setNotificationWorkflow(e.target.value);
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
classes: {
|
||||||
|
notchedOutline: classes.notchedOutline,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<div style={{minWidth: 150, maxWidth: 150, marginTop: 5, marginLeft: 10, }}>
|
||||||
|
{orgSaveButton}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} style={{}}>
|
<Grid item xs={12} style={{}}>
|
||||||
|
|||||||
@@ -1131,7 +1131,7 @@ const ParsedAction = (props) => {
|
|||||||
borderRadius: theme.palette.borderRadius,
|
borderRadius: theme.palette.borderRadius,
|
||||||
}}
|
}}
|
||||||
onChange={(event, newValue) => {
|
onChange={(event, newValue) => {
|
||||||
console.log("SELECT: ", event, newValue)
|
console.log("SELECT: ", event, newValue)
|
||||||
// Workaround with event lol
|
// Workaround with event lol
|
||||||
//if (newValue !== undefined && newValue !== null) {
|
//if (newValue !== undefined && newValue !== null) {
|
||||||
// setNewSelectedAction({ target: { value: newValue.name } });
|
// setNewSelectedAction({ target: { value: newValue.name } });
|
||||||
|
|||||||
@@ -2718,7 +2718,7 @@ const Apps = (props) => {
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent style={{ color: "rgba(255,255,255,0.65)" }}>
|
<DialogContent style={{ color: "rgba(255,255,255,0.65)" }}>
|
||||||
<Typography variant="body1">
|
<Typography variant="body1">
|
||||||
Paste in a URL, and we will make it into an app for you. This may take multiple minutes based on the size of the documentation. {isCloud ? "" : "Uses to Shuffle Cloud (https://shuffler.io) for processing."}
|
Paste in a URL, and we will make it into an app for you. This may take multiple minutes based on the size of the documentation. <b>{isCloud ? "" : "Uses Shuffle Cloud (https://shuffler.io) for processing (for now)."}</b>
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
style={{ backgroundColor: inputColor }}
|
style={{ backgroundColor: inputColor }}
|
||||||
|
|||||||
Reference in New Issue
Block a user