A bunch of commits moved from cloud -> onprem to sync up current work on 2.0
This commit is contained in:
+108
-107
@@ -1713,6 +1713,112 @@ class AppBase:
|
||||
ret = requests.post("%s%s" % (self.url, upload_path), files=files, headers=new_headers, verify=False, proxies=self.proxy_config)
|
||||
|
||||
return file_ids
|
||||
|
||||
def validate_condition(self, sourcevalue, check, destinationvalue):
|
||||
if check == "=" or check == "==" or check.lower() == "equals":
|
||||
if str(sourcevalue).lower() == str(destinationvalue).lower():
|
||||
return True
|
||||
elif check == "!=" or check.lower() == "does not equal":
|
||||
if str(sourcevalue).lower() != str(destinationvalue).lower():
|
||||
return True
|
||||
elif check.lower() == "startswith":
|
||||
if str(sourcevalue).lower().startswith(str(destinationvalue).lower()):
|
||||
return True
|
||||
elif check.lower() == "endswith":
|
||||
if str(sourcevalue).lower().endswith(str(destinationvalue).lower()):
|
||||
return True
|
||||
elif check.lower() == "contains":
|
||||
if destinationvalue.lower() in sourcevalue.lower():
|
||||
return True
|
||||
|
||||
elif check.lower() == "is empty" or check.lower() == "is_empty":
|
||||
try:
|
||||
if len(json.loads(sourcevalue)) == 0:
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.info(f"[ERROR] Failed to check if empty as list: {e}")
|
||||
|
||||
if len(str(sourcevalue)) == 0:
|
||||
return True
|
||||
|
||||
elif check.lower() == "contains_any_of":
|
||||
newvalue = [destinationvalue.lower()]
|
||||
if "," in destinationvalue:
|
||||
newvalue = destinationvalue.split(",")
|
||||
elif ", " in destinationvalue:
|
||||
newvalue = destinationvalue.split(", ")
|
||||
|
||||
for item in newvalue:
|
||||
if not item:
|
||||
continue
|
||||
|
||||
if item.strip() in sourcevalue:
|
||||
return True
|
||||
|
||||
|
||||
# FIXME: This will be buggy if using > and >= operators in the future.
|
||||
elif check.lower() == "larger than" or check.lower() == "bigger than" or check == ">" or check == ">=":
|
||||
try:
|
||||
if str(sourcevalue).isdigit() and str(destinationvalue).isdigit():
|
||||
if int(sourcevalue) > int(destinationvalue):
|
||||
return True
|
||||
|
||||
except AttributeError as e:
|
||||
self.logger.info("[WARNING] Condition larger than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e))
|
||||
|
||||
try:
|
||||
destinationvalue = len(json.loads(destinationvalue))
|
||||
except Exception as e:
|
||||
self.logger.info(f"[WARNING] Failed to convert destination to list: {e}")
|
||||
try:
|
||||
# Check if it's a list in autocast and if so, check the length
|
||||
if len(json.loads(sourcevalue)) > int(destinationvalue):
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.info(f"[WARNING] Failed to check if larger than as list: {e}")
|
||||
|
||||
|
||||
# FIXME: This will be buggy if using < and <= operators in the future.
|
||||
elif check.lower() == "smaller than" or check.lower() == "less than" or check == "<" or check == "<=":
|
||||
self.logger.info("In smaller than check: %s %s" % (sourcevalue, destinationvalue))
|
||||
|
||||
try:
|
||||
if str(sourcevalue).isdigit() and str(destinationvalue).isdigit():
|
||||
if int(sourcevalue) < int(destinationvalue):
|
||||
return True
|
||||
|
||||
except AttributeError as e:
|
||||
pass
|
||||
|
||||
try:
|
||||
destinationvalue = len(json.loads(destinationvalue))
|
||||
except Exception as e:
|
||||
self.logger.info(f"[WARNING] Failed to convert destination to list: {e}")
|
||||
|
||||
try:
|
||||
# Check if it's a list in autocast and if so, check the length
|
||||
if len(json.loads(sourcevalue)) < int(destinationvalue):
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.info(f"[WARNING] Failed to check if smaller than as list: {e}")
|
||||
|
||||
elif check.lower() == "re" or check.lower() == "matches regex":
|
||||
try:
|
||||
found = re.search(str(destinationvalue), str(sourcevalue))
|
||||
except re.error as e:
|
||||
return False
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
if found == None:
|
||||
return False
|
||||
|
||||
return True
|
||||
else:
|
||||
self.logger.error("[DEBUG] Condition: can't handle %s yet. Setting to true" % check)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
#async def execute_action(self, action):
|
||||
def execute_action(self, action):
|
||||
@@ -2596,7 +2702,6 @@ class AppBase:
|
||||
return returndata, is_loop
|
||||
|
||||
|
||||
|
||||
# Sending self as it's not a normal function
|
||||
def parse_liquid(template, self):
|
||||
|
||||
@@ -3083,110 +3188,7 @@ class AppBase:
|
||||
|
||||
return "", parameter["value"], is_loop
|
||||
|
||||
def run_validation(sourcevalue, check, destinationvalue):
|
||||
#self.logger.info("[DEBUG] Checking %s '%s' %s" % (sourcevalue, check, destinationvalue))
|
||||
|
||||
if check == "=" or check.lower() == "equals":
|
||||
if str(sourcevalue).lower() == str(destinationvalue).lower():
|
||||
return True
|
||||
elif check == "!=" or check.lower() == "does not equal":
|
||||
if str(sourcevalue).lower() != str(destinationvalue).lower():
|
||||
return True
|
||||
elif check.lower() == "startswith":
|
||||
if str(sourcevalue).lower().startswith(str(destinationvalue).lower()):
|
||||
return True
|
||||
elif check.lower() == "endswith":
|
||||
if str(sourcevalue).lower().endswith(str(destinationvalue).lower()):
|
||||
return True
|
||||
elif check.lower() == "contains":
|
||||
if destinationvalue.lower() in sourcevalue.lower():
|
||||
return True
|
||||
|
||||
elif check.lower() == "is empty" or check.lower() == "is_empty":
|
||||
try:
|
||||
if len(json.loads(sourcevalue)) == 0:
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.info(f"[ERROR] Failed to check if empty as list: {e}")
|
||||
|
||||
if len(str(sourcevalue)) == 0:
|
||||
return True
|
||||
|
||||
elif check.lower() == "contains_any_of":
|
||||
newvalue = [destinationvalue.lower()]
|
||||
if "," in destinationvalue:
|
||||
newvalue = destinationvalue.split(",")
|
||||
elif ", " in destinationvalue:
|
||||
newvalue = destinationvalue.split(", ")
|
||||
|
||||
for item in newvalue:
|
||||
if not item:
|
||||
continue
|
||||
|
||||
if item.strip() in sourcevalue:
|
||||
return True
|
||||
|
||||
elif check.lower() == "larger than" or check.lower() == "bigger than":
|
||||
try:
|
||||
if str(sourcevalue).isdigit() and str(destinationvalue).isdigit():
|
||||
if int(sourcevalue) > int(destinationvalue):
|
||||
return True
|
||||
|
||||
except AttributeError as e:
|
||||
self.logger.info("[WARNING] Condition larger than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e))
|
||||
|
||||
try:
|
||||
destinationvalue = len(json.loads(destinationvalue))
|
||||
except Exception as e:
|
||||
self.logger.info(f"[WARNING] Failed to convert destination to list: {e}")
|
||||
try:
|
||||
# Check if it's a list in autocast and if so, check the length
|
||||
if len(json.loads(sourcevalue)) > int(destinationvalue):
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.info(f"[WARNING] Failed to check if larger than as list: {e}")
|
||||
|
||||
|
||||
elif check.lower() == "smaller than" or check.lower() == "less than":
|
||||
self.logger.info("In smaller than check: %s %s" % (sourcevalue, destinationvalue))
|
||||
|
||||
try:
|
||||
if str(sourcevalue).isdigit() and str(destinationvalue).isdigit():
|
||||
if int(sourcevalue) < int(destinationvalue):
|
||||
return True
|
||||
|
||||
except AttributeError as e:
|
||||
pass
|
||||
|
||||
try:
|
||||
destinationvalue = len(json.loads(destinationvalue))
|
||||
except Exception as e:
|
||||
self.logger.info(f"[WARNING] Failed to convert destination to list: {e}")
|
||||
|
||||
try:
|
||||
# Check if it's a list in autocast and if so, check the length
|
||||
if len(json.loads(sourcevalue)) < int(destinationvalue):
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.info(f"[WARNING] Failed to check if smaller than as list: {e}")
|
||||
|
||||
elif check.lower() == "re" or check.lower() == "matches regex":
|
||||
try:
|
||||
found = re.search(str(destinationvalue), str(sourcevalue))
|
||||
except re.error as e:
|
||||
return False
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
if found == None:
|
||||
return False
|
||||
|
||||
return True
|
||||
else:
|
||||
self.logger.error("[DEBUG] Condition: can't handle %s yet. Setting to true" % check)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def check_branch_conditions(action, fullexecution, self):
|
||||
# relevantbranches = workflow.branches where destination = action
|
||||
try:
|
||||
@@ -3286,8 +3288,7 @@ class AppBase:
|
||||
self.logger.error("[ERROR] Skipping '%s' -> %s -> '%s' because %s is invalid." % (sourcevalue, condition["condition"]["value"], destinationvalue, condition["condition"]["value"]))
|
||||
continue
|
||||
|
||||
# Configuration = negated because of WorkflowAppActionParam..
|
||||
validation = run_validation(sourcevalue, condition["condition"]["value"], destinationvalue)
|
||||
validation = self.validate_condition(sourcevalue, condition["condition"]["value"], destinationvalue)
|
||||
try:
|
||||
if condition["condition"]["configuration"]:
|
||||
validation = not validation
|
||||
|
||||
@@ -571,14 +571,20 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
|
||||
//return
|
||||
}
|
||||
|
||||
if len(actionResult.ExecutionId) == 0 {
|
||||
resp.WriteHeader(400)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Provide execution_id and authorization"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId)
|
||||
if err != nil {
|
||||
if err != nil || workflowExecution.ExecutionId != actionResult.ExecutionId {
|
||||
if len(actionResult.ExecutionId) > 0 {
|
||||
log.Printf("[WARNING][%s] Failed getting execution (streamresult): %s", actionResult.ExecutionId, err)
|
||||
}
|
||||
|
||||
resp.WriteHeader(401)
|
||||
resp.WriteHeader(400)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`)))
|
||||
return
|
||||
}
|
||||
@@ -637,9 +643,26 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if workflowExecution.Workflow.Sharing == "form" {
|
||||
newWorkflow := shuffle.Workflow{
|
||||
Name: workflowExecution.Workflow.Name,
|
||||
ID: workflowExecution.Workflow.ID,
|
||||
Owner: workflowExecution.Workflow.Owner,
|
||||
OrgId: workflowExecution.Workflow.OrgId,
|
||||
|
||||
Sharing: workflowExecution.Workflow.Sharing,
|
||||
Description: workflowExecution.Workflow.Description,
|
||||
InputQuestions: workflowExecution.Workflow.InputQuestions,
|
||||
InputMarkdown: workflowExecution.Workflow.InputMarkdown,
|
||||
}
|
||||
|
||||
workflowExecution.Results = []shuffle.ActionResult{}
|
||||
workflowExecution.Workflow = newWorkflow
|
||||
}
|
||||
|
||||
newjson, err := json.Marshal(workflowExecution)
|
||||
if err != nil {
|
||||
resp.WriteHeader(401)
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow execution"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1129,7 +1129,7 @@ const AppFramework = (props) => {
|
||||
}, [newSelectedApp])
|
||||
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
const imgSize = 50;
|
||||
var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ const AppSelection = props => {
|
||||
document.title = "Choose your apps"
|
||||
const ref = useRef()
|
||||
let navigate = useNavigate();
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
|
||||
useEffect(() => {
|
||||
if (newSelectedApp === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) {
|
||||
|
||||
@@ -24,7 +24,7 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e52
|
||||
const Appsearch = props => {
|
||||
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp } = props
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
|
||||
const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs
|
||||
//const theme = useTheme();
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
Delete,
|
||||
RestaurantRounded,
|
||||
Cloud,
|
||||
CheckCircle
|
||||
} from "@mui/icons-material";
|
||||
|
||||
//import { useAlert
|
||||
@@ -71,6 +72,7 @@ const Billing = (props) => {
|
||||
const [currentAppRunsInNumber, setCurrentAppRunsInNumber] = useState(0);
|
||||
const [alertThresholds, setAlertThresholds] = useState(selectedOrganization.Billing !== undefined && selectedOrganization.Billing.AlertThreshold !== undefined && selectedOrganization.Billing.AlertThreshold !== null ? selectedOrganization.Billing.AlertThreshold : [{ percentage: '', count: '', Email_send: false }]);
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [deleteAlertVerification, setDeleteAlertVerification] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (userdata.app_execution_limit !== undefined && userdata.app_execution_usage !== undefined) {
|
||||
@@ -353,13 +355,13 @@ const Billing = (props) => {
|
||||
if (subscription.name === "Enterprise" && subscription.active === true) {
|
||||
top_text = "Current Plan"
|
||||
|
||||
newPaperstyle.border = "1px solid #f85a3e"
|
||||
// newPaperstyle.border = "1px solid #f85a3e"
|
||||
}
|
||||
|
||||
var showSupport = false
|
||||
if (subscription.name.includes("default")) {
|
||||
top_text = "Custom Contract"
|
||||
newPaperstyle.border = "1px solid #f85a3e"
|
||||
newPaperstyle.border = "1px solid rgba(255,255,255,0.3)"
|
||||
showSupport = true
|
||||
}
|
||||
|
||||
@@ -379,7 +381,7 @@ const Billing = (props) => {
|
||||
|
||||
if (highlight === true) {
|
||||
// Add an "Upgrade now" button
|
||||
newPaperstyle.border = "1px solid #f85a3e"
|
||||
newPaperstyle.border = "1px solid rgba(255,255,255,0.3)"
|
||||
}
|
||||
|
||||
if (hovered) {
|
||||
@@ -820,7 +822,8 @@ const Billing = (props) => {
|
||||
height: 40,
|
||||
fontSize: 16,
|
||||
color: "white",
|
||||
backgroundImage: userdata.has_card_available ? null : "linear-gradient(to right, #f86a3e, #f34079)",
|
||||
backgroundColor: userdata.has_card_available ? null : "#f86743",
|
||||
// backgroundImage: userdata.has_card_available ? null : "linear-gradient(to right, #f86a3e, #f34079)",
|
||||
textTransform: "none",
|
||||
|
||||
}}
|
||||
@@ -852,7 +855,7 @@ const Billing = (props) => {
|
||||
height: 40,
|
||||
fontSize: 16,
|
||||
color: "white",
|
||||
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)",
|
||||
backgroundColor: "#f86743",
|
||||
textTransform: 'none'
|
||||
}}
|
||||
onClick={() => {
|
||||
@@ -1036,11 +1039,11 @@ const Billing = (props) => {
|
||||
<Paper style={{
|
||||
padding: 20,
|
||||
// maxWidth: 400,
|
||||
minWidth: 340,
|
||||
width: 340,
|
||||
height: 480,
|
||||
backgroundColor: hovered ? "#232427" : theme.palette.platformColor,
|
||||
borderRadius: theme.palette.borderRadius * 2,
|
||||
border: "1px solid #f85a3e",
|
||||
border: "1px solid rgba(255,255,255,0.3)",
|
||||
marginRight: 10,
|
||||
marginTop: 15,
|
||||
}}
|
||||
@@ -1133,8 +1136,8 @@ const Billing = (props) => {
|
||||
height: 40,
|
||||
fontSize: 16,
|
||||
color: "white",
|
||||
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)",
|
||||
textTransform: 'none'
|
||||
backgroundColor: "#f86743",
|
||||
textTransform: 'none',
|
||||
}}
|
||||
onClick={() => {
|
||||
if (Cloud) {
|
||||
@@ -1188,7 +1191,7 @@ const Billing = (props) => {
|
||||
height: 40,
|
||||
fontSize: 16,
|
||||
color: "white",
|
||||
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)",
|
||||
backgroundColor: "#f86743",
|
||||
textTransform: 'none',
|
||||
cursor: getProfessionalServices ? 'pointer' : 'not-allowed',
|
||||
opacity: getProfessionalServices ? 1 : 0.6,
|
||||
@@ -1315,7 +1318,7 @@ const Billing = (props) => {
|
||||
width: 340,
|
||||
backgroundColor: hovered ? "#232427" : theme.palette.platformColor,
|
||||
borderRadius: theme.palette.borderRadius * 2,
|
||||
border: "1px solid #f85a3e",
|
||||
border: "1px solid rgba(255,255,255,0.3)",
|
||||
marginRight: 10,
|
||||
marginTop: 15,
|
||||
}}
|
||||
@@ -1373,7 +1376,7 @@ const Billing = (props) => {
|
||||
height: 40,
|
||||
fontSize: 16,
|
||||
color: "white",
|
||||
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)",
|
||||
backgroundColor: "#f86743",
|
||||
textTransform: 'none'
|
||||
}}
|
||||
onClick={() => {
|
||||
@@ -1381,6 +1384,8 @@ const Billing = (props) => {
|
||||
ReactGA.event({
|
||||
category: "Billing",
|
||||
action: "click_public_training_button",
|
||||
label: "Public Training",
|
||||
userId: userdata?.id
|
||||
});
|
||||
}
|
||||
navigate("/training")
|
||||
@@ -1399,21 +1404,24 @@ const Billing = (props) => {
|
||||
height: 40,
|
||||
fontSize: 16,
|
||||
color: "white",
|
||||
backgroundImage: "linear-gradient(to right, #f86a3e, #f34079)",
|
||||
backgroundColor: "#f86743",
|
||||
textTransform: 'none'
|
||||
}}
|
||||
onClick={() => {
|
||||
if (Cloud) {
|
||||
ReactGA.event({
|
||||
category: "Billing",
|
||||
action: "click_public_training_button",
|
||||
action: "click_private_training_button",
|
||||
label: "Private Training",
|
||||
userId: userdata?.id,
|
||||
});
|
||||
}
|
||||
setOpenPrivateTraining(true)
|
||||
setOpenPrivateTraining(true);
|
||||
}}
|
||||
>
|
||||
Private Training
|
||||
</Button>
|
||||
|
||||
<Dialog open={openPrivateTraining}
|
||||
onClose={() => setOpenPrivateTraining(false)}
|
||||
fullWidth
|
||||
@@ -1818,6 +1826,7 @@ const Billing = (props) => {
|
||||
// Update currentIndex based on remaining elements
|
||||
const findCurrentIndex = newAlertThresholds.some(threshold => threshold.Email_send === false);
|
||||
setCurrentIndex(findCurrentIndex ? newAlertThresholds.findIndex(threshold => threshold.Email_send === false) : - 1);
|
||||
toast.info("Alert Threshold deleted successfully. Don't forget to save your changes.");
|
||||
};
|
||||
|
||||
const HandleEditOrgForAlertThreshold = (orgId) => {
|
||||
@@ -1997,15 +2006,6 @@ const Billing = (props) => {
|
||||
/>
|
||||
</span>
|
||||
: null}
|
||||
{isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null :
|
||||
<ConsultationManagement
|
||||
globalUrl={globalUrl}
|
||||
userdata={userdata}
|
||||
selectedOrganization={selectedOrganization}
|
||||
/> : null}
|
||||
|
||||
|
||||
<TrainingService />
|
||||
|
||||
{isCloud &&
|
||||
selectedOrganization.subscriptions !== undefined &&
|
||||
@@ -2261,7 +2261,29 @@ const Billing = (props) => {
|
||||
|
||||
</div>
|
||||
) : null*/}
|
||||
<div style={{ marginTop: 20, marginLeft: 10 }}>
|
||||
{!isChildOrg && isCloud && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', marginTop: 10 }}>
|
||||
<Typography style={{ marginBottom: 5 }} variant="h4">
|
||||
Professional Services
|
||||
</Typography>
|
||||
<Typography color="textSecondary">
|
||||
We offer priority support through consultations and training to help you make the most of our product. If you have any questions, please reach out to us at support@shuffler.io.
|
||||
</Typography>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', marginTop: 5 }}>
|
||||
{billingInfo.subscription !== undefined && billingInfo.subscription !== null ? (
|
||||
isChildOrg ? null : (
|
||||
<ConsultationManagement
|
||||
globalUrl={globalUrl}
|
||||
userdata={userdata}
|
||||
selectedOrganization={selectedOrganization}
|
||||
/>
|
||||
)
|
||||
) : null}
|
||||
<TrainingService />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ marginTop: isCloud && 40, marginLeft: 10 }}>
|
||||
<Typography
|
||||
style={{ marginBottom: 5 }}
|
||||
variant="h4"
|
||||
@@ -2303,7 +2325,9 @@ const Billing = (props) => {
|
||||
: " " + 0 + " "}
|
||||
app runs.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body1" color="textSecondary" style={{ marginTop: 10 }}>
|
||||
Please note: Once your app runs reach the set alert threshold, all admins in the organization will receive an email notification.
|
||||
</Typography>
|
||||
<div style={{ marginTop: 15 }}>
|
||||
{alertThresholds.map((threshold, index) => (
|
||||
<div key={index} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
@@ -2354,6 +2378,7 @@ const Billing = (props) => {
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
/>
|
||||
<span style={{ marginLeft: alertThresholds[index].Email_send === true ? 10 : 35, color: 'green' }}>{alertThresholds[index].Email_send === true && <Tooltip title="We have already sent alert for this threshold."><CheckCircle /></Tooltip>}</span>
|
||||
{
|
||||
alertThresholds.length > 1 &&
|
||||
(
|
||||
@@ -2362,18 +2387,24 @@ const Billing = (props) => {
|
||||
disableElevation
|
||||
sx={{
|
||||
padding: 0,
|
||||
color: 'red',
|
||||
'&:hover': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
}}
|
||||
|
||||
onClick={() => { handleDeleteAlertThreshold(index) }}
|
||||
onClick={() => { setDeleteAlertVerification(true) }}
|
||||
>
|
||||
<DeleteIcon />
|
||||
<DeleteIcon sx={{ color: theme.palette.secondary.main }} />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<Dialog open={deleteAlertVerification} onClose={() => setDeleteAlertVerification(false)} sx={{ '& .MuiBackdrop-root': { backgroundColor: 'rgba(0, 0, 0, 0.3)', }, }}>
|
||||
<DialogTitle>Are you sure you want to delete this threshold?</DialogTitle>
|
||||
<DialogActions>
|
||||
<Button style={{ textTransform: 'none', fontSize: 16 }} color="primary" onClick={() => setDeleteAlertVerification(false)}>Cancel</Button>
|
||||
<Button style={{ textTransform: 'none', fontSize: 16 }} color="secondary" onClick={() => { handleDeleteAlertThreshold(index); setDeleteAlertVerification(false) }} >Delete</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import theme from "../theme.jsx";
|
||||
import { toast } from 'react-toastify';
|
||||
import ReactJson from "react-json-view";
|
||||
import ReactJson from "react-json-view-ssr";
|
||||
|
||||
import {
|
||||
Typography,
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
|
||||
import { useInterval } from "react-powerhooks";
|
||||
import { toast } from 'react-toastify';
|
||||
import theme from "../theme.jsx";
|
||||
import WorkflowValidationTimeline from "../components/WorkflowValidationTimeline.jsx"
|
||||
|
||||
import {
|
||||
InputAdornment,
|
||||
@@ -79,7 +80,7 @@ const ConfigureWorkflow = (props) => {
|
||||
useEffect(() => {
|
||||
if (requiredActions.length === 0) {
|
||||
if (setConfigurationFinished !== undefined) {
|
||||
setConfigurationFinished(true)
|
||||
setConfigurationFinished(true)
|
||||
}
|
||||
}
|
||||
}, [requiredActions])
|
||||
@@ -141,17 +142,18 @@ const ConfigureWorkflow = (props) => {
|
||||
|
||||
// Where is this from?
|
||||
if (workflow === undefined || workflow === null || workflow.id === undefined) {
|
||||
return null;
|
||||
//console.log("Workflow is undefined or null: ", workflow)
|
||||
return null
|
||||
}
|
||||
|
||||
if (apps === undefined || apps === null) {
|
||||
console.log("Apps is undefined or null: ", apps)
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
if (appAuthentication === undefined || appAuthentication === null) {
|
||||
console.log("App authentication is undefined or null: ", appAuthentication)
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
const getApp = (actionId, appId) => {
|
||||
@@ -1386,9 +1388,23 @@ const ConfigureWorkflow = (props) => {
|
||||
: null
|
||||
}
|
||||
|
||||
<div style={{marginTop: 10, }} />
|
||||
|
||||
{/*
|
||||
<WorkflowValidationTimeline
|
||||
workflow={workflow}
|
||||
|
||||
apps={apps}
|
||||
|
||||
getParents={undefined}
|
||||
execution={undefined}
|
||||
/>
|
||||
<div style={{marginBottom: 10, }} />
|
||||
*/}
|
||||
|
||||
{requiredActions.length > 0 ? (
|
||||
<span>
|
||||
<Typography variant="body2" style={{}}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Please configure the following steps to help us complete your workflow. This can also be done later.
|
||||
</Typography>
|
||||
|
||||
|
||||
@@ -1,22 +1,57 @@
|
||||
import React from "react";
|
||||
import React, { useState, useEffect, } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
IconButton,
|
||||
Typography,
|
||||
Switch,
|
||||
Tooltip,
|
||||
Select,
|
||||
MenuItem,
|
||||
Divider,
|
||||
FormLabel,
|
||||
} from "@mui/material";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
|
||||
import DashboardBarchart, { LoadStats } from '../components/DashboardBarchart.jsx';
|
||||
import {
|
||||
Edit as EditIcon,
|
||||
} from "@mui/icons-material";
|
||||
import { toast } from "react-toastify";
|
||||
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
|
||||
import theme from '../theme.jsx';
|
||||
|
||||
const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, isTenzirActive, ...otherProps }) => {
|
||||
|
||||
const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, isTenzirActive, availableDetection, ruleMapping, setRuleMapping, ...otherProps }) => {
|
||||
const [openCodeEditor, setOpenCodeEditor] = React.useState(false);
|
||||
const [fileData, setFileData] = React.useState("");
|
||||
const [isEnabled, setIsEnabled] = React.useState(otherProps.is_enabled);
|
||||
const [filteredBarchart, setFilteredBarchart] = React.useState(null)
|
||||
|
||||
const [responseValue, setResponseValue] = React.useState("No response action")
|
||||
const isCloud = ["localhost:3002", "shuffler.io"].includes(window.location.host);
|
||||
|
||||
console.log("Rulemapping: ", ruleMapping)
|
||||
useEffect(() => {
|
||||
|
||||
//const url = `${globalUrl}/api/v1/stats/app_executions_test2`
|
||||
//const resp = LoadStats(globalUrl, ruleName)
|
||||
//const resp = LoadStats(globalUrl, "app_executions_test2")
|
||||
const resp = LoadStats(globalUrl, "app_executions_cloud")
|
||||
resp.then((data) => {
|
||||
if (data === undefined) {
|
||||
setFilteredBarchart([])
|
||||
} else {
|
||||
setFilteredBarchart(data)
|
||||
}
|
||||
})
|
||||
|
||||
if (ruleMapping !== undefined && ruleMapping !== null && ruleMapping.value !== undefined && ruleMapping.value !== null) {
|
||||
console.log("FIX MAPPING FROM ruleMapping.value: ", ruleMapping)
|
||||
}
|
||||
}, [])
|
||||
|
||||
console.log("Response Value: ", responseValue)
|
||||
|
||||
const handleSwitchChange = (event) => {
|
||||
if (folderDisabled) {
|
||||
toast.warn("Enable the directory to enable individual rules");
|
||||
@@ -32,7 +67,8 @@ const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, i
|
||||
toggleRule(file_id, !newIsEnabled, globalUrl, () => {
|
||||
setIsEnabled(newIsEnabled);
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const UpdateText = (text) => {
|
||||
fetch(`${globalUrl}/api/v1/files/${file_id}/edit`, {
|
||||
@@ -64,32 +100,121 @@ const RuleCard = ({ ruleName, description, file_id, globalUrl, folderDisabled, i
|
||||
<Card style={{
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
minHeight: 100,
|
||||
marginBottom: 10,
|
||||
paddingBottom: 0,
|
||||
}}>
|
||||
<CardContent>
|
||||
<CardContent
|
||||
style={{
|
||||
padding: "10px 30px 0px 30px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
color: "white",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6">{ruleName}</Typography>
|
||||
<Typography variant="h6">{ruleName.replaceAll("_", " ")} ({filteredBarchart === null || filteredBarchart.total === undefined ? 0 : filteredBarchart.total})</Typography>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<IconButton onClick={() => openEditBar(file_id, setOpenCodeEditor, setFileData, globalUrl)}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<Switch
|
||||
checked={isEnabled && !folderDisabled}
|
||||
onChange={handleSwitchChange}
|
||||
disabled={false}
|
||||
/>
|
||||
|
||||
<Select
|
||||
MenuProps={{
|
||||
disableScrollLock: true,
|
||||
}}
|
||||
labelId="Response Action"
|
||||
value={responseValue}
|
||||
SelectDisplayProps={{
|
||||
style: {
|
||||
color: "rgba(255,255,255,0.4)",
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
onChange={(e) => {
|
||||
toast("Changing response: " + e.target.value)
|
||||
console.log("Target: ", e.target.value)
|
||||
|
||||
setResponseValue(e.target.value)
|
||||
|
||||
// FIXME: Handle:
|
||||
// 1. Get the current cache for the detection
|
||||
// 2. Create a new mapping for Detection -> Response
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
height: 40,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
}}
|
||||
value="No response action"
|
||||
>
|
||||
<em>No selected response</em>
|
||||
</MenuItem>
|
||||
|
||||
<Divider />
|
||||
|
||||
{availableDetection === undefined || availableDetection === null ? null : availableDetection.map((data, index) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={index}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
overflowX: "auto",
|
||||
}}
|
||||
value={data.name}
|
||||
>
|
||||
{data.name}
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
|
||||
|
||||
<Tooltip title="Edit Rule" placement="top">
|
||||
<IconButton onClick={() => openEditBar(file_id, setOpenCodeEditor, setFileData, globalUrl)}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={isEnabled && !folderDisabled ? "Disable Rule" : "Enable Rule"} placement="top">
|
||||
<Switch
|
||||
checked={isEnabled && !folderDisabled}
|
||||
onChange={handleSwitchChange}
|
||||
disabled={false}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
overflow: 'visible',
|
||||
zIndex: 10,
|
||||
//border: "1px solid rgba(255,255,255,0.3)",
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
marginTop: 5,
|
||||
|
||||
minHeight: 40,
|
||||
maxHeight: 40,
|
||||
}}>
|
||||
{filteredBarchart === null ? null :
|
||||
<DashboardBarchart
|
||||
timelineData={filteredBarchart}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
{/*
|
||||
<Typography variant="body2" style={{ marginTop: '2%' }}>
|
||||
{description}
|
||||
</Typography>
|
||||
*/}
|
||||
|
||||
<ShuffleCodeEditor
|
||||
isCloud={isCloud}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useContext } from "react";
|
||||
import theme from '../theme.jsx';
|
||||
import { isMobile } from "react-device-detect"
|
||||
import { MuiChipsInput } from "mui-chips-input";
|
||||
import { toast } from "react-toastify"
|
||||
import UsecaseSearch from "../components/UsecaseSearch.jsx"
|
||||
import WorkflowGrid from "../components/WorkflowGrid.jsx"
|
||||
import dayjs from 'dayjs';
|
||||
@@ -63,7 +64,7 @@ import {
|
||||
} from "@mui/icons-material";
|
||||
|
||||
const EditWorkflow = (props) => {
|
||||
const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, saveWorkflow, expanded, } = props
|
||||
const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, saveWorkflow, expanded, scrollTo, } = props
|
||||
|
||||
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
|
||||
|
||||
@@ -79,15 +80,30 @@ const EditWorkflow = (props) => {
|
||||
const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "")
|
||||
const [dueDate, setDueDate] = React.useState(workflow.due_date !== undefined && workflow.due_date !== null && workflow.due_date !== 0 ? dayjs(workflow.due_date*1000) : dayjs().subtract(1, 'day'))
|
||||
|
||||
const [inputQuestions, setInputQuestions] = React.useState(workflow.input_questions !== undefined && workflow.input_questions !== null ? JSON.parse(JSON.stringify(workflow.input_questions)) : [])
|
||||
const [inputQuestions, setInputQuestions] = React.useState(workflow.input_questions !== undefined && workflow.input_questions !== null ? JSON.parse(JSON.stringify(workflow.input_questions)) : [])
|
||||
const [inputMarkdown, setInputMarkdown] = React.useState(workflow.input_markdown !== undefined && workflow.input_markdown !== null ? workflow.input_markdown : "")
|
||||
const [outputMarkdown, setOutputMarkdown] = React.useState(workflow.output_markdown !== undefined && workflow.output_markdown !== null ? workflow.output_markdown : "")
|
||||
const [scrollDone, setScrollDone] = React.useState(false)
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
if (scrollTo !== undefined && scrollTo !== null && scrollTo.length > 0 && scrollDone === false) {
|
||||
setTimeout(() => {
|
||||
const foundScroll = document.getElementById(scrollTo)
|
||||
if (foundScroll !== null) {
|
||||
// Smooth scroll
|
||||
foundScroll.scrollIntoView({ behavior: "smooth" })
|
||||
}
|
||||
|
||||
}, 200)
|
||||
setScrollDone(true)
|
||||
|
||||
}
|
||||
|
||||
// Gets the generated workflow
|
||||
const getGeneratedWorkflow = (workflow_id) => {
|
||||
fetch(globalUrl + "/api/v1/workflows/" + workflow_id, {
|
||||
const getGeneratedWorkflow = (workflow_id) => {
|
||||
const url = `${globalUrl}/api/v1/workflows/${workflow_id}`
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -95,54 +111,55 @@ const EditWorkflow = (props) => {
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 when getting workflow");
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 when getting workflow");
|
||||
}
|
||||
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.id === workflow_id) {
|
||||
console.log("GOT WORKFLOW: ", responseJson)
|
||||
if (name === "") {
|
||||
innerWorkflow.name = responseJson.name
|
||||
setName(responseJson.name)
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.id === workflow_id) {
|
||||
console.log("GOT WORKFLOW: ", responseJson)
|
||||
if (name === "") {
|
||||
innerWorkflow.name = responseJson.name
|
||||
setName(responseJson.name)
|
||||
}
|
||||
|
||||
if (description === "") {
|
||||
innerWorkflow.description = responseJson.description
|
||||
setDescription(description)
|
||||
}
|
||||
|
||||
if (newWorkflowTags === []) {
|
||||
innerWorkflow.tags = responseJson.tags
|
||||
setNewWorkflowTags(responseJson.tags)
|
||||
}
|
||||
|
||||
if (selectedUsecases === []) {
|
||||
selectedUsecases = responseJson.usecase_ids
|
||||
}
|
||||
|
||||
innerWorkflow.id = responseJson.id
|
||||
innerWorkflow.blogpost = responseJson.blogpost
|
||||
innerWorkflow.actions = responseJson.actions
|
||||
innerWorkflow.triggers = responseJson.triggers
|
||||
innerWorkflow.branches = responseJson.branches
|
||||
innerWorkflow.comments = responseJson.comments
|
||||
innerWorkflow.workflow_variables = responseJson.workflow_variables
|
||||
innerWorkflow.execution_variables = responseJson.execution_variables
|
||||
|
||||
|
||||
setInnerWorkflow(innerWorkflow)
|
||||
setUpdate(Math.random())
|
||||
if (description === "") {
|
||||
innerWorkflow.description = responseJson.description
|
||||
setDescription(description)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
//toast(error.toString());
|
||||
console.log("Get workflow error: ", error.toString());
|
||||
})
|
||||
}
|
||||
|
||||
if (newWorkflowTags === []) {
|
||||
innerWorkflow.tags = responseJson.tags
|
||||
setNewWorkflowTags(responseJson.tags)
|
||||
}
|
||||
|
||||
if (selectedUsecases === []) {
|
||||
selectedUsecases = responseJson.usecase_ids
|
||||
}
|
||||
|
||||
innerWorkflow.id = responseJson.id
|
||||
innerWorkflow.blogpost = responseJson.blogpost
|
||||
innerWorkflow.actions = responseJson.actions
|
||||
innerWorkflow.triggers = responseJson.triggers
|
||||
innerWorkflow.branches = responseJson.branches
|
||||
innerWorkflow.comments = responseJson.comments
|
||||
innerWorkflow.workflow_variables = responseJson.workflow_variables
|
||||
innerWorkflow.execution_variables = responseJson.execution_variables
|
||||
|
||||
|
||||
setInnerWorkflow(innerWorkflow)
|
||||
setUpdate(Math.random())
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
//toast(error.toString());
|
||||
console.log("Get workflow error: ", error.toString());
|
||||
})
|
||||
}
|
||||
|
||||
if (foundWorkflowId.length > 0) {
|
||||
getGeneratedWorkflow(foundWorkflowId)
|
||||
@@ -162,6 +179,7 @@ const EditWorkflow = (props) => {
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
anchor={"right"}
|
||||
open={modalOpen}
|
||||
onClose={() => {
|
||||
setModalOpen(false);
|
||||
@@ -191,7 +209,7 @@ const EditWorkflow = (props) => {
|
||||
<Tooltip title="Open Workflow Form for 'normal' users">
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
href={`/workflows/${workflow.id}/run`}
|
||||
href={`/forms/${workflow.id}`}
|
||||
target="_blank"
|
||||
style={{
|
||||
textDecoration: "none",
|
||||
@@ -207,17 +225,18 @@ const EditWorkflow = (props) => {
|
||||
}
|
||||
</div>
|
||||
<Typography variant="body2" color="textSecondary" style={{marginTop: 20, maxWidth: 440,}}>
|
||||
Workflows can be built from scratch, or from templates. <a href="/usecases" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Usecases</a> can help you discover next steps, and you can <a href="/search?tab=workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Learn more</a>
|
||||
Workflows can be built from scratch, or from templates. <a href="/usecases2" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Usecases</a> can help you discover next steps, and you can <a href="/search?tab=workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Learn more</a>
|
||||
</Typography>
|
||||
|
||||
{/*
|
||||
<div style={{marginTop: 10, marginBottom: 10, marginRight: 50, }}>
|
||||
<WorkflowValidationTimeline
|
||||
originalWorkflow={workflow}
|
||||
|
||||
apps={apps}
|
||||
workflow={workflow}
|
||||
/>
|
||||
</div>
|
||||
*/}
|
||||
|
||||
{showUpload === true ?
|
||||
<div style={{ float: "right" }}>
|
||||
@@ -247,7 +266,7 @@ const EditWorkflow = (props) => {
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<FormControl>
|
||||
<div style={{borderTop: "1px solid rgba(255,255,255,0.5)", width: 600, position: "fixed", left: 0, bottom: 0, zIndex: 1002, backgroundColor: "rgba(53,53,53,1)", height: 75, paddingTop: 20, paddingLeft: 75, }}>
|
||||
<div style={{borderTop: "1px solid rgba(255,255,255,0.5)", width: 600, position: "fixed", right: 20, bottom: 0, zIndex: 1002, backgroundColor: "rgba(53,53,53,1)", height: 75, paddingTop: 20, paddingLeft: 75, }}>
|
||||
{/*
|
||||
<Button
|
||||
style={{}}
|
||||
@@ -602,7 +621,7 @@ const EditWorkflow = (props) => {
|
||||
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 === "" ?
|
||||
<Typography variant="body2" style={{marginTop: 10, color: "rgba(255,255,255,0.7)"}}>
|
||||
Your organization does not have any suborgs yet. Please <a href="/admin?tab=suborgs" style={{textDecoration: "none", color: "#f86a3e"}} target="_blank">make one</a>, then try again.
|
||||
Your organization does not have any suborgs yet, OR you may not have access to any suborgs directly.. Please <a href="/admin?tab=suborgs" style={{textDecoration: "none", color: "#f86a3e"}} target="_blank">make one</a> or get access to suborgs by admin, then try again.
|
||||
</Typography>
|
||||
:
|
||||
<Typography variant="body2" style={{marginTop: 10, color: "rgba(255,255,255,0.7)"}}>
|
||||
@@ -702,152 +721,7 @@ const EditWorkflow = (props) => {
|
||||
|
||||
<Divider style={{marginTop: 20, marginBottom: 20, }} />
|
||||
|
||||
<Typography variant="h6" style={{marginTop: 50, }}>
|
||||
Input fields
|
||||
</Typography>
|
||||
<Typography variant="body2" color="textSecondary" style={{marginBottom: 20, }}>
|
||||
Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the <a href={`/workflows/${workflow.id}/run`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>workflow run page</a>. If chosen in the User Input node, these will be required fields.
|
||||
</Typography>
|
||||
|
||||
|
||||
{inputQuestions.map((data, index) => {
|
||||
console.log("Inputfield: ", data)
|
||||
|
||||
return (
|
||||
<div style={{display: "flex", }}>
|
||||
<TextField
|
||||
disabled={data.deleted === true}
|
||||
style={{
|
||||
height: 50,
|
||||
flex: 2,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
marginRight: 5,
|
||||
}}
|
||||
fullWidth={true}
|
||||
placeholder="Question"
|
||||
id="standard-required"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
defaultValue={data.name}
|
||||
onChange={(e) => {
|
||||
inputQuestions[index].name = e.target.value
|
||||
setInputQuestions(inputQuestions)
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
minHeight: 50,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
disabled={data.deleted === true}
|
||||
style={{
|
||||
height: 50,
|
||||
flex: 2,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
marginRight: 5,
|
||||
}}
|
||||
fullWidth={true}
|
||||
placeholder="JSON key"
|
||||
id="standard-required"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
defaultValue={data.value}
|
||||
onChange={(e) => {
|
||||
inputQuestions[index].value = e.target.value
|
||||
setInputQuestions(inputQuestions)
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
minHeight: 50,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
color="primary"
|
||||
style={{ maxWidth: 50, marginLeft: 15 }}
|
||||
disabled={data.deleted === true}
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
// Remove current index
|
||||
console.log("Removing index: ", index)
|
||||
inputQuestions[index].deleted = true
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
>
|
||||
<RemoveIcon style={{}} />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
<Button
|
||||
color="primary"
|
||||
style={{ maxWidth: 50, marginLeft: 15, marginTop: 20, }}
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
inputQuestions.push({
|
||||
"name": "",
|
||||
"value": "",
|
||||
"deleted": false,
|
||||
"required": false
|
||||
})
|
||||
setInputQuestions(inputQuestions)
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
>
|
||||
<AddIcon style={{}} />
|
||||
</Button>
|
||||
|
||||
|
||||
{inputQuestions.length === 0 ? null :
|
||||
<div>
|
||||
<Typography variant="h6" style={{marginTop: 50, }}>
|
||||
Input Markdown
|
||||
</Typography>
|
||||
<TextField
|
||||
multiline
|
||||
rows={3}
|
||||
fullWidth
|
||||
color="primary"
|
||||
value={inputMarkdown}
|
||||
onChange={(e) => {
|
||||
setInputMarkdown(e.target.value)
|
||||
workflow.input_markdown = e.target.value
|
||||
setWorkflow(workflow)
|
||||
setUpdate(Math.random())
|
||||
}}
|
||||
/>
|
||||
|
||||
{/*
|
||||
<Typography variant="h6" style={{marginTop: 50, }}>
|
||||
Output Markdown
|
||||
</Typography>
|
||||
<TextField
|
||||
multiLine
|
||||
rows={3}
|
||||
fullWidth
|
||||
color="primary"
|
||||
/>
|
||||
*/}
|
||||
</div>
|
||||
}
|
||||
|
||||
<Divider style={{marginTop: 20, marginBottom: 20, }} />
|
||||
|
||||
<Typography variant="body1" style={{marginTop: 50, }}>
|
||||
Git Backup Repository
|
||||
@@ -998,8 +872,152 @@ const EditWorkflow = (props) => {
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
<Divider style={{marginTop: 20, marginBottom: 20, }} />
|
||||
|
||||
<Typography variant="h6" style={{marginTop: 50, }}>
|
||||
Input fields
|
||||
</Typography>
|
||||
<Typography variant="body2" color="textSecondary" style={{marginBottom: 20, }}>
|
||||
Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the <a href={`/forms/${workflow.id}`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Form page</a> for this workflow. If chosen in the User Input node, these will be required fields. Use Semi-Colon ";" to create dropdown options. The first key will be the name shown, and subsequent keys will be the available values.
|
||||
</Typography>
|
||||
|
||||
|
||||
{inputQuestions.map((data, index) => {
|
||||
var showListinfo = false
|
||||
if (data.value !== undefined && data.value !== null && data.value.length > 0) {
|
||||
if (data.value.includes(";")) {
|
||||
showListinfo = true
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{display: "flex", }}>
|
||||
<TextField
|
||||
disabled={data.deleted === true}
|
||||
style={{
|
||||
flex: 2,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
marginRight: 5,
|
||||
}}
|
||||
fullWidth={true}
|
||||
placeholder="Question"
|
||||
id="standard-required"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
defaultValue={data.name}
|
||||
onChange={(e) => {
|
||||
inputQuestions[index].name = e.target.value
|
||||
setInputQuestions(inputQuestions)
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
disabled={data.deleted === true}
|
||||
style={{
|
||||
flex: 2,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
marginRight: 5,
|
||||
}}
|
||||
fullWidth={true}
|
||||
placeholder="$exec JSON key"
|
||||
id="standard-required"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
helperText={showListinfo === true ? "Dropdown list" : null}
|
||||
defaultValue={data.value}
|
||||
onChange={(e) => {
|
||||
// Replace multiple semicolon with one
|
||||
e.target.value = e.target.value.replace(";;", ";")
|
||||
|
||||
inputQuestions[index].value = e.target.value
|
||||
setInputQuestions(inputQuestions)
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
color="primary"
|
||||
style={{ maxWidth: 50, marginLeft: 15 }}
|
||||
disabled={data.deleted === true}
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
// Remove current index
|
||||
console.log("Removing index: ", index)
|
||||
inputQuestions[index].deleted = true
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
>
|
||||
<RemoveIcon style={{}} />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
<Button
|
||||
color="primary"
|
||||
style={{ maxWidth: 50, marginLeft: 15, marginTop: 20, }}
|
||||
variant="outlined"
|
||||
|
||||
disabled={inputQuestions !== undefined && inputQuestions !== null && inputQuestions.length > 5}
|
||||
onClick={() => {
|
||||
inputQuestions.push({
|
||||
"name": "",
|
||||
"value": "",
|
||||
"deleted": false,
|
||||
"required": false
|
||||
})
|
||||
setInputQuestions(inputQuestions)
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
>
|
||||
<AddIcon style={{}} />
|
||||
</Button>
|
||||
|
||||
<div id="input_markdown">
|
||||
<Typography variant="h6" style={{marginTop: 50, }}>
|
||||
Input Markdown
|
||||
</Typography>
|
||||
<Typography variant="body2" color="textSecondary" style={{marginBottom: 20, }}>
|
||||
Markdown will be shown on the <a href={`/forms/${workflow.id}`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Form page</a>. Output for a Workflow is also shown in Markdown, and is controlled by the LAST action that runs.
|
||||
</Typography>
|
||||
<TextField
|
||||
multiline
|
||||
minRows={3}
|
||||
fullWidth
|
||||
color="primary"
|
||||
value={inputMarkdown}
|
||||
onKeyDown={(e) => {
|
||||
//console.log("KEY: ", e.key)
|
||||
if (e.key === "Tab") {
|
||||
e.preventDefault()
|
||||
}
|
||||
}}
|
||||
|
||||
onChange={(e) => {
|
||||
setInputMarkdown(e.target.value)
|
||||
workflow.input_markdown = e.target.value
|
||||
setWorkflow(workflow)
|
||||
setUpdate(Math.random())
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
|
||||
|
||||
<Tooltip color="primary" title={"Add more details"} placement="top">
|
||||
@@ -1047,7 +1065,7 @@ const EditWorkflow = (props) => {
|
||||
</span>
|
||||
: null}
|
||||
|
||||
{newWorkflow === true && name.length > 2 ?
|
||||
{/*newWorkflow === true && name.length > 2 ?
|
||||
<div style={{marginLeft: 30, }}>
|
||||
<WorkflowGrid
|
||||
maxRows={1}
|
||||
@@ -1062,7 +1080,7 @@ const EditWorkflow = (props) => {
|
||||
onlyResults={true}
|
||||
/>
|
||||
</div>
|
||||
: null}
|
||||
: null*/}
|
||||
</FormControl>
|
||||
</Drawer>
|
||||
)
|
||||
|
||||
@@ -132,6 +132,8 @@ const Header = (props) => {
|
||||
isMobile,
|
||||
serverside,
|
||||
billingInfo,
|
||||
|
||||
notifications,
|
||||
} = props;
|
||||
const [isHeader, setIsHeader] = React.useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
@@ -311,18 +313,19 @@ const Header = (props) => {
|
||||
localStorage.setItem("globalUrl", responseJson.region_url);
|
||||
//globalUrl = responseJson.region_url
|
||||
}
|
||||
|
||||
if (responseJson["reason"] === "SSO_REDIRECT") {
|
||||
toast.info("Redirecting to SSO login page as SSO is required for this organization.")
|
||||
setTimeout(() => {
|
||||
toast.info("Redirecting to SSO login page as SSO is required for this organization.")
|
||||
window.location.href = responseJson["url"]
|
||||
return
|
||||
}, 2000)
|
||||
} else {
|
||||
toast("Successfully changed active organization - refreshing!");
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 2000);
|
||||
}
|
||||
toast("Successfully changed active organization - refreshing!");
|
||||
} else {
|
||||
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) {
|
||||
toast(responseJson.reason);
|
||||
@@ -416,26 +419,19 @@ const Header = (props) => {
|
||||
</MenuItem>
|
||||
</Link>
|
||||
|
||||
<Link to="/admin?admin_tab=priorities" style={hrefStyle}>
|
||||
<MenuItem
|
||||
onClick={(event) => {
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
<NotificationsIcon style={{ marginRight: 5 }} /> Notifications
|
||||
</MenuItem>
|
||||
</Link>
|
||||
|
||||
<Divider style={{ marginTop: 10, marginBottom: 10, }} />
|
||||
<Link to="/docs" style={hrefStyle}>
|
||||
<MenuItem
|
||||
onClick={(event) => {
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
<HelpOutlineIcon style={{ marginRight: 5 }} /> About
|
||||
</MenuItem>
|
||||
</Link>
|
||||
<Link to="/admin?admin_tab=priorities" style={hrefStyle}>
|
||||
<MenuItem
|
||||
onClick={(event) => {
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
<NotificationsIcon style={{ marginRight: 5 }} /> Notifications ({
|
||||
notifications === undefined || notifications === null ? 0 :
|
||||
notifications?.filter((notification) => notification.read === false).length
|
||||
})
|
||||
</MenuItem>
|
||||
</Link>
|
||||
{/*
|
||||
<Link to="/getting-started" style={hrefStyle}>
|
||||
<MenuItem
|
||||
@@ -457,7 +453,7 @@ const Header = (props) => {
|
||||
</MenuItem>
|
||||
</Link>
|
||||
|
||||
{userdata?.public_username === undefined || userdata?.public_username === null || userdata?.public_username.length <= 0 ? null :
|
||||
{/*userdata?.public_username === undefined || userdata?.public_username === null || userdata?.public_username.length <= 0 ? null :
|
||||
<Link to={`/creators/${userdata.public_username}`} style={hrefStyle}>
|
||||
<MenuItem
|
||||
onClick={(event) => {
|
||||
@@ -467,9 +463,18 @@ const Header = (props) => {
|
||||
<EmojiObjectsIcon style={{ marginRight: 5 }} /> Creator page
|
||||
</MenuItem>
|
||||
</Link>
|
||||
}
|
||||
*/}
|
||||
|
||||
<Divider style={{ marginTop: 10, marginBottom: 10, }} />
|
||||
<Link to="/docs" style={hrefStyle}>
|
||||
<MenuItem
|
||||
onClick={(event) => {
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
<HelpOutlineIcon style={{ marginRight: 5 }} /> About
|
||||
</MenuItem>
|
||||
</Link>
|
||||
<MenuItem
|
||||
style={{ color: "white" }}
|
||||
onClick={(event) => {
|
||||
@@ -483,7 +488,7 @@ const Header = (props) => {
|
||||
<Divider style={{ marginBottom: 10, }} />
|
||||
|
||||
<Typography variant="body2" color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, }}>
|
||||
Version: 1.4.0
|
||||
Version: 1.4.5
|
||||
</Typography>
|
||||
</Menu>
|
||||
</span>
|
||||
@@ -910,7 +915,6 @@ const Header = (props) => {
|
||||
}}
|
||||
>
|
||||
{avatarMenu}
|
||||
{/*notificationMenu*/}
|
||||
{supportMenu}
|
||||
{logoCheck}
|
||||
</span>
|
||||
|
||||
@@ -167,7 +167,7 @@ const AuthenticationOauth2 = (props) => {
|
||||
//console.log("APP: ", selectedApp)
|
||||
if (selectedApp.name.toLowerCase() == "outlook_graph" || selectedApp.name.toLowerCase() == "outlook_office365") {
|
||||
handleOauth2Request(
|
||||
"efe4c3fe-84a1-4821-a84f-23a6cfe8e72d",
|
||||
"fd55c175-aa30-4fa6-b303-09a29fb3f750",
|
||||
"",
|
||||
"https://graph.microsoft.com",
|
||||
["Mail.ReadWrite", "Mail.Send", "offline_access"],
|
||||
@@ -524,7 +524,7 @@ const AuthenticationOauth2 = (props) => {
|
||||
//alert('"Secure Payment" window closed!');
|
||||
|
||||
if (getAppAuthentication !== undefined) {
|
||||
getAppAuthentication(true, true, true);
|
||||
getAppAuthentication(true, true, true, selectedAction.id)
|
||||
}
|
||||
|
||||
toast("Authentication successful!")
|
||||
@@ -538,7 +538,7 @@ const AuthenticationOauth2 = (props) => {
|
||||
setFinalized(true)
|
||||
}
|
||||
} else {
|
||||
console.log("Not closed")
|
||||
//console.log("Not closed")
|
||||
}
|
||||
}, 1000);
|
||||
//do {
|
||||
@@ -739,7 +739,7 @@ const AuthenticationOauth2 = (props) => {
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<span style={{}}>
|
||||
Oauth2 requires a client ID and secret to authenticate, defined in the remote system. {authenticationType.type === "oauth2-app" ? null : <span>Your redirect URL is <b>{window.location.origin}/set_authentication</b> - </span>}
|
||||
Oauth2 requires a client ID and secret to authenticate, defined in the remote system. <span>Your redirect URL is <b>{window.location.origin}/set_authentication</b> - </span>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="norefferer"
|
||||
|
||||
@@ -323,6 +323,44 @@ const OrgHeaderexpanded = (props) => {
|
||||
setSSORequired(event.target.checked);
|
||||
};
|
||||
|
||||
|
||||
const HandleTestSSO = () => {
|
||||
const url = `${globalUrl}/api/v1/orgs/${selectedOrganization?.id}/change`;
|
||||
const data = {
|
||||
org_id: selectedOrganization?.id,
|
||||
sso_test: true,
|
||||
}
|
||||
fetch(url, {
|
||||
mode: "cors",
|
||||
credentials: "include",
|
||||
crossDomain: true,
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
}).then((response) => {
|
||||
if (response.status !== 200) {
|
||||
toast.error("Failed to test sso. Please try again later or contact support@shuffler.io if issue persist.")
|
||||
return
|
||||
}
|
||||
return response.json();
|
||||
}).then((responjson) => {
|
||||
if (responjson["reason"] === "SSO_REDIRECT") {
|
||||
setTimeout(() => {
|
||||
toast.info("Redirecting to SSO login page as SSO is required for this organization.")
|
||||
window.location.href = responjson["url"];
|
||||
return
|
||||
}, 2000)
|
||||
} else {
|
||||
toast.error("No SSO found for this org. Please set up sso for this org.")
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log("error for sso test is: ", err)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Grid container spacing={3} style={{ textAlign: "left" }}>
|
||||
@@ -677,6 +715,42 @@ const OrgHeaderexpanded = (props) => {
|
||||
{SSORequired ? 'Required' : 'Optional'}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', marginTop: 20, marginLeft: 10, width: "100%", borderBottom: '1px solid #414347', paddingBottom: 10 }}>
|
||||
<Typography variant="body1" style={{ margin: '5px 0px 5px 10px' }}>
|
||||
You can test your SSO configuration by clicking the button below. Before testing, ensure you have set Open ID Connect or SAML SSO credentials.
|
||||
</Typography>
|
||||
<Tooltip
|
||||
title={
|
||||
!(
|
||||
ssoEntrypoint.length > 0 ||
|
||||
ssoCertificate.length > 0 ||
|
||||
openidAuthorization.length > 0 ||
|
||||
openidClientId.length > 0
|
||||
)
|
||||
? "Please ensure all SSO credentials are set before testing."
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<span style={{ width: 100 }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
style={{ width: 100, textTransform: 'none', margin: 10 }}
|
||||
disabled={
|
||||
!(
|
||||
ssoEntrypoint.length > 0 ||
|
||||
ssoCertificate.length > 0 ||
|
||||
openidAuthorization.length > 0 ||
|
||||
openidClientId.length > 0
|
||||
)
|
||||
}
|
||||
onClick={HandleTestSSO}
|
||||
>
|
||||
Test SSO
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Grid item xs={12} style={{}}>
|
||||
<Typography variant="h6" style={{ textAlign: "center", }}>OpenID connect</Typography>
|
||||
<Grid container style={{ marginTop: 10, }}>
|
||||
|
||||
@@ -90,9 +90,10 @@ import {
|
||||
Circle as CircleIcon,
|
||||
SquareFoot as SquareFootIcon,
|
||||
Storage as StorageIcon,
|
||||
Check as CheckIcon,
|
||||
} from '@mui/icons-material';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
export const useStyles = makeStyles({
|
||||
notchedOutline: {
|
||||
borderColor: "#f85a3e !important",
|
||||
},
|
||||
@@ -1500,92 +1501,7 @@ const ParsedAction = (props) => {
|
||||
<DescriptionIcon style={{ color: "rgba(255,255,255,0.7)" }} />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
{/*
|
||||
<IconButton
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
height: 30,
|
||||
marginLeft: 15,
|
||||
paddingRight: 0,
|
||||
}}
|
||||
onClick={() => {}}
|
||||
>
|
||||
<a
|
||||
href="https://shuffler.io/docs/workflows#nodes"
|
||||
rel="norefferer"
|
||||
target="_blank"
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
>
|
||||
<Tooltip
|
||||
color="primary"
|
||||
title="What are actions?"
|
||||
placement="top"
|
||||
>
|
||||
<HelpOutlineIcon style={{ color: "rgba(255,255,255,0.7)" }} />
|
||||
</Tooltip>
|
||||
</a>
|
||||
</IconButton>
|
||||
*/}
|
||||
{/*
|
||||
<IconButton
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
height: 30,
|
||||
marginLeft: 15,
|
||||
paddingRight: 0,
|
||||
}}
|
||||
onClick={() => {
|
||||
//setAuthenticationModalOpen(true);
|
||||
console.log("Should enable/disable magic!")
|
||||
console.log("Action: ", selectedAction)
|
||||
if (selectedAction.run_magic_output === undefined) {
|
||||
selectedAction.run_magic_output = true
|
||||
} else {
|
||||
if (selectedAction.run_magic_output === true) {
|
||||
selectedAction.run_magic_output = false
|
||||
} else {
|
||||
selectedAction.run_magic_output = true
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedAction(selectedAction)
|
||||
setUpdate(Math.random());
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
color="primary"
|
||||
title={selectedAction.run_magic_output === undefined || selectedAction.run_magic_output === null || selectedAction.run_magic_output === false ? "Click to enable magic parsing" : "Click to disable magic parsing"}
|
||||
placement="top"
|
||||
>
|
||||
<AutoFixHighIcon style={{ color: selectedAction.run_magic_output === undefined || selectedAction.run_magic_output === null || selectedAction.run_magic_output === false ? "rgba(255,255,255,0.7)" : "#f86a3e"}} />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
*/}
|
||||
{/*
|
||||
<IconButton
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
height: 30,
|
||||
marginLeft: 15,
|
||||
paddingRight: 0,
|
||||
}}
|
||||
onClick={() => {
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
color="primary"
|
||||
title={"Find related tworkflows"}
|
||||
placement="top"
|
||||
>
|
||||
<a href={`https://shuffler.io/search?tab=workflows&q=${selectedAction.app_name}`} target="_blank">
|
||||
<SearchIcon style={{ color: "rgba(255,255,255,0.7)"}} />
|
||||
</a>
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
*/}
|
||||
<IconButton
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
@@ -1602,6 +1518,10 @@ const ParsedAction = (props) => {
|
||||
aiSubmit("Fill based on previous values", undefined, undefined, selectedAction)
|
||||
//}
|
||||
setAutocompleting(true)
|
||||
|
||||
setTimeout(() => {
|
||||
setAutocompleting(false)
|
||||
}, 3000)
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
@@ -1998,7 +1918,7 @@ const ParsedAction = (props) => {
|
||||
|
||||
for (let [key,keyval] in Object.entries(selectedAction.parameters)) {
|
||||
if (selectedAction.parameters[key].configuration === false) {
|
||||
console.log("FIELDSKIP: ", selectedAction.parameters[key].name)
|
||||
//console.log("FIELDSKIP: ", selectedAction.parameters[key].name)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -2085,7 +2005,18 @@ const ParsedAction = (props) => {
|
||||
}}
|
||||
value={data}
|
||||
>
|
||||
{data.last_modified === true ?
|
||||
|
||||
{data?.validation?.valid === true ?
|
||||
<Tooltip title="Authentication has been validated" placement="top">
|
||||
<Chip
|
||||
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer", borderColor: green, }}
|
||||
label={"Valid"}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
/>
|
||||
</Tooltip>
|
||||
: null }
|
||||
{data?.last_modified === true ?
|
||||
<Chip
|
||||
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}}
|
||||
label={"Latest"}
|
||||
@@ -2093,14 +2024,14 @@ const ParsedAction = (props) => {
|
||||
color="secondary"
|
||||
/>
|
||||
: null}
|
||||
{data.app.app_version !== undefined && data.app.app_version !== null && data.app.app_version !== "" && data.app.app_version !== "undefined" ?
|
||||
{/*data.app.app_version !== undefined && data.app.app_version !== null && data.app.app_version !== "" && data.app.app_version !== "undefined" ?
|
||||
<Chip
|
||||
style={{marginLeft: 0, padding: 0, marginRight: 10, cursor: "pointer",}}
|
||||
label={data.app.app_version}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
/>
|
||||
: null}
|
||||
: null*/}
|
||||
{data.label}
|
||||
</MenuItem>
|
||||
);
|
||||
@@ -2342,6 +2273,7 @@ const ParsedAction = (props) => {
|
||||
value={selectedAction}
|
||||
classes={{ inputRoot: classes.inputRoot }}
|
||||
groupBy={(option) => {
|
||||
// FIXME: Sorting
|
||||
// Most popular
|
||||
// Is categorized
|
||||
// Uncategorized
|
||||
@@ -2364,7 +2296,6 @@ const ParsedAction = (props) => {
|
||||
},
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
//console.log("Option contains?: ", inputValue, options)
|
||||
const lowercaseValue = inputValue.toLowerCase()
|
||||
options = options.filter(x => x.name.replaceAll("_", " ").toLowerCase().includes(lowercaseValue) || x.description.toLowerCase().includes(lowercaseValue))
|
||||
|
||||
@@ -2449,22 +2380,6 @@ const ParsedAction = (props) => {
|
||||
extraUrl = descSplit[descSplit.length-1]
|
||||
}
|
||||
|
||||
//for (let [line,lineval] in Object.entries(descSplit)) {
|
||||
// if (descSplit[line].includes("http") && descSplit[line].includes("://")) {
|
||||
// const urlsplit = descSplit[line].split("/")
|
||||
// try {
|
||||
// extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/")
|
||||
// } catch (e) {
|
||||
// //console.log("Failed - running with -1")
|
||||
// extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/")
|
||||
// }
|
||||
|
||||
|
||||
// //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line])
|
||||
// //break
|
||||
// }
|
||||
//}
|
||||
|
||||
if (extraUrl.length > 0) {
|
||||
if (extraUrl.includes(" ")) {
|
||||
extraUrl = extraUrl.split(" ")[0]
|
||||
@@ -2492,6 +2407,7 @@ const ParsedAction = (props) => {
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => {
|
||||
|
||||
if (params.inputProps?.value) {
|
||||
const prefixes = ["Post", "Put", "Patch"];
|
||||
for (let prefix of prefixes) {
|
||||
@@ -2512,84 +2428,86 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
|
||||
|
||||
const actionDescription = (
|
||||
<Box
|
||||
p={1.5}
|
||||
borderRadius={3}
|
||||
boxShadow={2}
|
||||
backgroundColor={theme.palette.textFieldStyle}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||
<Typography variant="body1" style={{ flexGrow: 1 }}>
|
||||
{params.inputProps.value}
|
||||
</Typography>
|
||||
<IconButton size="small"
|
||||
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}}
|
||||
|
||||
onClick={() => {
|
||||
setHiddenDescription(true)
|
||||
const inputElement = document.getElementById(uiBox);
|
||||
if (inputElement) {
|
||||
inputElement.focus();
|
||||
}
|
||||
}}>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Divider sx={{ backgroundColor: theme.palette.surfaceColor, marginTop: "5px", marginBottom : "10px", height: "3px" }}/>
|
||||
<Box display="flex" flexDirection="column">
|
||||
<Typography variant="body2" mb={0.5}>
|
||||
<strong>Description: </strong> {selectedAction?.description}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip title={actionDescription}
|
||||
placement="right"
|
||||
open={!hiddenDescription}
|
||||
PopperProps={{
|
||||
sx: {
|
||||
'& .MuiTooltip-tooltip': {
|
||||
backgroundColor: 'transparent',
|
||||
boxShadow: 'none',
|
||||
},
|
||||
'& .MuiTooltip-arrow': {
|
||||
color: 'transparent',
|
||||
},
|
||||
},
|
||||
}}
|
||||
const actionDescription = null
|
||||
/*(
|
||||
<Box
|
||||
p={1.5}
|
||||
borderRadius={3}
|
||||
boxShadow={2}
|
||||
backgroundColor={theme.palette.textFieldStyle}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
>
|
||||
<TextField
|
||||
{...params}
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||
<Typography variant="body1" style={{ flexGrow: 1 }}>
|
||||
{params.inputProps.value}
|
||||
</Typography>
|
||||
<IconButton size="small"
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}}
|
||||
|
||||
onClick={() => {
|
||||
setHiddenDescription(true)
|
||||
const inputElement = document.getElementById(uiBox);
|
||||
if (inputElement) {
|
||||
inputElement.focus();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Divider sx={{ backgroundColor: theme.palette.surfaceColor, marginTop: "5px", marginBottom : "10px", height: "3px" }}/>
|
||||
<Box display="flex" flexDirection="column">
|
||||
<Typography variant="body2" mb={0.5}>
|
||||
<strong>Description: </strong> {selectedAction?.description}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
*/
|
||||
|
||||
data-lpignore="true"
|
||||
autocomplete="off"
|
||||
dataLPIgnore="true"
|
||||
autoComplete="off"
|
||||
|
||||
color="primary"
|
||||
id="checkbox-search"
|
||||
variant="body1"
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
return (
|
||||
<Tooltip title={actionDescription}
|
||||
placement="right"
|
||||
open={!hiddenDescription}
|
||||
PopperProps={{
|
||||
sx: {
|
||||
'& .MuiTooltip-tooltip': {
|
||||
backgroundColor: 'transparent',
|
||||
boxShadow: 'none',
|
||||
},
|
||||
'& .MuiTooltip-arrow': {
|
||||
color: 'transparent',
|
||||
},
|
||||
},
|
||||
}}
|
||||
label={isIntegration ? "Choose a category" : "Find Actions"}
|
||||
variant="outlined"
|
||||
name={`disable_autocomplete_${Math.random()}`}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<TextField
|
||||
{...params}
|
||||
|
||||
data-lpignore="true"
|
||||
autocomplete="off"
|
||||
dataLPIgnore="true"
|
||||
autoComplete="off"
|
||||
|
||||
color="primary"
|
||||
id="checkbox-search"
|
||||
variant="body1"
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
label={isIntegration ? "Choose a category" : "Find Actions"}
|
||||
variant="outlined"
|
||||
name={`disable_autocomplete_${Math.random()}`}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{/*setNewSelectedAction !== undefined ?
|
||||
@@ -2939,58 +2857,24 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (
|
||||
(selectedAction.auth_not_required !== undefined && !selectedAction.auth_not_required) &&
|
||||
selectedActionParameters[count] !== undefined &&
|
||||
selectedActionParameters[count] !== null &&
|
||||
selectedActionParameters[count].value !== undefined &&
|
||||
selectedAction.parameters[count] !== undefined &&
|
||||
selectedAction.parameters[count] !== null &&
|
||||
selectedAction.parameters[count].value !== undefined &&
|
||||
selectedAction.selectedAuthentication !== undefined &&
|
||||
selectedAction.selectedAuthentication.fields !== undefined &&
|
||||
selectedAction.selectedAuthentication.fields[data.name] !==
|
||||
undefined
|
||||
) {
|
||||
*/
|
||||
|
||||
/*
|
||||
if (selectedAction.selectedAuthentication !== undefined && selectedAction.selectedAuthentication.fields !== undefined && selectedAction.selectedAuthentication.fields[data.name] !== undefined) {
|
||||
|
||||
// This sets the placeholder in the frontend. (Replaced in backend)
|
||||
selectedActionParameters[count].value = selectedAction.selectedAuthentication.fields[data.name];
|
||||
selectedAction.parameters[count].value = selectedAction.selectedAuthentication.fields[data.name];
|
||||
setSelectedAction(selectedAction);
|
||||
//setUpdate(Math.random())
|
||||
|
||||
if (authWritten) {
|
||||
return null
|
||||
}
|
||||
|
||||
authWritten = true
|
||||
return (
|
||||
<Typography
|
||||
key={count}
|
||||
id="skip_auth"
|
||||
variant="body2"
|
||||
color="textSecondary"
|
||||
style={{ marginTop: 5 }}
|
||||
>
|
||||
Authentication fields are hidden
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
*/
|
||||
if (selectedAction.parameters === undefined || selectedAction.parameters === null || selectedAction.parameters.length !== selectedActionParameters.length) {
|
||||
|
||||
//selectedAction.parameters = selectedActionParameters
|
||||
console.log("PARAM BUG: ", selectedAction)
|
||||
}
|
||||
|
||||
//!selectedAction.auth_not_required &&
|
||||
if (selectedAction.selectedAuthentication !== undefined && selectedAction.selectedAuthentication.fields !== undefined && selectedAction.selectedAuthentication.fields[data.name] !== undefined) {
|
||||
|
||||
// This sets the placeholder in the frontend. (Replaced in backend)
|
||||
selectedActionParameters[count].value =
|
||||
selectedAction.selectedAuthentication.fields[data.name];
|
||||
selectedAction.parameters[count].value =
|
||||
selectedAction.selectedAuthentication.fields[data.name];
|
||||
if (selectedActionParameters[count] !== undefined) {
|
||||
selectedActionParameters[count].value = selectedAction.selectedAuthentication.fields[data.name]
|
||||
}
|
||||
|
||||
if (selectedAction.parameters[count] !== undefined) {
|
||||
selectedAction.parameters[count].value = selectedAction.selectedAuthentication.fields[data.name]
|
||||
}
|
||||
|
||||
setSelectedAction(selectedAction);
|
||||
//setUpdate(Math.random())
|
||||
|
||||
@@ -3064,7 +2948,7 @@ const ParsedAction = (props) => {
|
||||
|
||||
if (data.value.length === 0) {
|
||||
if (data.name.toLowerCase() === "headers") {
|
||||
console.log("Should show headers field instead with + and -!")
|
||||
//console.log("Should show headers field instead with + and -!")
|
||||
|
||||
// Check if file ID exists
|
||||
//
|
||||
@@ -3360,13 +3244,6 @@ const ParsedAction = (props) => {
|
||||
<IconButton size="small"
|
||||
onClick={() => {
|
||||
setUiBox("closed")
|
||||
|
||||
/*
|
||||
const inputElement = document.getElementById(uiBox);
|
||||
if (inputElement) {
|
||||
inputElement.focus();
|
||||
}
|
||||
*/
|
||||
}}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
|
||||
@@ -393,7 +393,10 @@ const Priorities = (props) => {
|
||||
|
||||
return (
|
||||
<div style={{width: clickedFromOrgTab ? 1030:1000, padding: clickedFromOrgTab ? 27:null, height: clickedFromOrgTab ? "auto":null, backgroundColor: clickedFromOrgTab ? '#212121':null, borderRadius: clickedFromOrgTab ? '16px':null, }}>
|
||||
<h2 style={{ display: clickedFromOrgTab?null:"inline", marginBottom: clickedFromOrgTab? 8:null, marginTop: clickedFromOrgTab?40:null, color: clickedFromOrgTab?"#ffffff":null }}>Notifications</h2>
|
||||
<h2 style={{ display: clickedFromOrgTab?null:"inline", marginBottom: clickedFromOrgTab? 8:null, marginTop: clickedFromOrgTab?40:null, color: clickedFromOrgTab?"#ffffff":null }}>Notifications ({
|
||||
notifications?.filter((notification) => showRead === true || notification.read === false).length
|
||||
})</h2>
|
||||
|
||||
<span style={{ marginLeft: clickedFromOrgTab?null:25, color: clickedFromOrgTab?"#9E9E9E":null, }}>
|
||||
Notifications help you find potential problems with your workflows and apps.
|
||||
<a
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
const Priority = (props) => {
|
||||
const { globalUrl, clickedFromOrgTab,userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, appFramework, } = props;
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
let navigate = useNavigate();
|
||||
|
||||
if (window.location.pathname === "/workflows") {
|
||||
|
||||
@@ -71,7 +71,7 @@ const SearchData = props => {
|
||||
// return null
|
||||
//}
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
// if (window.location.pathname === "/docs" || window.location.pathname === "/apps" || window.location.pathname === "/usecases" ) {
|
||||
// setModalOpen(false)
|
||||
// }
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
|
||||
|
||||
import { validateJson } from "../views/Workflows.jsx";
|
||||
import ReactJson from "react-json-view";
|
||||
import ReactJson from "react-json-view-ssr";
|
||||
import PaperComponent from "../components/PaperComponent.jsx";
|
||||
|
||||
import { padding, textAlign } from '@mui/system';
|
||||
|
||||
@@ -349,7 +349,7 @@ const UsecaseSearch = (props) => {
|
||||
const [selectedAction, setSelectedAction] = React.useState({});
|
||||
const [firstRequest, setFirstRequest] = React.useState(true);
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
//const alert = useAlert()
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -161,7 +161,7 @@ const WelcomeForm = (props) => {
|
||||
const [clickdiff, setclickdiff] = useState(0);
|
||||
const [mouseHoverIndex, setMouseHoverIndex] = useState(-1)
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
//const alert = useAlert();
|
||||
let navigate = useNavigate();
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e52
|
||||
const AppGrid = props => {
|
||||
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, onlyResults, inputsearch } = props
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
|
||||
const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 4 : parsedXs
|
||||
//const [apps, setApps] = React.useState([]);
|
||||
|
||||
@@ -47,7 +47,7 @@ const WorkflowTemplatePopup = (props) => {
|
||||
|
||||
const [requestSent, setRequestSent] = React.useState(false)
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
let navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
if (modalOpen !== true) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
grey,
|
||||
} from "../views/AngularWorkflow.jsx"
|
||||
|
||||
import WorkflowTemplatePopup2 from "../components/WorkflowTemplatePopup2.jsx"
|
||||
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
||||
import theme from "../theme.jsx";
|
||||
const itemHeight = 24
|
||||
@@ -63,7 +64,7 @@ export const getParentNodes = (workflow, action) => {
|
||||
currentnode = workflow.triggers.find((element) => element.id === allkeys[parentkey])
|
||||
|
||||
if (currentnode === undefined) {
|
||||
console.log("Could not find parent node for: ", allkeys[parentkey])
|
||||
//console.log("Could not find parent node for: ", allkeys[parentkey])
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -128,11 +129,13 @@ export const getParentNodes = (workflow, action) => {
|
||||
}
|
||||
|
||||
const WorkflowValidationTimeline = (props) => {
|
||||
const { workflow, originalWorkflow, apps, getParents, execution} = props
|
||||
const { globalUrl, userdata, workflow, originalWorkflow, apps, getParents, execution, showHoverColor, } = props
|
||||
|
||||
const [hovering, setHovering] = useState(false)
|
||||
const [decidedColor, setDecidedColor] = useState(grey)
|
||||
const [isClicked, setIsClicked] = useState(false)
|
||||
|
||||
const showMiddle = false
|
||||
|
||||
|
||||
if (workflow === undefined || workflow === null) {
|
||||
return null
|
||||
}
|
||||
@@ -146,13 +149,11 @@ const WorkflowValidationTimeline = (props) => {
|
||||
}
|
||||
|
||||
if (workflow.triggers === undefined || workflow.triggers === null) {
|
||||
workflow.triggers = []
|
||||
|
||||
workflow.triggers = []
|
||||
}
|
||||
|
||||
if (workflow.branches === undefined || workflow.branches === null) {
|
||||
workflow.branches = []
|
||||
|
||||
workflow.branches = []
|
||||
}
|
||||
|
||||
var results = []
|
||||
@@ -260,6 +261,12 @@ const WorkflowValidationTimeline = (props) => {
|
||||
relevantactions.push(...newactions)
|
||||
}
|
||||
|
||||
console.log("Relevant actions (return null if 0-1): ", relevantactions)
|
||||
|
||||
if (relevantactions.length <= 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Sort according to how many parents a node has. MAY be wrong~
|
||||
relevantactions.sort((a, b) => {
|
||||
if (a.order === undefined) {
|
||||
@@ -279,15 +286,81 @@ const WorkflowValidationTimeline = (props) => {
|
||||
var skipped = false
|
||||
|
||||
var previousTools = false
|
||||
var scheduleNotStarted = false
|
||||
|
||||
if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.validation_ran === false) {
|
||||
console.log("Validation didn't run. Why?")
|
||||
return null
|
||||
}
|
||||
|
||||
if (workflow.validation !== undefined && workflow.validation !== null && workflow.validation.errors !== undefined && workflow.validation.errors !== null && workflow.validation.errors.length > 0) {
|
||||
var newErrors = []
|
||||
for (var key in workflow.validation.errors) {
|
||||
const error = workflow.validation.errors[key]
|
||||
if (error.type === "SCHEDULE") {
|
||||
scheduleNotStarted = true
|
||||
continue
|
||||
}
|
||||
|
||||
newErrors.push(error)
|
||||
}
|
||||
|
||||
workflow.validation.errors = newErrors
|
||||
}
|
||||
|
||||
// Use this variable to control visualization
|
||||
//const showMiddle = false
|
||||
// border: workflow.validation.valid ? `2px solid ${green}` : "1px solid rgba(255,255,255,0.4)",
|
||||
var middleError = ""
|
||||
var startBranchColor = ""
|
||||
var middleBranchColor = ""
|
||||
|
||||
|
||||
const showHoverForClick = showHoverColor === true ? true : false
|
||||
return (
|
||||
<div style={{ padding: "10px 5px 10px 5px", borderRadius: theme.palette.borderRadius, }}>
|
||||
<div
|
||||
style={{
|
||||
padding: "10px 5px 10px 5px",
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
|
||||
border: hovering === true && showHoverForClick === true ? `1px solid ${decidedColor}` : "1px solid rgba(255,255,255,0.0)",
|
||||
cursor: hovering === true && showHoverForClick === true ? "pointer" : "default",
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
if (isClicked === false) {
|
||||
setHovering(true)
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (isClicked === false) {
|
||||
setHovering(false)
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
if (showHoverForClick === true) {
|
||||
setIsClicked(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
{isClicked === false ? null :
|
||||
<WorkflowTemplatePopup2
|
||||
globalUrl={globalUrl}
|
||||
userdata={userdata}
|
||||
|
||||
isModalOpenDefault={isClicked}
|
||||
workflowBuilt={true}
|
||||
setIsClicked={setIsClicked}
|
||||
inputWorkflowId={workflow.id}
|
||||
/>
|
||||
}
|
||||
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
|
||||
{scheduleNotStarted === true ?
|
||||
null
|
||||
: null}
|
||||
|
||||
{relevantactions.map((action, index) => {
|
||||
action.result = {}
|
||||
if (results !== undefined) {
|
||||
@@ -309,8 +382,10 @@ const WorkflowValidationTimeline = (props) => {
|
||||
const validate = validateJson(action.result.result)
|
||||
if (validate.valid) {
|
||||
if (validate.result.success === true) {
|
||||
nodecolor = green
|
||||
branchcolor = green
|
||||
} else {
|
||||
nodecolor = grey
|
||||
branchcolor = grey
|
||||
}
|
||||
}
|
||||
@@ -319,9 +394,12 @@ const WorkflowValidationTimeline = (props) => {
|
||||
} else if (action.status === "SKIPPED") {
|
||||
branchcolor = grey
|
||||
} else {
|
||||
// FIXME: How do we handle this?
|
||||
if (action.status === undefined) {
|
||||
branchcolor = green
|
||||
nodecolor = grey
|
||||
branchcolor = grey
|
||||
} else {
|
||||
nodecolor = red
|
||||
branchcolor = red
|
||||
}
|
||||
}
|
||||
@@ -389,29 +467,64 @@ const WorkflowValidationTimeline = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
var appgroup = []
|
||||
if (action.app_name === "shuffle-subflow") {
|
||||
if (action.status === "SUCCESS") {
|
||||
nodecolor = green
|
||||
branchcolor = green
|
||||
}
|
||||
|
||||
if (workflow.validation.subflow_apps !== undefined && workflow.validation.subflow_apps !== null && workflow.validation.subflow_apps.length > 0) {
|
||||
nodecolor = red
|
||||
branchcolor = red
|
||||
|
||||
for (var subflowkey in workflow.validation.subflow_apps) {
|
||||
const subflowApp = workflow.validation.subflow_apps[subflowkey]
|
||||
founderror += "- " + subflowApp.error+"\n"
|
||||
|
||||
if (subflowApp.error === action.id) {
|
||||
appgroup.push(subflowApp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!showMiddle && relevantactions.length > 2 && index > 0 && index === relevantactions.length - 2) {
|
||||
if (founderror.length > 0) {
|
||||
middleError += founderror+"\n"
|
||||
|
||||
middleBranchColor = branchcolor
|
||||
}
|
||||
|
||||
if (index === relevantactions.length-2 && relevantactions.length > 2) {
|
||||
|
||||
const selectedIcon = middleError.length > 0 ?
|
||||
<Tooltip title={middleError}>
|
||||
<Tooltip title={
|
||||
<Typography variant="body1" style={{margin: 5, whiteSpace: "pre-line", }}>
|
||||
{middleError}
|
||||
</Typography>
|
||||
}>
|
||||
<IconButton style={{width: 30, height: 30, backgroundColor: "rgba(255,255,255,0.0)", borderRadius: 30, marginTop: 2, }}>
|
||||
<ErrorOutlineIcon style={{color: "red", }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
: null
|
||||
|
||||
return (
|
||||
selectedIcon
|
||||
)
|
||||
return selectedIcon
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Returns for anything non-middle
|
||||
if (relevantactions.length > 2 && index >= 1 && index < relevantactions.length - 2) {
|
||||
if (founderror.length > 0) {
|
||||
middleError += founderror+"\n"
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
if (skipped && !lastitem) {
|
||||
nodecolor = grey
|
||||
branchcolor = grey
|
||||
@@ -423,28 +536,14 @@ const WorkflowValidationTimeline = (props) => {
|
||||
branchcolor = nodecolor
|
||||
}
|
||||
|
||||
var appgroup = []
|
||||
if (action.trigger_type === "WEBHOOK") {
|
||||
nodecolor = green
|
||||
branchcolor = green
|
||||
} else if (action.app_name === "shuffle-subflow") {
|
||||
if (action.status === "SUCCESS") {
|
||||
nodecolor = green
|
||||
branchcolor = green
|
||||
}
|
||||
|
||||
for (var subflowkey in workflow.validation.subflow_apps) {
|
||||
const subflowApp = workflow.validation.subflow_apps[subflowkey]
|
||||
if (subflowApp.error === action.id) {
|
||||
appgroup.push(subflowApp)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var flex = index !== 0 && index !== relevantactions.length - 1 ? 1 : 3
|
||||
|
||||
if (nodecolor === green) {
|
||||
branchcolor = green
|
||||
} else if (nodecolor === yellow) {
|
||||
@@ -455,12 +554,29 @@ const WorkflowValidationTimeline = (props) => {
|
||||
|
||||
if (index === 0) {
|
||||
startBranchColor = branchcolor
|
||||
} else if (index !== 0 && index !== relevantactions.length - 1) {
|
||||
// FIXME: This doesn't work yet
|
||||
middleBranchColor = branchcolor
|
||||
}
|
||||
|
||||
if (lastitem && middleError.length === 0) {
|
||||
branchcolor = startBranchColor
|
||||
if (lastitem) {
|
||||
if (middleError.length === 0) {
|
||||
branchcolor = startBranchColor
|
||||
} else {
|
||||
//branchcolor = middleBranchColor
|
||||
}
|
||||
|
||||
if (founderror === "") {
|
||||
nodecolor = green
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: This could mean the workflow hasn't ran yet
|
||||
if (workflow.validation.valid === false && (workflow.validation.errors === undefined || workflow.validation.errors === null || workflow.validation.errors.length == 0) && (workflow.validation.subflow_apps === undefined || workflow.validation.subflow_apps === null || workflow.validation.subflow_apps.length == 0)) {
|
||||
nodecolor = grey
|
||||
branchcolor = grey
|
||||
}
|
||||
|
||||
const branchTooltip = branchcolor === yellow ? "Check nodes for errors" : ""
|
||||
const appname = action.app_name.replaceAll('_', ' ').slice(0, 16)
|
||||
|
||||
@@ -488,6 +604,14 @@ const WorkflowValidationTimeline = (props) => {
|
||||
console.log("MISSING IMAGE: ", appname, image, action)
|
||||
}
|
||||
|
||||
if (decidedColor === grey && nodecolor === green) {
|
||||
setDecidedColor(red)
|
||||
}
|
||||
|
||||
if (decidedColor !== red && nodecolor === red) {
|
||||
setDecidedColor(red)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{display: "flex", flex: flex, justifyContent: "right", }}>
|
||||
{lastitem ?
|
||||
@@ -528,7 +652,7 @@ const WorkflowValidationTimeline = (props) => {
|
||||
:
|
||||
<Tooltip title={
|
||||
<Typography variant="body1" style={{margin: 5, color: "white", }}>
|
||||
{founderror.length > 0 ? founderror : `App: ${appname}`}
|
||||
{founderror.length > 0 ? founderror : `App: ${appname} - Action: ${action.label}`}
|
||||
</Typography>
|
||||
} placement="top">
|
||||
|
||||
|
||||
@@ -467,13 +467,66 @@ const data = [
|
||||
"font-size": "0px",
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: "node:selected",
|
||||
css: {
|
||||
"border-color": "#f86a3e",
|
||||
"border-width": "7px",
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: "node:selected",
|
||||
css: {
|
||||
"border-color": "#f86a3e",
|
||||
"border-width": "7px",
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[buttonType="condition-drag"]`,
|
||||
css: {
|
||||
"width": "5px",
|
||||
"height": "5px",
|
||||
"background-color": "#f85a3e",
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[name="switch"]`,
|
||||
css: {
|
||||
label: function(element) {
|
||||
// Load from the actual element
|
||||
var nodeheight = 400
|
||||
var conditions = [{
|
||||
"name": "Condition 1",
|
||||
"check": "X equals Y",
|
||||
},
|
||||
{
|
||||
"name": "Condition 2",
|
||||
"check": "X2 equals Y2",
|
||||
},
|
||||
{
|
||||
"name": "Condition 3",
|
||||
"check": "X3 equals Y3",
|
||||
}]
|
||||
|
||||
conditions.push({
|
||||
"name": "Else",
|
||||
"check": "If all else fails",
|
||||
})
|
||||
|
||||
const newlines = nodeheight / conditions.length
|
||||
console.log("Newlines: ", newlines)
|
||||
|
||||
const label = conditions.map((condition) => {
|
||||
return `${condition.name}\n\n\n`
|
||||
}).join("\n")
|
||||
|
||||
return label
|
||||
},
|
||||
color: "white",
|
||||
"border-color": "#f85a3e",
|
||||
"background-color": "#1f1f1f",
|
||||
"font-size": "19px",
|
||||
"text-margin-x": "-110px",
|
||||
"text-wrap": "wrap",
|
||||
shape: "roundrectangle",
|
||||
width: "100",
|
||||
height: "300",
|
||||
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
//{
|
||||
|
||||
@@ -6274,7 +6274,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
style={{ minWidth: 50, maxWidth: 50 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="License"
|
||||
primary="Scale"
|
||||
style={{ minWidth: 85, maxWidth: 85 }}
|
||||
/>
|
||||
<ListItemText
|
||||
@@ -6406,7 +6406,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip
|
||||
title="Not licensed, and can't scale.. This may cause service disruption."
|
||||
title="In Verbose mode. Set SHUFFLE_SWARM_CONFIG=run to Scale. This will not be as verbose. Details: https://shuffler.io/docs/configuration#scaling-shuffle"
|
||||
placement="top"
|
||||
>
|
||||
<a
|
||||
@@ -6441,7 +6441,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
environment.running_ip.length === 0 ? (
|
||||
<div>Not running</div>
|
||||
) : (
|
||||
environment.running_ip.split(":")[0]
|
||||
environment.running_ip
|
||||
)
|
||||
) : (
|
||||
"N/A"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -544,7 +544,7 @@ const AppCreator = (defaultprops) => {
|
||||
};
|
||||
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
|
||||
useEffect(() => {
|
||||
if (window.location.pathname.includes("apps/edit")) {
|
||||
@@ -897,8 +897,31 @@ const AppCreator = (defaultprops) => {
|
||||
};
|
||||
|
||||
if (methodvalue["x-label"] !== undefined && methodvalue["x-label"] !== null) {
|
||||
console.log("LABEL: ", methodvalue["x-label"])
|
||||
|
||||
var correctlabel = ""
|
||||
const labels = methodvalue["x-label"].split(",")
|
||||
for (let labelkey in labels) {
|
||||
var label = labels[labelkey].trim()
|
||||
if (label.toLowerCase() === "no label") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove quotes and escapes
|
||||
label = label.replace(/['"]+/g, '')
|
||||
label = label.replace(/\\/g, '')
|
||||
|
||||
//label = label.replace("_", " ", -1)
|
||||
//label = label.charAt(0).toUpperCase() + label.slice(1)
|
||||
|
||||
correctlabel = label
|
||||
break
|
||||
}
|
||||
|
||||
console.log("LABEL: ", correctlabel)
|
||||
// FIX: Map labels only if they're actually in the category list
|
||||
newaction.action_label = methodvalue["x-label"]
|
||||
//newaction.action_label = methodvalue["x-label"]
|
||||
newaction.action_label = correctlabel
|
||||
}
|
||||
|
||||
if (methodvalue["x-required-fields"] !== undefined && methodvalue["x-required-fields"] !== null) {
|
||||
@@ -3124,15 +3147,14 @@ const AppCreator = (defaultprops) => {
|
||||
Scopes for Oauth2
|
||||
</Typography>
|
||||
<MuiChipsInput
|
||||
style={{border: "2px solid #f86a3e", borderRadius: theme.palette.borderRadius,}}
|
||||
required
|
||||
required
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
maxHeight: 50,
|
||||
maxHeight: 160,
|
||||
},
|
||||
}}
|
||||
style={{ maxHeight: 80, overflowX: "hidden", overflowY: "auto" }}
|
||||
style={{ minHeight: 160, maxHeight: 160, overflowX: "hidden", overflowY: "auto" }}
|
||||
placeholder="Available Oauth2 Scopes"
|
||||
color="primary"
|
||||
fullWidth
|
||||
@@ -3159,7 +3181,7 @@ const AppCreator = (defaultprops) => {
|
||||
required
|
||||
style={{ marginTop: 0, backgroundColor: inputColor }}
|
||||
fullWidth={true}
|
||||
placeholder="Field Name (key, NOT your actual API-key)"
|
||||
placeholder="The Key to use as the header/query - NOT your actual API-key"
|
||||
type="name"
|
||||
id="standard-required"
|
||||
margin="normal"
|
||||
@@ -4441,7 +4463,7 @@ const AppCreator = (defaultprops) => {
|
||||
setUpdate(Math.random())
|
||||
}
|
||||
}}
|
||||
value={data.action_label}
|
||||
value={data?.action_label?.replace(" ", "_").toLowerCase()}
|
||||
style={{
|
||||
border: data.action_label === undefined || data.action_label === "No Label" ? "" : `2px solid ${bgColor}`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
@@ -4463,7 +4485,7 @@ const AppCreator = (defaultprops) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={labelindex}
|
||||
value={label}
|
||||
value={label.replace(" ", "_").toLowerCase()}
|
||||
style={{
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -3035,7 +3035,7 @@ const Apps = (props) => {
|
||||
height: "50px",
|
||||
}}
|
||||
variant="contained"
|
||||
disabled={openApi.length === 0 || appValidation.length > 0}
|
||||
disabled={openApi.length === 0 || appValidation.length > 0 || validation}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setOpenApiError("");
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useNavigate, Link, useParams } from "react-router-dom";
|
||||
//import { useAlert
|
||||
import { ToastContainer, toast } from "react-toastify"
|
||||
import Draggable from "react-draggable";
|
||||
import DashboardBarchart, { LoadStats } from '../components/DashboardBarchart.jsx';
|
||||
|
||||
import {
|
||||
Autocomplete,
|
||||
@@ -18,6 +19,8 @@ import {
|
||||
TextField,
|
||||
IconButton,
|
||||
Button,
|
||||
Select,
|
||||
MenuItem,
|
||||
Typography,
|
||||
Grid,
|
||||
Paper,
|
||||
@@ -146,28 +149,8 @@ const inputdata = [
|
||||
const LineChartWrapper = ({keys, height, width}) => {
|
||||
const [hovered, setHovered] = useState("");
|
||||
|
||||
//console.log("Date: ", new Date("2019-11-14T08:00:00.000Z"))
|
||||
console.log("Keys: ", keys)
|
||||
var inputdata = keys.data
|
||||
|
||||
/*
|
||||
const inputdata = [{
|
||||
"key": "Intel",
|
||||
"data": [
|
||||
{ key: new Date('11/22/2019'), data: 3, metadata: {color: "orange", "name": "Intel"}},
|
||||
{ key: new Date('11/24/2019'), data: 8, metadata: {color: "orange", "name": "Intel"}},
|
||||
{ key: new Date('11/29/2019'), data: 2, metadata: {color: "orange", "name": "Intel"}},
|
||||
]},
|
||||
{
|
||||
"key": "Popper",
|
||||
"data": [
|
||||
{ key: new Date('11/24/2019'), data: 9, },
|
||||
{ key: new Date('11/29/2019'), data: 3, },
|
||||
]
|
||||
}
|
||||
]
|
||||
*/
|
||||
|
||||
return (
|
||||
<div style={{}}>
|
||||
<Typography variant="h6" style={{marginBotton: 15}}>
|
||||
@@ -211,7 +194,6 @@ const LineChartWrapper = ({keys, height, width}) => {
|
||||
offset: '5px, 5px'
|
||||
}}
|
||||
content={(data, color) => {
|
||||
console.log("DATA: ", data)
|
||||
const name = data.metadata !== undefined && data.metadata.name !== undefined ? data.metadata.name : "No"
|
||||
|
||||
return (
|
||||
@@ -350,12 +332,35 @@ const Dashboard = (props) => {
|
||||
const [frameworkData, setFrameworkData] = useState(undefined);
|
||||
|
||||
const [widgetData, setWidgetData] = useState([]);
|
||||
const [newWidgetData, setNewWidgetData] = useState([]);
|
||||
|
||||
const [, setUpdate] = useState(0);
|
||||
|
||||
let navigate = useNavigate();
|
||||
const isCloud =
|
||||
window.location.host === "localhost:3002" ||
|
||||
window.location.host === "shuffler.io";
|
||||
|
||||
useEffect(() => {
|
||||
const widgetnames = ["app_executions_cloud"]
|
||||
for (let widgetkey in widgetnames) {
|
||||
const widgetName = widgetnames[widgetkey]
|
||||
|
||||
console.log("NAME: ", widgetName)
|
||||
|
||||
const resp = LoadStats(globalUrl, widgetName)
|
||||
if (resp !== undefined) {
|
||||
resp.then((data) => {
|
||||
console.log("Got data in parent: ", data)
|
||||
if (data === undefined) {
|
||||
} else {
|
||||
newWidgetData.push(data)
|
||||
setNewWidgetData(newWidgetData)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedUsecaseCategory.length === 0) {
|
||||
@@ -368,6 +373,7 @@ const Dashboard = (props) => {
|
||||
}
|
||||
}, [selectedUsecaseCategory])
|
||||
|
||||
|
||||
const checkSelectedParams = () => {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
||||
const params = Object.fromEntries(urlSearchParams.entries())
|
||||
@@ -409,23 +415,22 @@ const Dashboard = (props) => {
|
||||
}, [usecases])
|
||||
|
||||
const getWidget = (dashboard, widget) => {
|
||||
fetch(`${globalUrl}/api/v1/dashboards/${dashboard}/widgets/${widget}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for framework!");
|
||||
}
|
||||
fetch(`${globalUrl}/api/v1/dashboards/${dashboard}/widgets/${widget}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for framework!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
console.log("Resp: ", responseJson)
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
if (responseJson.reason !== undefined) {
|
||||
//toast("Failed loading: " + responseJson.reason)
|
||||
@@ -441,14 +446,12 @@ const Dashboard = (props) => {
|
||||
}
|
||||
|
||||
const foundWidget = widgetData.findIndex(data => data.title === widget)
|
||||
console.log("Found: ", foundWidget)
|
||||
if (foundWidget !== undefined && foundWidget !== null && foundWidget >= 0) {
|
||||
widgetData[foundWidget] = tmpdata
|
||||
} else {
|
||||
widgetData.push(tmpdata)
|
||||
}
|
||||
|
||||
console.log("Data: ", widgetData)
|
||||
setWidgetData(widgetData)
|
||||
}
|
||||
})
|
||||
@@ -480,7 +483,7 @@ const Dashboard = (props) => {
|
||||
useEffect(() => {
|
||||
getWidget("main", "Overall")
|
||||
getWidget("main", "Overall2")
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
const fetchdata = (stats_id) => {
|
||||
fetch(globalUrl + "/api/v1/stats/" + stats_id, {
|
||||
@@ -646,7 +649,6 @@ const Dashboard = (props) => {
|
||||
stats["workflow_executions"].data !== undefined
|
||||
) {
|
||||
setStatsRan(true);
|
||||
//console.log("NEW DATA?: ", stats)
|
||||
console.log("SET WORKFLOW: ", stats["workflow_executions"]);
|
||||
//var curday = startDate.getDate()
|
||||
|
||||
@@ -729,6 +731,100 @@ const Dashboard = (props) => {
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
const WidgetController = (props) => {
|
||||
const { data, index, availableStats, } = props
|
||||
const [hovering, setHovering] = useState(false)
|
||||
|
||||
const newname = data.key !== undefined ? data.key.replaceAll("_", " ") : ""
|
||||
|
||||
console.log("KEYDATA: ", data)
|
||||
|
||||
const loadNewStats = (newkey) => {
|
||||
const resp = LoadStats(globalUrl, newkey)
|
||||
if (resp !== undefined) {
|
||||
resp.then((respdata) => {
|
||||
if (respdata === undefined || respdata === null) {
|
||||
toast("Failed to laod data. Please try again, or contact support@shuffler.io if this persists.")
|
||||
} else {
|
||||
newWidgetData[index] = respdata
|
||||
setNewWidgetData(newWidgetData)
|
||||
|
||||
setUpdate(Math.random())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Draggable>
|
||||
<Paper
|
||||
style={{
|
||||
height: "100%", width: "100%", maxWidth: 500, margin: 15, padding: "15px 15px 15px 15px", textAlign: "left",
|
||||
backgroundColor: hovering ? theme.palette.inputColor : theme.palette.backgroundColor,
|
||||
cursor: hovering ? "pointer" : "default",
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setHovering(true)
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHovering(false)
|
||||
}}
|
||||
>
|
||||
<div style={{display: "flex", justifyContent: "space-between", alignItems: "center",}}>
|
||||
<Typography variant="h6">
|
||||
{newname}
|
||||
</Typography>
|
||||
|
||||
{data.available_keys === undefined || data.available_keys === null || data.available_keys.length === 0 ? null :
|
||||
<Select
|
||||
MenuProps={{
|
||||
disableScrollLock: true,
|
||||
}}
|
||||
labelId="Response Action"
|
||||
value={data.key}
|
||||
SelectDisplayProps={{
|
||||
style: {
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
onChange={(e) => {
|
||||
loadNewStats(e.target.value)
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
height: 40,
|
||||
maxWidth: 150,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
>
|
||||
{data.available_keys.map((foundKey, index) => {
|
||||
const parsedKeyName = foundKey.replaceAll("_", " ")
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
}}
|
||||
value={foundKey}
|
||||
>
|
||||
<em>{parsedKeyName}</em>
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
}
|
||||
</div>
|
||||
<DashboardBarchart
|
||||
timelineData={data}
|
||||
height={50}
|
||||
/>
|
||||
</Paper>
|
||||
</Draggable>
|
||||
)
|
||||
}
|
||||
|
||||
const data = (
|
||||
<div className="content" style={{width: 1000, margin: "auto", paddingBottom: 200, textAlign: "center",}}>
|
||||
<div style={{width: 500, margin: "auto"}}>
|
||||
@@ -739,12 +835,25 @@ const Dashboard = (props) => {
|
||||
: null}
|
||||
</div>
|
||||
|
||||
{widgetData === undefined || widgetData === null || widgetData === [] || widgetData.length === 0 ? null :
|
||||
{/*widgetData === undefined || widgetData === null || widgetData === [] || widgetData.length === 0 ? null :
|
||||
<Draggable>
|
||||
<Paper style={{height: 350, width: 500, padding: "15px 15px 15px 15px", }}>
|
||||
<LineChartWrapper keys={widgetData[0]} height={280} width={470} />
|
||||
</Paper>
|
||||
</Draggable>
|
||||
*/}
|
||||
|
||||
{newWidgetData === undefined || newWidgetData === null || newWidgetData === [] ? null :
|
||||
newWidgetData.map((data, index) => {
|
||||
|
||||
return (
|
||||
<WidgetController
|
||||
key={index}
|
||||
index={index}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from "react"
|
||||
import { toast } from 'react-toastify';
|
||||
import Markdown from 'react-markdown'
|
||||
import theme from '../theme.jsx';
|
||||
import ReactJson from "react-json-view";
|
||||
import ReactJson from "react-json-view-ssr";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { BrowserView, MobileView } from "react-device-detect";
|
||||
import { useParams, useNavigate, Link } from "react-router-dom";
|
||||
@@ -152,9 +152,19 @@ export const OuterLink = (props) => {
|
||||
|
||||
|
||||
export const Img = (props) => {
|
||||
var height = "auto"
|
||||
var width = 750
|
||||
if (props.height !== undefined && props.height !== null) {
|
||||
height = props.height
|
||||
}
|
||||
|
||||
if (props.width !== undefined && props.width !== null) {
|
||||
width = props.width
|
||||
}
|
||||
|
||||
return(
|
||||
<img
|
||||
style={{border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, width: 750, maxWidth: "100%", marginTop: 15, marginBottom: 15, }}
|
||||
style={{border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, width: width, maxWidth: width, margin: "auto", marginTop: 10, marginBottom: 10, }}
|
||||
alt={props.alt}
|
||||
src={props.src}
|
||||
/>
|
||||
|
||||
+367
-382
@@ -7,15 +7,17 @@ import { CodeHandler, Img, OuterLink, } from "../views/Docs.jsx";
|
||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||
import { validateJson, GetIconInfo } from "./Workflows.jsx";
|
||||
import EditWorkflow from "../components/EditWorkflow.jsx"
|
||||
import { ToastContainer, toast } from "react-toastify"
|
||||
import { toast } from "react-toastify"
|
||||
import { makeStyles } from '@mui/material/styles';
|
||||
import { useInterval } from "react-powerhooks";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import Markdown from "react-markdown";
|
||||
import theme from '../theme.jsx';
|
||||
import rehypeRaw from "rehype-raw";
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
Tooltip,
|
||||
Select,
|
||||
IconButton,
|
||||
CircularProgress,
|
||||
TextField,
|
||||
@@ -28,6 +30,7 @@ import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
MenuItem,
|
||||
} from '@mui/material';
|
||||
|
||||
import {
|
||||
@@ -45,7 +48,7 @@ const bodyDivStyle = {
|
||||
width: isMobile? "100%":"500px",
|
||||
position: "relative",
|
||||
|
||||
marginTop: 25,
|
||||
paddingBottom: 250,
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +74,7 @@ const RunWorkflow = (defaultprops) => {
|
||||
|
||||
const boxStyle = {
|
||||
color: "white",
|
||||
padding: 50,
|
||||
padding: "25px 50px 50px 50px",
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
marginBottom: 150,
|
||||
borderRadius: 25,
|
||||
@@ -102,6 +105,17 @@ const RunWorkflow = (defaultprops) => {
|
||||
const [executionInfo, setExecutionInfo] = useState("");
|
||||
|
||||
const handleValidateForm = (executionArgument) => {
|
||||
// Check if every field exists
|
||||
if (executionArgument === undefined || executionArgument === null) {
|
||||
return true
|
||||
}
|
||||
|
||||
for (var key in executionArgument) {
|
||||
if (executionArgument[key] === undefined || executionArgument[key] === null || executionArgument[key] === "") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -124,7 +138,10 @@ const RunWorkflow = (defaultprops) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
toast.success("Saved workflow")
|
||||
if (responseJson.success === false) {
|
||||
toast.error("Failed saving workflow. Please try again.")
|
||||
}
|
||||
//toast.success("Saved workflow")
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error("Save workflow error: " + error)
|
||||
@@ -197,178 +214,25 @@ const RunWorkflow = (defaultprops) => {
|
||||
*/}
|
||||
|
||||
{executionData.result !== undefined && executionData.result !== null && executionData.result.length > 0 ?
|
||||
<Typography variant="h6">
|
||||
{executionData.result}
|
||||
</Typography>
|
||||
<div style={{marginTop: 20, }}>
|
||||
<Divider />
|
||||
<Markdown
|
||||
components={{
|
||||
img: Img,
|
||||
code: CodeHandler,
|
||||
a: OuterLink,
|
||||
}}
|
||||
id="markdown_wrapper"
|
||||
escapeHtml={false}
|
||||
style={{
|
||||
maxWidth: "100%", minWidth: "100%",
|
||||
}}
|
||||
>
|
||||
{executionData.result}
|
||||
</Markdown>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
{/*executionData.results.map((data, index) => {
|
||||
if (executionData.results.length !== 1 && (data.status === "SKIPPED")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// FIXME: The latter replace doens't really work if ' is used in a string
|
||||
var showResult = data.result.trim();
|
||||
const validate = validateJson(showResult);
|
||||
|
||||
const curapp = apps.find((a) => a.name === data.action.app_name && a.app_version === data.action.app_version);
|
||||
const imgsize = 50;
|
||||
const statusColor = data.status === "FINISHED" || data.status === "SUCCESS" ? green : data.status === "ABORTED" || data.status === "FAILURE" ? "red" : yellow;
|
||||
|
||||
var imgSrc = curapp === undefined ? "" : curapp.large_image;
|
||||
if (
|
||||
imgSrc.length === 0 &&
|
||||
workflow.actions !== undefined &&
|
||||
workflow.actions !== null
|
||||
) {
|
||||
// Look for the node in the workflow
|
||||
const action = workflow.actions.find(
|
||||
(action) => action.id === data.action.id
|
||||
);
|
||||
if (action !== undefined && action !== null) {
|
||||
imgSrc = action.large_image;
|
||||
}
|
||||
}
|
||||
|
||||
var actionimg =
|
||||
curapp === null ? null : (
|
||||
<img
|
||||
alt={data.action.app_name}
|
||||
src={imgSrc}
|
||||
style={{
|
||||
marginRight: 20,
|
||||
width: imgsize,
|
||||
height: imgsize,
|
||||
border: `2px solid ${statusColor}`,
|
||||
borderRadius:
|
||||
executionData.start === data.action.id ? 25 : 5,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (data.action.app_name === "shuffle-subflow") {
|
||||
//const parsedImage = triggers[2].large_image;
|
||||
//actionimg = (
|
||||
// <img
|
||||
// alt={"Shuffle Subflow"}
|
||||
// src={parsedImage}
|
||||
// style={{
|
||||
// marginRight: 20,
|
||||
// width: imgsize,
|
||||
// height: imgsize,
|
||||
// border: `2px solid ${statusColor}`,
|
||||
// borderRadius:
|
||||
// executionData.start === data.action.id ? 25 : 5,
|
||||
// }}
|
||||
// />
|
||||
//);
|
||||
} else if (data.action.app_name === "User Input") {
|
||||
//actionimg = (
|
||||
// <img
|
||||
// alt={"Shuffle Subflow"}
|
||||
// src={triggers[3].large_image}
|
||||
// style={{
|
||||
// marginRight: 20,
|
||||
// width: imgsize,
|
||||
// height: imgsize,
|
||||
// border: `2px solid ${statusColor}`,
|
||||
// borderRadius:
|
||||
// executionData.start === data.action.id ? 25 : 5,
|
||||
// }}
|
||||
// />
|
||||
//);
|
||||
}
|
||||
|
||||
if (validate.valid && typeof validate.result === "string") {
|
||||
validate.result = JSON.parse(validate.result);
|
||||
}
|
||||
|
||||
if (validate.valid && typeof validate.result === "object") {
|
||||
if (
|
||||
validate.result.result !== undefined &&
|
||||
validate.result.result !== null
|
||||
) {
|
||||
try {
|
||||
validate.result.result = JSON.parse(validate.result.result);
|
||||
} catch (e) {
|
||||
//console.log("ERROR PARSING: ", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var similarActionsView = null
|
||||
if (data.similar_actions !== undefined && data.similar_actions !== null) {
|
||||
var minimumMatch = 85
|
||||
var matching_executions = []
|
||||
if (data.similar_actions !== undefined && data.similar_actions !== null) {
|
||||
for (let [k,kval] in Object.entries(data.similar_actions)){
|
||||
if (data.similar_actions.hasOwnProperty(k)) {
|
||||
if (data.similar_actions[k].similarity > minimumMatch) {
|
||||
matching_executions.push(data.similar_actions[k].execution_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matching_executions.length !== 0) {
|
||||
var parsed_url = matching_executions.join(",")
|
||||
|
||||
similarActionsView =
|
||||
<Tooltip
|
||||
color="primary"
|
||||
title="See executions with similar results (not identical)"
|
||||
placement="top"
|
||||
style={{ zIndex: 50000, marginLeft: 50, }}
|
||||
>
|
||||
<IconButton
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
height: 30,
|
||||
paddingLeft: 0,
|
||||
width: 30,
|
||||
}}
|
||||
onClick={() => {
|
||||
//navigate(`?execution_highlight=${parsed_url}`)
|
||||
}}
|
||||
>
|
||||
<PreviewIcon style={{ color: "rgba(255,255,255,0.5)" }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
marginBottom: 20,
|
||||
border:
|
||||
data.action.sub_action === true
|
||||
? "1px solid rgba(255,255,255,0.3)"
|
||||
: "1px solid rgba(255,255,255, 0.3)",
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
padding: "15px 10px 10px 10px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
onMouseOver={() => {
|
||||
}}
|
||||
onMouseOut={() => {
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: 5, display: "flex" }}>
|
||||
<Typography variant="body1">
|
||||
<b>Status </b>
|
||||
</Typography>
|
||||
<Typography variant="body1" color="textSecondary" style={{ marginRight: 15, }}>
|
||||
{data.status}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})*/}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -388,7 +252,7 @@ const RunWorkflow = (defaultprops) => {
|
||||
|
||||
var data = {
|
||||
"execution_argument": executionArgument,
|
||||
"execution_source": "questions",
|
||||
"execution_source": "form",
|
||||
}
|
||||
|
||||
if (workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0) {
|
||||
@@ -467,6 +331,10 @@ const RunWorkflow = (defaultprops) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
toast("This Form is not available for you to run. If you this is an error, contact support@shuffler.io with a link to this form")
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then(responseJson => {
|
||||
@@ -509,7 +377,9 @@ const RunWorkflow = (defaultprops) => {
|
||||
}
|
||||
|
||||
const getWorkflow = (workflow_id, selectedNode) => {
|
||||
fetch(globalUrl + "/api/v1/workflows/" + workflow_id, {
|
||||
const url = `${globalUrl}/api/v1/workflows/${workflow_id}`
|
||||
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -522,6 +392,10 @@ const RunWorkflow = (defaultprops) => {
|
||||
console.log("Status not 200 for workflows :O!");
|
||||
}
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
toast("This Form is not available to you. If you think this is an error, please contact support@shuffler.io with the URL.")
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
@@ -543,16 +417,24 @@ const RunWorkflow = (defaultprops) => {
|
||||
}
|
||||
|
||||
if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0) {
|
||||
|
||||
var newexec = {}
|
||||
for (let questionkey in responseJson.input_questions) {
|
||||
const question = responseJson.input_questions[questionkey]
|
||||
newexec[question.value] = ""
|
||||
|
||||
var multiChoiceOptions = question.value !== undefined && question.value !== null && question.value.length > 0 && question.value.includes(";") ? question.value.split(";") : []
|
||||
if (multiChoiceOptions.length > 1) {
|
||||
newexec[multiChoiceOptions[0]] = ""
|
||||
} else {
|
||||
newexec[question.value] = ""
|
||||
}
|
||||
}
|
||||
|
||||
setExecutionArgument(newexec)
|
||||
}
|
||||
|
||||
if (selectedNode !== undefined && selectedNode !== null && selectedNode.length > 0) {
|
||||
|
||||
var found = false
|
||||
for (var actionkey in responseJson.actions) {
|
||||
if (responseJson.actions[actionkey].id === selectedNode) {
|
||||
@@ -567,6 +449,7 @@ const RunWorkflow = (defaultprops) => {
|
||||
if (responseJson.triggers[triggerkey].id !== selectedNode) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
setFoundSourcenode(responseJson.triggers[triggerkey])
|
||||
|
||||
@@ -864,215 +747,277 @@ const RunWorkflow = (defaultprops) => {
|
||||
const basedata =
|
||||
<div style={bodyDivStyle}>
|
||||
<Paper style={boxStyle}>
|
||||
{workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ?
|
||||
<div style={{marginBottom: 20, }}>
|
||||
<Markdown
|
||||
components={{
|
||||
img: Img,
|
||||
code: CodeHandler,
|
||||
a: OuterLink,
|
||||
}}
|
||||
id="markdown_wrapper"
|
||||
escapeHtml={false}
|
||||
style={{
|
||||
maxWidth: "100%", minWidth: "100%",
|
||||
}}
|
||||
>
|
||||
{workflow.input_markdown}
|
||||
</Markdown>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
<form onSubmit={(e) => {onSubmit(e)}} style={{margin: "50px 0px 15px 0px",}}>
|
||||
{workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ? null :
|
||||
<div>
|
||||
<img
|
||||
alt={workflow.name}
|
||||
src={image}
|
||||
style={{
|
||||
marginRight: 20,
|
||||
width: 100,
|
||||
height: 100,
|
||||
border: `2px solid ${green}`,
|
||||
borderRadius: 50,
|
||||
position: "absolute",
|
||||
top: -50,
|
||||
left: 200,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography variant="h6" style={{marginBottom: 10, marginTop: 50, textAlign: "center", }}>
|
||||
{organization}
|
||||
{workflow.id === undefined || workflow.id === null ?
|
||||
<div style={{paddingTop: 150, marginTop: 150, width: 250, itemAlign: "center", textAlign: "center", margin: "auto", }}>
|
||||
<CircularProgress />
|
||||
<Typography variant="body1" style={{marginTop: 20, }}>
|
||||
Loding Form Details...
|
||||
</Typography>
|
||||
<Divider style={{marginTop: 20, marginBottom: 20, }}/>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
|
||||
{disabledButtons && message.length > 0 ? null :
|
||||
<Typography color="textSecondary" style={{textAlign: "center", }}>
|
||||
{message}
|
||||
{workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ?
|
||||
<div style={{marginBottom: 20, }}>
|
||||
<Markdown
|
||||
components={{
|
||||
img: Img,
|
||||
code: CodeHandler,
|
||||
a: OuterLink,
|
||||
}}
|
||||
id="markdown_wrapper"
|
||||
escapeHtml={false}
|
||||
style={{
|
||||
maxWidth: "100%", minWidth: "100%",
|
||||
}}
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
>
|
||||
{workflow.input_markdown}
|
||||
</Markdown>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
<form onSubmit={(e) => {onSubmit(e)}} style={{margin: "25px 0px 15px 0px",}}>
|
||||
{workflow.input_markdown !== undefined && workflow.input_markdown !== null && workflow.input_markdown.length > 0 ? null :
|
||||
<div>
|
||||
<img
|
||||
alt={workflow.name}
|
||||
src={image}
|
||||
style={{
|
||||
marginRight: 20,
|
||||
width: 100,
|
||||
height: 100,
|
||||
border: `2px solid ${green}`,
|
||||
borderRadius: 50,
|
||||
position: "absolute",
|
||||
top: -50,
|
||||
left: 200,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography variant="h6" style={{marginBottom: 10, marginTop: 50, textAlign: "center", }}>
|
||||
{organization}
|
||||
</Typography>
|
||||
}
|
||||
<Divider style={{marginTop: 20, marginBottom: 20, }}/>
|
||||
|
||||
{answer !== undefined && answer !== null ? null :
|
||||
<Typography variant="h6" style={{marginBottom: 15, textAlign: "center", }}><b>{workflow.name}</b></Typography>
|
||||
}
|
||||
|
||||
{workflowQuestion.length > 0 ?
|
||||
<div style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
padding: 20,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
marginBottom: 35,
|
||||
marginTop: 30,
|
||||
}}>
|
||||
<Typography variant="body1" style={{ marginRight: 15, textAlign: "center", whiteSpace: "pre-line", }}>
|
||||
{workflowQuestion}
|
||||
{disabledButtons && message.length > 0 ? null :
|
||||
<Typography color="textSecondary" style={{textAlign: "center", }}>
|
||||
{message}
|
||||
</Typography>
|
||||
</div>
|
||||
: null}
|
||||
}
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
{workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
|
||||
<div style={{marginBottom: 5, }}>
|
||||
{workflow.input_questions.map((question, index) => {
|
||||
return (
|
||||
<div style={{marginBottom: 5}}>
|
||||
{question.name}
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
|
||||
label={question.value}
|
||||
required
|
||||
{answer !== undefined && answer !== null ? null :
|
||||
<Typography variant="h6" style={{marginBottom: 15, textAlign: "center", }}><b>{workflow.name}</b></Typography>
|
||||
}
|
||||
|
||||
disabled={disabledButtons}
|
||||
fullWidth={true}
|
||||
placeholder=""
|
||||
id="emailfield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onBlur={(e) => {
|
||||
//setExecutionArgument(e.target.value)
|
||||
executionArgument[question.value] = e.target.value
|
||||
setUpdate(Math.random())
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
:
|
||||
answer !== undefined && answer !== null ? null :
|
||||
<span>
|
||||
Runtime Argument
|
||||
<div style={{marginBottom: 5}}>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
|
||||
multiLine
|
||||
maxRows={2}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
fullWidth={true}
|
||||
placeholder=""
|
||||
id="emailfield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(e) => {
|
||||
setExecutionArgument(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
}
|
||||
|
||||
{executionRunning ?
|
||||
<span style={{width: 50, height: 50, margin: "auto", alignItems: "center", justifyContent: "center", textAlign: "center", }}>
|
||||
<CircularProgress style={{marginTop: 20, marginBottom: 20, marginLeft: 185, }}/>
|
||||
|
||||
{executionData.status !== undefined && executionData.status !== null && executionData.status !== "" ?
|
||||
<Typography variant="body2" style={{margin: "auto", marginTop: 20, marginBottom: 20, textAlign: "center", alignItem: "center", }} color="textSecondary">
|
||||
Status: {executionData.status}
|
||||
</Typography>
|
||||
{workflowQuestion.length > 0 ?
|
||||
<div style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
padding: 20,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
marginBottom: 35,
|
||||
marginTop: 30,
|
||||
}}>
|
||||
<Typography variant="body1" style={{ marginRight: 15, textAlign: "center", whiteSpace: "pre-line", }}>
|
||||
{workflowQuestion}
|
||||
</Typography>
|
||||
</div>
|
||||
: null}
|
||||
</span>
|
||||
:
|
||||
((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) ?
|
||||
<span style={{marginTop: 20, }}>
|
||||
|
||||
{disabledButtons && message.length > 0 ?
|
||||
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
|
||||
{message}. You may close this window.
|
||||
</Typography>
|
||||
:
|
||||
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
|
||||
{disabledButtons ? "Answered. You may close this window." : ""}
|
||||
</Typography>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
{workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
|
||||
<div style={{marginBottom: 5, }}>
|
||||
{workflow.input_questions.map((question, index) => {
|
||||
|
||||
{disabledButtons ? null :
|
||||
<Typography variant="body2" color="textSecondary" style={{textAlign: "center", marginTop: 10, }}>
|
||||
What do you want to do?
|
||||
</Typography>
|
||||
}
|
||||
<div fullWidth style={{width: "100%", marginTop: 10, marginBottom: 10, display: "flex", }}>
|
||||
<Button fullWidth id="continue_execution" variant="contained" disabled={disabledButtons} color="primary" style={{flex: 1,}} onClick={() => {
|
||||
onSubmit(null, execution_id, authorization, true)
|
||||
// Multiple choice checks for semicolon-splits
|
||||
var multiChoiceOptions = question.value !== undefined && question.value !== null && question.value.length > 0 && question.value.includes(";") ? question.value.split(";") : []
|
||||
// Remove empty keys from array
|
||||
multiChoiceOptions = multiChoiceOptions.filter(function(e) { return e !== "" })
|
||||
if (multiChoiceOptions.length > 1 && (executionArgument[multiChoiceOptions[0]] === undefined || executionArgument[multiChoiceOptions[0]] === null || executionArgument[multiChoiceOptions[0]] === "")) {
|
||||
// Set the first item to be default
|
||||
executionArgument[multiChoiceOptions[0]] = multiChoiceOptions[1]
|
||||
}
|
||||
|
||||
setButtonClicked("FINISHED")
|
||||
setExecutionData({
|
||||
status: "FINISHED",
|
||||
})
|
||||
}}>Continue</Button>
|
||||
<Typography variant="body1" style={{marginLeft: 3, marginRight: 3, marginTop: 3, }}>
|
||||
or
|
||||
</Typography>
|
||||
<Button fullWidth id="abort_execution" variant="contained" color="primary" disabled={disabledButtons} style={{ flex: 1, }} onClick={() => {
|
||||
onSubmit(null, execution_id, authorization, false)
|
||||
return (
|
||||
<div style={{marginBottom: 10}} key={index}>
|
||||
{question.name}
|
||||
|
||||
setButtonClicked("ABORTED")
|
||||
setExecutionData({
|
||||
status: "ABORTED",
|
||||
})
|
||||
}}>Stop</Button>
|
||||
{multiChoiceOptions.length > 1 ?
|
||||
<Select
|
||||
fullWidth
|
||||
required
|
||||
label={multiChoiceOptions[0]}
|
||||
value={executionArgument[multiChoiceOptions[0]]}
|
||||
onChange={(e) => {
|
||||
const curQuestion = multiChoiceOptions[0]
|
||||
executionArgument[curQuestion] = e.target.value
|
||||
setUpdate(Math.random())
|
||||
}}
|
||||
>
|
||||
|
||||
{multiChoiceOptions.map((option, menuIndex) => {
|
||||
if (index === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={menuIndex}
|
||||
value={option}
|
||||
>
|
||||
{option}
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
|
||||
|
||||
</Select>
|
||||
:
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
|
||||
label={question.value}
|
||||
required
|
||||
|
||||
disabled={disabledButtons}
|
||||
fullWidth={true}
|
||||
placeholder=""
|
||||
id="emailfield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(e) => {
|
||||
executionArgument[question.value] = e.target.value
|
||||
setExecutionArgument(executionArgument)
|
||||
setUpdate(Math.random())
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
:
|
||||
answer !== undefined && answer !== null ? null :
|
||||
<span>
|
||||
Runtime Argument
|
||||
<div style={{marginBottom: 5}}>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
|
||||
multiLine
|
||||
maxRows={2}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
fullWidth={true}
|
||||
placeholder=""
|
||||
id="emailfield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(e) => {
|
||||
setExecutionArgument(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
:
|
||||
<div style={{display: "flex", marginTop: "15px"}}>
|
||||
<Button variant="contained" type="submit" color="primary" fullWidth disabled={!handleValidateForm(executionArgument) || executionLoading}>
|
||||
{executionLoading ?
|
||||
<CircularProgress color="secondary" style={{color: "white",}} /> : "Submit"}
|
||||
</Button>
|
||||
}
|
||||
|
||||
{executionRunning ?
|
||||
<span style={{width: 50, height: 50, margin: "auto", alignItems: "center", justifyContent: "center", textAlign: "center", }}>
|
||||
<CircularProgress style={{marginTop: 20, marginBottom: 20, marginLeft: 185, }}/>
|
||||
|
||||
{executionData.status !== undefined && executionData.status !== null && executionData.status !== "" ?
|
||||
<Typography variant="body2" style={{margin: "auto", marginTop: 20, marginBottom: 20, textAlign: "center", alignItem: "center", }} color="textSecondary">
|
||||
Status: {executionData.status}
|
||||
</Typography>
|
||||
: null}
|
||||
</span>
|
||||
:
|
||||
((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) ?
|
||||
<span style={{marginTop: 20, }}>
|
||||
|
||||
{disabledButtons && message.length > 0 ?
|
||||
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
|
||||
{message}. You may close this window.
|
||||
</Typography>
|
||||
:
|
||||
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
|
||||
{disabledButtons ? "Answered. You may close this window." : ""}
|
||||
</Typography>
|
||||
}
|
||||
|
||||
{disabledButtons ? null :
|
||||
<Typography variant="body2" color="textSecondary" style={{textAlign: "center", marginTop: 10, }}>
|
||||
What do you want to do?
|
||||
</Typography>
|
||||
}
|
||||
<div fullWidth style={{width: "100%", marginTop: 10, marginBottom: 10, display: "flex", }}>
|
||||
<Button fullWidth id="continue_execution" variant="contained" disabled={disabledButtons} color="primary" style={{flex: 1,}} onClick={() => {
|
||||
onSubmit(null, execution_id, authorization, true)
|
||||
|
||||
setButtonClicked("FINISHED")
|
||||
setExecutionData({
|
||||
status: "FINISHED",
|
||||
})
|
||||
}}>Continue</Button>
|
||||
<Typography variant="body1" style={{marginLeft: 3, marginRight: 3, marginTop: 3, }}>
|
||||
or
|
||||
</Typography>
|
||||
<Button fullWidth id="abort_execution" variant="contained" color="primary" disabled={disabledButtons} style={{ flex: 1, }} onClick={() => {
|
||||
onSubmit(null, execution_id, authorization, false)
|
||||
|
||||
setButtonClicked("ABORTED")
|
||||
setExecutionData({
|
||||
status: "ABORTED",
|
||||
})
|
||||
}}>Stop</Button>
|
||||
</div>
|
||||
</span>
|
||||
:
|
||||
<div style={{display: "flex", marginTop: "15px"}}>
|
||||
<Button
|
||||
variant={executionData.result !== undefined && executionData.result !== null && executionData.result.length > 0 ? "outlined" : "contained"}
|
||||
type="submit"
|
||||
color="primary"
|
||||
fullWidth
|
||||
disabled={!handleValidateForm(executionArgument) || executionLoading}
|
||||
>
|
||||
{executionLoading ?
|
||||
<CircularProgress color="secondary" style={{color: "white",}} /> : "Submit"}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
|
||||
{/*buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ?
|
||||
<img id="finalize_gif" src="/images/finalize.gif" alt="finalize workflow animation" style={{width: 150, marginLeft: 125, borderRadius: theme.palette.borderRadius, }}
|
||||
onLoad={() => {
|
||||
console.log("Img loaded.")
|
||||
setTimeout(() => {
|
||||
console.log("Img closing.")
|
||||
setButtonClicked("finished")
|
||||
|
||||
}, 1250)
|
||||
|
||||
}}
|
||||
/>
|
||||
: null*/}
|
||||
|
||||
<div style={{marginTop: "10px"}}>
|
||||
{executionInfo}
|
||||
</div>
|
||||
}
|
||||
|
||||
{/*buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ?
|
||||
<img id="finalize_gif" src="/images/finalize.gif" alt="finalize workflow animation" style={{width: 150, marginLeft: 125, borderRadius: theme.palette.borderRadius, }}
|
||||
onLoad={() => {
|
||||
console.log("Img loaded.")
|
||||
setTimeout(() => {
|
||||
console.log("Img closing.")
|
||||
setButtonClicked("finished")
|
||||
|
||||
}, 1250)
|
||||
|
||||
}}
|
||||
/>
|
||||
: null*/}
|
||||
|
||||
<div style={{marginTop: "10px"}}>
|
||||
{executionInfo}
|
||||
</div>
|
||||
|
||||
{answer !== undefined && answer !== null ? null :
|
||||
<ShowExecutionResults executionData={executionData} />
|
||||
}
|
||||
</form>
|
||||
{answer !== undefined && answer !== null ? null :
|
||||
<ShowExecutionResults executionData={executionData} />
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</Paper>
|
||||
</div>
|
||||
|
||||
@@ -1092,6 +1037,7 @@ const RunWorkflow = (defaultprops) => {
|
||||
usecases={undefined}
|
||||
|
||||
expanded={true}
|
||||
scrollTo={"input_markdown"}
|
||||
/>
|
||||
: null}
|
||||
|
||||
@@ -1103,20 +1049,59 @@ const RunWorkflow = (defaultprops) => {
|
||||
PaperProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
minWidth: isMobile ? "90%" : 400,
|
||||
maxWidth: isMobile ? "90%" : 400,
|
||||
minHeight: 350,
|
||||
paddingTop: 25,
|
||||
paddingLeft: 50,
|
||||
minWidth: isMobile ? "90%" : 500,
|
||||
maxWidth: isMobile ? "90%" : 500,
|
||||
minHeight: 400,
|
||||
maxHeight: 400,
|
||||
padding: 25,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
//minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
|
||||
//maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle style={{padding: 30, paddingBottom: 0, zIndex: 1000,}}>
|
||||
Share Landingpage
|
||||
<DialogTitle style={{padding: 30, paddingLeft: 20, paddingBottom: 0, zIndex: 1000,}}>
|
||||
Form Sharing Options for '{workflow.name}'
|
||||
</DialogTitle>
|
||||
<DialogContent style={{paddingTop: 10, display: "flex", minHeight: 300, zIndex: 1001, paddingBottom: 200, }}>
|
||||
<DialogContent style={{marginTop: 20,}}>
|
||||
<Typography variant="body1">
|
||||
<b>General Access</b>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Form sharing and workflow sharing are not the same. By sharing a form, you are enabling anyone with the link to fill out the form AND run the workflow. They will NOT have access to seeing workflow details. By default, anyone with access to an organization can use a form.
|
||||
</Typography>
|
||||
|
||||
<div />
|
||||
{workflow !== undefined && workflow !== null && workflow.sharing !== undefined && workflow.sharing !== null ?
|
||||
<Select
|
||||
fullWidth
|
||||
style={{marginTop: 25, }}
|
||||
value={workflow.sharing === "" ? "private" : workflow.sharing}
|
||||
onChange={(e) => {
|
||||
console.log("SHARING: ", e.target.value)
|
||||
|
||||
workflow.sharing = e.target.value
|
||||
setWorkflow(workflow)
|
||||
saveWorkflow(workflow)
|
||||
setUpdate(Math.random())
|
||||
|
||||
toast("Form sharing updated.")
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
value={"private"}
|
||||
>
|
||||
Organization only
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem
|
||||
value={"form"}
|
||||
>
|
||||
Anyone with the link
|
||||
</MenuItem>
|
||||
</Select>
|
||||
: null}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -1135,19 +1120,19 @@ const RunWorkflow = (defaultprops) => {
|
||||
<Button
|
||||
variant={"outlined"}
|
||||
color={"secondary"}
|
||||
disabled
|
||||
onClick={() => {
|
||||
setSharingOpen(true)
|
||||
}}
|
||||
>
|
||||
Manage Sharing
|
||||
Share Form
|
||||
</Button>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
{basedata}
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
<div style={{width: 100, itemAlign: "center", textAlign: "center", margin: "auto", }}>
|
||||
<CircularProgress />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ const SetAuthentication = (props) => {
|
||||
const [loadFail, setLoadFail] = useState("");
|
||||
const [appAuthentication, setAppAuthentication] = React.useState([]);
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
//const alert = useAlert();
|
||||
|
||||
const parseIncomingOpenapiData = (data) => {
|
||||
@@ -142,8 +142,8 @@ const SetAuthentication = (props) => {
|
||||
// 3. Help them set info for the app
|
||||
// Make sure to test both private and public apps
|
||||
|
||||
const appname = app.name !== undefined ? app.name : "";
|
||||
const appLink = "/apps/" + app.id || "";
|
||||
const appname = app.name !== undefined ? app.name : ""
|
||||
const appLink = "/apps/" + app.id || ""
|
||||
|
||||
console.log("App: ", app)
|
||||
|
||||
@@ -154,21 +154,16 @@ const SetAuthentication = (props) => {
|
||||
:
|
||||
<><div>
|
||||
<Typography variant="h4" style={{ marginBottom: 20, }}>
|
||||
A Shuffle Organization has invited you to: Configure <a href={appLink} target="_blank" style={{ color: '#FF8444', textDecoration: 'none' }}>{appname}</a> Authentication
|
||||
You are invited to: Configure <a href={appLink} target="_blank" style={{ color: '#FF8444', textDecoration: 'none' }}>{appname}</a> Authentication
|
||||
</Typography>
|
||||
|
||||
{/* What does this mean box */}
|
||||
<Typography variant="h6" style={{ marginBottom: 20, }}>
|
||||
What does this mean?
|
||||
</Typography>
|
||||
<Typography variant="body1" style={{ marginBottom: 20, }}>
|
||||
<Typography variant="body1" style={{ marginBottom: 20, color: "rgba(255,255,255,0.4)", }}>
|
||||
A Shuffle Organization has invited you to configure authentication for this app so that they can use this authentication in one of their workflows.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6">
|
||||
Authenticate Here:
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body1" style={{ marginBottom: 20, }}>
|
||||
{app.authentication === undefined || app.authentication === null || app.authentication.length === 0 ?
|
||||
null
|
||||
@@ -195,7 +190,7 @@ const SetAuthentication = (props) => {
|
||||
appAuthentication={appAuthentication} />}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6" style={{ marginBottom: 20, }}>
|
||||
<Typography variant="h6" style={{ marginTop: 50, marginBottom: 20, }}>
|
||||
What can they do with this?
|
||||
</Typography>
|
||||
|
||||
@@ -203,12 +198,11 @@ const SetAuthentication = (props) => {
|
||||
You can check the actions they want to use <a href={appLink} target="_blank" style={{ color: '#FF8444', textDecoration: 'none' }}>here</a>.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body1" style={{ marginBottom: 20, }}>
|
||||
{/* Add a box below */}
|
||||
<Typography variant="body1" style={{ marginBottom: 20, color: "rgba(255,255,255,0.4)",}}>
|
||||
<div className="collapsible-container">
|
||||
<div className="collapsible-list">
|
||||
{app.actions?.map((item, index) => (
|
||||
<div key={index} className="collapsible-item">
|
||||
<div key={index} className="collapsible-item" style={{cursor: "pointer", }}>
|
||||
<div className="collapsible-label" onClick={() => handleToggle(index)}>
|
||||
{item.label}
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@ const Welcome = (props) => {
|
||||
}
|
||||
}, [activeStep])
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||
const [steps, setSteps] = useState([
|
||||
"Help us get to know you",
|
||||
"Find your Apps",
|
||||
|
||||
@@ -3528,7 +3528,7 @@ const Workflows = (props) => {
|
||||
var workflowDelay = -150
|
||||
var appDelay = -75
|
||||
|
||||
const foundPriority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true)
|
||||
const foundPriority = userdata === undefined || userdata === null || userdata.priorities === undefined || userdata.priorities === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true)
|
||||
return (
|
||||
<div style={viewStyle}>
|
||||
<div style={workflowViewStyle}>
|
||||
|
||||
Reference in New Issue
Block a user