Merged upstream

This commit is contained in:
monilprajapati
2024-06-25 17:47:29 +05:30
14 changed files with 854 additions and 386 deletions
+2 -2
View File
@@ -1775,7 +1775,7 @@ const Billing = (props) => {
</div>
</div>
<Button
variant="contained"
variant="outlined"
color="primary"
style={{ marginTop: 15, textTransform: 'none' }}
onClick={addAlertThreshold}
@@ -1783,7 +1783,7 @@ const Billing = (props) => {
Add Threshold
</Button>
<Button
variant="contained"
variant="outlined"
color="primary"
style={{ marginTop: 15, textTransform: 'none', marginLeft: 20 }}
onClick={() => { HandleEditOrgForAlertThreshold(selectedOrganization.id) }}
+46 -33
View File
@@ -338,26 +338,6 @@ const EditWorkflow = (props) => {
autoFocus
fullWidth
/>
<div style={{display: "flex", }}>
<TextField
onBlur={(event) => {
setDescription(event.target.value)
}}
InputProps={{
style: {
color: "white",
},
}}
maxRows={4}
color="primary"
defaultValue={innerWorkflow.description}
placeholder="Description"
multiline
label="Description"
margin="dense"
fullWidth
/>
</div>
<div style={{display: "flex", marginTop: 10, }}>
{usecases !== null && usecases !== undefined && usecases.length > 0 ?
<FormControl style={{flex: 1, marginRight: 5,}}>
@@ -425,27 +405,56 @@ const EditWorkflow = (props) => {
fullWidth
value={newWorkflowTags}
onChange={(chip) => {
console.log("Chip: ", chip)
//newWorkflowTags.push(chip);
setNewWorkflowTags(chip);
}}
onBlur={(event) => {
if (event.target.value.length === 0) {
return
}
if (newWorkflowTags.includes(event.target.value)) {
return
}
newWorkflowTags.push(event.target.value)
setNewWorkflowTags(newWorkflowTags)
setUpdate(Math.random())
}}
onAdd={(chip) => {
newWorkflowTags.push(chip);
setNewWorkflowTags(newWorkflowTags);
newWorkflowTags.push(chip)
setNewWorkflowTags(newWorkflowTags)
}}
onDelete={(chip, index) => {
console.log("Deleting: ", chip, index)
newWorkflowTags.splice(index, 1);
setNewWorkflowTags(newWorkflowTags);
setUpdate(Math.random());
newWorkflowTags.splice(index, 1)
setNewWorkflowTags(newWorkflowTags)
setUpdate(Math.random())
}}
/>
</div>
{showMoreClicked === true ?
<span style={{marginTop: 25, }}>
<div style={{marginTop: 50, }}>
<TextField
onBlur={(event) => {
setDescription(event.target.value)
}}
InputProps={{
style: {
color: "white",
},
}}
multiLine
rows={3}
color="primary"
defaultValue={innerWorkflow.description}
placeholder="Description"
multiline
label="Description"
margin="dense"
fullWidth
/>
<div style={{display: "flex"}}>
<FormControl style={{marginTop: 15, }}>
@@ -564,6 +573,8 @@ const EditWorkflow = (props) => {
fullWidth
/>
<Divider style={{marginTop: 20, marginBottom: 20, }} />
<Typography variant="body1" style={{marginTop: 50, }}>
MSSP Suborg Distribution (beta - contact support@shuffler.io for more info)
</Typography>
@@ -669,12 +680,13 @@ const EditWorkflow = (props) => {
</Link>
}
<Divider style={{marginTop: 20, marginBottom: 20, }} />
<Typography variant="h6" style={{marginTop: 50, }}>
Input fields
</Typography>
<Typography variant="body1" 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>.
<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>
@@ -781,6 +793,7 @@ const EditWorkflow = (props) => {
<AddIcon style={{}} />
</Button>
<Divider style={{marginTop: 20, marginBottom: 20, }} />
<Typography variant="body1" style={{marginTop: 50, }}>
Git Backup Repository
@@ -931,7 +944,7 @@ const EditWorkflow = (props) => {
</span>
</Grid>
</Grid>
</span>
</div>
: null}
+2 -1
View File
@@ -554,7 +554,8 @@ const Header = (props) => {
aria-haspopup="true"
onClick={(event) => { }}
>
<svg viewBox="0 0 256 199" width="256" height="199" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" style={{height: 30, width: 30, }}><path d="M216.856 16.597A208.502 208.502 0 0 0 164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046-19.692-2.961-39.203-2.961-58.533 0-1.832-4.4-4.55-9.933-6.846-14.046a207.809 207.809 0 0 0-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161.094 161.094 0 0 0 79.735 175.3a136.413 136.413 0 0 1-21.846-10.632 108.636 108.636 0 0 0 5.356-4.237c42.122 19.702 87.89 19.702 129.51 0a131.66 131.66 0 0 0 5.355 4.237 136.07 136.07 0 0 1-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36ZM85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2c12.867 0 23.236 11.804 23.015 26.2.02 14.375-10.148 26.18-23.015 26.18Zm85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18Z" fill="#5865F2"/></svg>
<svg viewBox="0 0 256 199" width="256" height="199" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" style={{height: 30, width: 30, }}><path d="M216.856 16.597A208.502 208.502 0 0 0 164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046-19.692-2.961-39.203-2.961-58.533 0-1.832-4.4-4.55-9.933-6.846-14.046a207.809 207.809 0 0 0-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161.094 161.094 0 0 0 79.735 175.3a136.413 136.413 0 0 1-21.846-10.632 108.636 108.636 0 0 0 5.356-4.237c42.122 19.702 87.89 19.702 129.51 0a131.66 131.66 0 0 0 5.355 4.237 136.07 136.07 0 0 1-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36ZM85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2c12.867 0 23.236 11.804 23.015 26.2.02 14.375-10.148 26.18-23.015 26.18Zm85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18Z" fill="#dadae1"/></svg>
{/*#f865f2*/}
</IconButton>
</Tooltip>
</a>
+6 -12
View File
@@ -471,32 +471,26 @@ const AuthenticationOauth2 = (props) => {
state += `%26refresh_uri%3d${authentication_url}`;
}
// No prompt forcing
//var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=login&scope=${resources}&state=${state}&access_type=offline`;
// FIXME: Should this be =consent?
var defaultPrompt = "login"
if (prompt !== undefined && prompt !== null && prompt.length > 0) {
defaultPrompt = prompt
defaultPrompt = prompt
}
var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=${defaultPrompt}&scope=${resources}&state=${state}&access_type=offline`;
if (admin_consent === true) {
console.log("Running Oauth2 WITH admin consent")
//url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`;
url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`;
}
// Force new consent
console.log("OAUTH2 URL: ", url)
// Force new consent
//const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`;
// Admin consent
// Admin consent
//const url = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${client_id}&scope=AaaServer.profile.Read&redirect_uri=${redirectUri}&prompt=consent`
// &resource=https%3A%2F%2Fgraph.microsoft.com&
// FIXME: Awful, but works for prototyping
// How can we get a callback properly realtime?
// How can we properly try-catch without breaks on error?
try {
var newwin = window.open(url, "", "width=582,height=700");
//console.log(newwin)
+24 -6
View File
@@ -823,7 +823,7 @@ const ParsedAction = (props) => {
if (selectedAction.app_name === "Shuffle Tools" && selectedAction.name === "filter_list" && data.name === "input_list") {
//console.log("FILTER LIST!: ", event, count, data)
const parsedvalue = event.target.value
if (parsedvalue.includes("#")) {
if (parsedvalue.includes(".#")) {
const splitparsed = parsedvalue.split(".#.")
//console.log("Cant contain #: ", splitparsed)
if (splitparsed.length > 1) {
@@ -832,14 +832,30 @@ const ParsedAction = (props) => {
selectedActionParameters[count].value = splitparsed[0]
selectedAction.parameters[count].value = splitparsed[0]
selectedActionParameters[1].value = splitparsed[1]
selectedAction.parameters[1].value = splitparsed[1]
forceUpdate = true
selectedActionParameters[1].value = splitparsed[1]
selectedAction.parameters[1].value = splitparsed[1]
} else {
// Remove .# and after
const splitparsed = parsedvalue.split(".#")
data.value = splitparsed[0]
selectedActionParameters[0].value = splitparsed[0]
selectedAction.parameters[0].value = splitparsed[0]
selectedActionParameters[1].value = ""
selectedAction.parameters[1].value = ""
toast.warn("No value found in the list. Please select an item in the list to filter based on.")
}
forceUpdate = true
selectedActionParameters[0].autocompleted = true
selectedAction.parameters[0].autocompleted = true
selectedActionParameters[1].autocompleted = true
selectedAction.parameters[1].autocompleted = true
}
}
setSelectedAction(selectedAction);
setSelectedAction(selectedAction);
if (forceUpdate || viewForceUpdate === true) {
setUpdate(Math.random())
}
@@ -910,6 +926,7 @@ const ParsedAction = (props) => {
}
}
// bad detection mechanism probably
if (event.target.value[event.target.value.length-1] === "." && actionlist.length > 0) {
console.log("GET THE LAST ARGUMENT FOR NODE!")
@@ -3839,7 +3856,8 @@ const ParsedAction = (props) => {
newname = newname.slice(0, newname.length-5)
}
selectedActionParameters[count].value += `{{ $${innerdata.name}.${newname} | size }}`
//selectedActionParameters[count].value += `{{ $${innerdata.name}.${newname} | size }}`
selectedActionParameters[count].value += `$${innerdata.name}.${newname}`
selectedAction.parameters[count].value = selectedActionParameters[count].value;
setSelectedAction(selectedAction);
setUpdate(Math.random());
+9 -3
View File
@@ -1568,6 +1568,11 @@ If you're interested, please let me know a time that works for you, or set up a
responseJson.lead_info !== null
) {
var leads = [];
console.log("LEADINFO: ", responseJson.lead_info)
if (responseJson.lead_info.testing_shuffle) {
leads.push("testing shuffle");
}
if (responseJson.lead_info.contacted) {
leads.push("contacted");
}
@@ -3572,7 +3577,7 @@ If you're interested, please let me know a time that works for you, or set up a
<FormControl sx={{ m: 1, width: 300 }} style={{}}>
<InputLabel id="">Status</InputLabel>
<Select
style={{ minWidth: 150, maxWidth: 150 }}
style={{ minWidth: 150, maxWidth: 150, }}
labelId="multiselect-status"
id="multiselect-status"
multiple
@@ -3583,9 +3588,10 @@ If you're interested, please let me know a time that works for you, or set up a
MenuProps={MenuProps}
>
{[
"testing shuffle",
"contacted",
"lead",
"demo done",
//"demo done",
"pov",
"customer",
"open source",
@@ -3745,7 +3751,7 @@ If you're interested, please let me know a time that works for you, or set up a
aria-label="disabled tabs example"
>
<Tab label=<span>Edit Details</span> />
<Tab label=<span>Org Limits & Cloud Sync</span> />
<Tab label=<span>Limits & Cloud Sync</span> />
<Tab label=<span>Priorities</span> />
<Tab label=<span>Billing & Stats</span> />
<Tab disabled={!isCloud} label=<span>Branding (Beta)</span> />
+189 -93
View File
@@ -1072,22 +1072,20 @@ const releaseToConnectLabel = "Release to Connect"
// User Input & Subflow nodes
if (param.name === "workflow" || param.name === "subflow") {
const paramIndex = param.name === "workflow" ? 0 : 5
console.log("Current vs new: ", workflow.triggers[trigger_index].parameters[paramIndex].value, subworkflow.id)
if (workflow.triggers[trigger_index].parameters[paramIndex].value !== subworkflow.id) {
if (param.value === workflow.id) {
setSubworkflow(workflow);
baseSubflow = workflow
} else {
const sub = responseJson.find((data) => data.id === param.value);
if (sub !== undefined && subworkflow.id !== sub.id) {
baseSubflow = sub
setSubworkflow(sub);
}
}
}
}
const paramIndex = param.name === "workflow" ? 0 : 5
if (workflow.triggers[trigger_index].parameters[paramIndex].value !== subworkflow.id) {
if (param.value === workflow.id) {
setSubworkflow(workflow);
baseSubflow = workflow
} else {
const sub = responseJson.find((data) => data.id === param.value);
if (sub !== undefined && subworkflow.id !== sub.id) {
baseSubflow = sub
setSubworkflow(sub);
}
}
}
}
if (param.name === "startnode" && param.value !== undefined && param.value !== null) {
@@ -3255,7 +3253,6 @@ const releaseToConnectLabel = "Release to Connect"
const getChildWorkflows = (parentWorkflowId) => {
if (originalWorkflow.suborg_distribution === undefined || originalWorkflow.suborg_distribution === null || originalWorkflow.suborg_distribution.length === 0) {
console.log("No suborg distribution")
return
}
@@ -5300,7 +5297,59 @@ const releaseToConnectLabel = "Release to Connect"
}
setTimeout(() => {
setSelectedTriggerIndex(trigger_index);
if (trigger_index !== -1) {
const trigger = workflow.triggers[trigger_index]
if (trigger !== undefined && trigger !== null) {
// Autofixer
if (trigger.trigger_type === "USERINPUT") {
const relevantparams = [
"alertinfo",
"options",
"type",
"email",
"sms",
"subflow",
]
var foundparams = 0
for (var paramkey in trigger.parameters) {
if (relevantparams.includes(trigger.parameters[paramkey].name)) {
foundparams++
}
}
if (foundparams < 6) {
trigger.parameters = [{
name: "alertinfo",
value: "Do you want to continue the workflow? Start parameters: $exec",
},{
name: "options",
value: "boolean",
},
{
name: "type",
value: "subflow",
},
{
name: "email",
value: "test@test.com",
},
{
name: "sms",
value: "0000000",
},
{
name: "subflow",
value: "",
}]
workflow.triggers[trigger_index].parameters = trigger.parameters
}
}
}
}
setSelectedTriggerIndex(trigger_index)
setSelectedTrigger(data)
setSelectedActionEnvironment(data.env)
}, 25)
@@ -5650,15 +5699,16 @@ const releaseToConnectLabel = "Release to Connect"
// Checks for errors in edges when they're added
const onEdgeAdded = (event) => {
const edge = event.target.data()
//console.log("EDGE ADDED!: ", edge)
setLastSaved(false);
const edge = event.target.data();
//console.log("edge added: ", edge)
if (edge.source === undefined && edge.target === undefined) {
// console.log("Edge source and target is undefined")
return
}
if (edge.readded === true) {
// console.log("Readded edge - stopping")
console.log("Readded edge - stopping")
event.target.data("readded", false)
return
@@ -5667,7 +5717,6 @@ const releaseToConnectLabel = "Release to Connect"
const sourcenode = cy.getElementById(edge.source)
const destinationnode = cy.getElementById(edge.target)
if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) {
// console.log("Source or destination node is undefined")
} else {
//console.log("Edge added: Is it a trigger? If so, check if it already has a branch and remove it: ", sourcenode.data())
if (sourcenode.data("type") === "TRIGGER") {
@@ -5680,17 +5729,17 @@ const releaseToConnectLabel = "Release to Connect"
console.log("Node: ", targetedge)
if (targetedge !== -1) {
event.target.remove()
//console.log("Found branch already!")
toast.error("Triggers can have exactly one target node")
event.target.remove()
return
// name: "Shuffle Workflow",
// name: "User Input",
} else {
// console.log("Node doesn't already have one")
console.log("Node doesn't already have one")
}
}, 50)
}
@@ -5704,10 +5753,6 @@ const releaseToConnectLabel = "Release to Connect"
}
}
if (edge.decorator === true) {
// console.log("Doing nothing to branch because decorator")
return
}
var targetnode = workflow.triggers.findIndex(
(data) => data.id === edge.target
@@ -5725,7 +5770,7 @@ const releaseToConnectLabel = "Release to Connect"
if (eventTarget.data("isButton") === true) {
const parentNode = cy.getElementById(eventTarget.data("attachedTo"))
event.target.remove()
// console.log("Setting it to parentnode: ", parentNode.data())
console.log("Setting it to parentnode: ", parentNode.data())
if (parentNode !== undefined && parentNode !== null) {
//event.target.data("target", eventTarget.data("attachedTo"))
@@ -5747,14 +5792,15 @@ const releaseToConnectLabel = "Release to Connect"
}
}
if (eventTarget.data("isDescriptor") === true || eventTarget.data("type") === "COMMENT") {
// console.log("Removing because of descriptor or comment")
event.target.remove()
return
if (
eventTarget.data("isDescriptor") === true ||
eventTarget.data("type") === "COMMENT"
) {
console.log("Removing because of descriptor or comment")
event.target.remove();
return;
}
setLastSaved(false)
targetnode = -1;
// Check if:
@@ -5762,51 +5808,38 @@ const releaseToConnectLabel = "Release to Connect"
// dest == dest && source == source
// backend: check all children? to stop recursion
var found = false;
const branches = cy.edges().jsons()
const startNode = cy.nodes().jsons().find((node) => node.data.isStartNode === true)
var startnodeId = workflow.start
if (startNode !== undefined && startNode !== null) {
startnodeId = startNode.data.id
}
//for (let branchkey in workflow.branches) {
for (let branchkey in branches) {
const branch = branches[branchkey].data
//if (workflow.branches[branchkey].destination_id === edge.source && workflow.branches[branchkey].source_id === edge.target) {
if (branch.target === edge.source && branch.source === edge.target) {
toast("A branch in the opposite direction already exists")
event.target.remove()
found = true
break
//} else if (workflow.branches[branchkey].destination_id === edge.target && workflow.branches[branchkey].source_id === edge.source) {
} else if (branch.target === edge.target && branch.source === edge.source) {
if (branch.conditions === undefined) {
// Edgehandles
} else {
// console.log("Removing because the same branch already exists")
event.target.remove()
found = true
break
}
} else if (edge.target === startnodeId) {
targetnode = workflow.triggers.findIndex((data) => data.id === edge.source)
for (let branchkey in workflow.branches) {
if (
workflow.branches[branchkey].destination_id === edge.source &&
workflow.branches[branchkey].source_id === edge.target
) {
toast("A branch in the opposite direction already exists");
event.target.remove();
found = true;
break;
} else if (
workflow.branches[branchkey].destination_id === edge.target &&
workflow.branches[branchkey].source_id === edge.source
) {
//toast("That branch already exists");
event.target.remove();
found = true;
break;
} else if (edge.target === workflow.start) {
targetnode = workflow.triggers.findIndex(
(data) => data.id === edge.source
);
if (targetnode === -1) {
if (targetnode.type !== "TRIGGER") {
toast("Can't make arrow to starting node")
event.target.remove()
break
toast("Can't make arrow to starting node");
event.target.remove();
break;
}
found = true;
}
//} else if (edge.source === workflow.branches[branchkey].source_id) {
} else if (edge.source === branch.source) {
} else if (edge.source === workflow.branches[branchkey].source_id) {
// FIXME: Verify multi-target for triggers
// 1. Check if destination exists
// 2. Check if source is a trigger
@@ -5850,6 +5883,7 @@ const releaseToConnectLabel = "Release to Connect"
newdst !== null
) {
const dstdata = RunAutocompleter(newdst.data());
//console.log("DST Autocompleter: ", dstdata);
}
var newbranch = {
@@ -12404,7 +12438,7 @@ const releaseToConnectLabel = "Release to Connect"
setSubworkflow(e.target.value);
// Sets the startnode
if (e.target.value.id !== workflow.id) {
if (e.target.value.id !== workflow.id && e.target.value.id.length > 0 ) {
console.log("WORKFLOW: ", e.target.value);
const startnode = e.target.value.actions.find((action) => action.id === e.target.value.start);
@@ -12502,9 +12536,9 @@ const releaseToConnectLabel = "Release to Connect"
let mappingWithName = {}
let listWithValues = workflow.triggers[selectedTriggerIndex].parameters[5]?.value.split(";").filter(e => e).map(e => e.split("="))
console.log("LIST WITH VALUES: ", listWithValues)
// if (listWithValues === undefined || listWithValues === null || listWithValues.length === 0) {
// return "no-overrides";
// }
if (listWithValues === undefined || listWithValues === null || listWithValues.length === 0) {
return "no-overrides";
}
for (let i = 0; i < listWithValues.length; i++) {
mappingWithName[listWithValues[i][0]] = listWithValues[i][1]
@@ -15016,7 +15050,12 @@ const releaseToConnectLabel = "Release to Connect"
const newname = (option.name.charAt(0).toUpperCase() + option.name.substring(1)).replaceAll("_", " ");
return newname;
}}
options={workflows}
options={
[{
"id": "",
"name": "No Workflow Selected",
}].concat(workflows)
}
fullWidth
style={{
backgroundColor: theme.palette.inputColor,
@@ -15171,14 +15210,40 @@ const releaseToConnectLabel = "Release to Connect"
</div>
<div style={{marginTop: 20, }} />
<b>Enabled Input-Questions</b>
<div style={{marginTop: 0, }} />
<b>Required Input-Questions</b>
{workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
<div>
{workflow.input_questions.map((question, index) => {
var foundParamIndex = workflow.triggers[selectedTriggerIndex].parameters.findIndex((param) => param.name === "input_questions")
const selectionClick = () => {
console.log("Clicked input question: ", question)
console.log("PARAMS: ", workflow.triggers[selectedTriggerIndex].parameters)
if (foundParamIndex === -1) {
workflow.triggers[selectedTriggerIndex].parameters.push({
"name": "input_questions",
"value": [],
})
foundParamIndex = workflow.triggers[selectedTriggerIndex].parameters.length - 1
} else {
try {
workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = JSON.parse(workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value)
} catch (e) {
console.log("Couldn't parse input questions: ", e)
workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = []
}
}
if (workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.includes(question.name)) {
workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.filter((item) => item !== question.name)
} else {
workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.push(question.name)
}
// Make it back to a string
workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value = JSON.stringify(workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value)
setWorkflow(workflow)
setUpdate(Math.random())
}
return (
@@ -15186,7 +15251,7 @@ const releaseToConnectLabel = "Release to Connect"
selectionClick()
}}>
<Checkbox
checked={false}
checked={foundParamIndex !== -1 ? workflow.triggers[selectedTriggerIndex].parameters[foundParamIndex].value.includes(question.name) : false}
/>
<Typography variant="body2" style={{marginTop: 10, }}>
{question.name}
@@ -15198,6 +15263,7 @@ const releaseToConnectLabel = "Release to Connect"
:
<div style={{cursor: "pointer", color: "#f85a3e", marginTop: 10, }} onClick={() => {
setEditWorkflowModalOpen(true)
toast.info("Expand and scroll down to add input-questions")
}}>
<Typography variant="body2">No Input-Questions found. Click to add them!</Typography>
</div>
@@ -15728,6 +15794,7 @@ const releaseToConnectLabel = "Release to Connect"
right: 0,
left: isMobile ? 20 : leftBarSize + 20,
top: isMobile ? 30 : appBarSize + 20,
pointerEvents: "none",
}
@@ -15748,14 +15815,17 @@ const releaseToConnectLabel = "Release to Connect"
<div style={topBarStyle}>
<div style={{
margin: "0px 10px 0px 10px",
pointerEvents: "auto",
}}>
<Breadcrumbs
aria-label="breadcrumb"
separator=""
style={{
color: "white"
color: "white",
pointerEvents: "auto",
}}
>
<Link
to="/workflows"
style={{ textDecoration: "none", color: "inherit" }}
@@ -15772,6 +15842,7 @@ const releaseToConnectLabel = "Release to Connect"
</Link>
<h2 style={{
margin: 0,
pointerEvents: "none",
}}>{workflow.name}</h2>
</Breadcrumbs>
@@ -15779,7 +15850,7 @@ const releaseToConnectLabel = "Release to Connect"
isCorrectOrg ? null :
<Typography variant="body1">
<b>Warning</b>: Change <span
style={{color: "#f85a3e", cursor: "pointer"}}
style={{color: "#f85a3e", cursor: "pointer", pointerEvents: "auto", }}
onClick={() => {
toast("Changing to correct organisation. Please wait a few seconds.")
@@ -16080,7 +16151,7 @@ const releaseToConnectLabel = "Release to Connect"
: null}
</div>
<div style={{display: "flex", marginLeft: 10, }}>
<div style={{display: "flex", marginLeft: 10, pointerEvents: "auto", }}>
{parentWorkflows.slice(0,5).map((wf, index) => {
return (
<a href={`/workflows/${wf.id}`} target="_blank" rel="noopener noreferrer" key={index}>
@@ -17078,10 +17149,12 @@ const releaseToConnectLabel = "Release to Connect"
//defaultReturn = <UserinputSidebar />
return null;
} else {
/*
console.log(
"Unable to handle invalid trigger type " +
selectedTrigger.trigger_type
);
*/
return null;
}
} else if (Object.getOwnPropertyNames(selectedEdge).length > 0) {
@@ -18387,7 +18460,7 @@ const releaseToConnectLabel = "Release to Connect"
</div>
{foundnotifications > 0 ?
<Tooltip title={"This workflow created " + foundnotifications + " notification(s). Click to explore them."} placement="top">
<Tooltip title={"This workflow created " + foundnotifications + " notification(s)"} placement="top">
<ErrorOutlineIcon
style={{color: "rgba(255,255,255,0.4)", marginTop: 10, marginRight: 10, }}
onClick={(e) => {
@@ -21662,6 +21735,8 @@ const releaseToConnectLabel = "Release to Connect"
const changeActionParameterCodeMirror = (event, count, data, actionlist) => {
// Check if event.target.value is an array. If it is, split with comma
console.log("1 - SELECTED ACTION: ", selectedAction)
console.log("1 - DATA: ", data)
if (data.startsWith("${") && data.endsWith("}")) {
// PARAM FIX - Gonna use the ID field, even though it's a hack
@@ -21700,8 +21775,6 @@ const releaseToConnectLabel = "Release to Connect"
}
if (event.target.value[event.target.value.length-1] === "." && actionlist.length > 0) {
console.log("GET THE LAST ARGUMENT FOR NODE!")
var curstring = ""
var record = false
for (let [key,keyval] in Object.entries(selectedAction.parameters[count].value)) {
@@ -21716,7 +21789,6 @@ const releaseToConnectLabel = "Release to Connect"
}
}
//console.log("CURSTRING: ", curstring)
if (curstring.length > 0 && actionlist !== null) {
// Search back in the action list
curstring = curstring.split(" ").join("_").toLowerCase()
@@ -21737,8 +21809,32 @@ const releaseToConnectLabel = "Release to Connect"
}
}
selectedAction.parameters[count].autocompleted = false
selectedAction.parameters[count].value = data
console.log("2 - SELECTED ACTION: ", selectedAction)
console.log("2 - DATA: ", data)
if (selectedAction.app_name === "Shuffle Tools" && selectedAction.name === "filter_list" && count === 0) {
const parsedvalue = data
console.log("Parsed value: ", parsedvalue)
if (parsedvalue.includes("#")) {
const splitparsed = parsedvalue.split(".#.")
//console.log("Cant contain #: ", splitparsed)
if (splitparsed.length > 1) {
console.log("IN HERE AY")
//data.value = splitparsed[0]
selectedAction.parameters[0].value = splitparsed[0]
selectedAction.parameters[1].value = splitparsed[1]
selectedAction.parameters[0].autocompleted = true
selectedAction.parameters[1].autocompleted = true
setUpdate(Math.random())
}
}
} else {
selectedAction.parameters[count].autocompleted = false
selectedAction.parameters[count].value = data
}
setSelectedAction(selectedAction)
//setUpdate(Math.random())
}
+191 -54
View File
@@ -2,6 +2,7 @@
import React, {useState, useEffect} from 'react';
import ReactDOM from "react-dom"
import { ToastContainer, toast } from "react-toastify"
import { useInterval } from "react-powerhooks";
import { makeStyles } from '@mui/material/styles';
import { useNavigate, Link, useParams } from "react-router-dom";
@@ -43,7 +44,8 @@ const bodyDivStyle = {
const RunWorkflow = (defaultprops) => {
const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, serverside } = defaultprops;
let navigate = useNavigate();
let navigate = useNavigate();
const [_, setUpdate] = useState(""); // Used to force rendring, don't remove
const [message, setMessage] = useState("");
const [workflow, setWorkflow] = React.useState({});
const [executionRequest, setExecutionRequest] = React.useState({});
@@ -55,6 +57,7 @@ const RunWorkflow = (defaultprops) => {
const [selectedOrganization, setSelectedOrganization] = React.useState(undefined);
const [apps, setApps] = React.useState([]);
const [buttonClicked, setButtonClicked] = React.useState("");
const [foundSourcenode, setFoundSourcenode] = React.useState(undefined);
const boxStyle = {
color: "white",
@@ -340,6 +343,8 @@ const RunWorkflow = (defaultprops) => {
event.preventDefault()
}
console.log("ONSUBMIT: ", event, execution_id, authorization, answer)
stop()
setMessage("")
setExecutionLoading(true)
@@ -389,12 +394,27 @@ const RunWorkflow = (defaultprops) => {
url += `?reference_execution=${execution_id}&authorization=${authorization}&answer=${answer}`
data = {}
fetchBody.method = "GET"
if (executionArgument !== undefined && executionArgument !== null) {
try {
if (typeof executionArgument === "string") {
url += "&note=" + executionArgument
} else {
url += "&note=" + JSON.stringify(executionArgument)
}
} catch (e) {
url += "&note=" + executionArgument
}
}
} else {
fetchBody.method = "POST"
fetchBody.body = JSON.stringify(data)
}
console.log("Pre request: ", url, fetchBody)
// IF there is an execution argument, we should use it
console.log("FULL URL: ", url)
fetch(url, fetchBody)
.then((response) => {
if (response.status !== 200 && response.status !== 201) {
@@ -408,16 +428,26 @@ const RunWorkflow = (defaultprops) => {
})
start();
return
return response.json()
}
}
return response.json();
return response.json()
})
.then(responseJson => {
setExecutionLoading(false)
if (responseJson["success"] === false) {
if (responseJson.success === false) {
console.log("Failed sending execution request")
if (responseJson.reason !== undefined && responseJson.reason !== null) {
toast.warn(responseJson.reason)
}
stop()
setMessage("")
setExecutionData({})
setExecutionInfo("")
setExecutionRunning(false)
setExecutionRequest({})
} else {
console.log("Started execution")
@@ -426,17 +456,24 @@ const RunWorkflow = (defaultprops) => {
} else {
setExecutionRunning(true);
setExecutionRequest(responseJson)
start();
start()
}
}
})
.catch(error => {
//setExecutionInfo("Error in workflow startup: " + error)
toast.warn("Error in workflow startup: " + error)
stop()
setMessage("")
setExecutionData({})
setExecutionInfo("")
setExecutionLoading(false)
})
}
const getWorkflow = (workflow_id) => {
const getWorkflow = (workflow_id, selectedNode) => {
fetch(globalUrl + "/api/v1/workflows/" + workflow_id, {
method: "GET",
headers: {
@@ -480,6 +517,48 @@ const RunWorkflow = (defaultprops) => {
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) {
found = true
setFoundSourcenode(responseJson.actions[actionkey])
break
}
}
if (!found) {
for (var triggerkey in responseJson.triggers) {
if (responseJson.triggers[triggerkey].id !== selectedNode) {
continue
}
setFoundSourcenode(responseJson.triggers[triggerkey])
if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0 && responseJson.triggers[triggerkey].trigger_type === "USERINPUT") {
// Look for input questions param
for (var paramkey in responseJson.triggers[triggerkey].parameters) {
if (responseJson.triggers[triggerkey].parameters[paramkey].name === "input_questions") {
var relevantquestions = []
for (var questionkey in responseJson.input_questions) {
if (responseJson.triggers[triggerkey].parameters[paramkey].value.includes(responseJson.input_questions[questionkey].name)) {
relevantquestions.push(responseJson.input_questions[questionkey])
}
}
responseJson.input_questions = relevantquestions
}
}
}
break
}
}
}
handleGetOrg(responseJson.org_id)
setWorkflow(responseJson);
})
@@ -489,7 +568,7 @@ const RunWorkflow = (defaultprops) => {
};
const { start, stop } = useInterval({
duration: 3000,
duration: 1500,
startImmediate: true,
callback: () => {
fetchUpdates(executionRequest.execution_id, executionRequest.authorization)
@@ -526,15 +605,11 @@ const RunWorkflow = (defaultprops) => {
for (var key in responseJson.results) {
if (responseJson.results[key].status === "WAITING") {
console.log("Found: ", responseJson.results[key])
const validate = validateJson(responseJson.results[key].result)
console.log("Validate: ", validate)
if (validate.valid && typeof validate.result === "string") {
validate.result = JSON.parse(validate.result)
}
console.log("Newresult: ", validate.result)
if (validate.result["information"] !== undefined && validate.result["information"] !== null) {
setWorkflowQuestion(validate.result["information"])
}
@@ -623,15 +698,15 @@ const RunWorkflow = (defaultprops) => {
return
}
fetch(globalUrl + "/api/v1/streams/results", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(innerRequest),
credentials: "include",
})
fetch(globalUrl + "/api/v1/streams/results", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(innerRequest),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for stream results :O!");
@@ -651,11 +726,14 @@ const RunWorkflow = (defaultprops) => {
});
};
const answer = new URLSearchParams(window.location.search).get("answer")
const execution_id = new URLSearchParams(window.location.search).get("reference_execution")
const authorization = new URLSearchParams(window.location.search).get("authorization")
const searchParams = new URLSearchParams(window.location.search)
const answer = searchParams.get("answer")
const execution_id = searchParams.get("reference_execution")
const authorization = searchParams.get("authorization")
const sourceNode = searchParams.get("source_node")
useEffect(() => {
getWorkflow(props.match.params.key)
getWorkflow(props.match.params.key, sourceNode)
if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null) {
console.log("Get execution: ", execution_id)
fetchUpdates(execution_id, authorization, true)
@@ -667,13 +745,60 @@ const RunWorkflow = (defaultprops) => {
}, [])
useEffect(() => {
if (executionData === undefined || executionData === null || executionData === {}) {
return
}
if (foundSourcenode === undefined || foundSourcenode === null || foundSourcenode === {}) {
return
}
if (foundSourcenode.trigger_type !== "USERINPUT") {
return
}
if (executionData.results === undefined || executionData.results === null || executionData.results.length === 0) {
return
}
for (var resultkey in executionData.results) {
const result = executionData.results[resultkey]
if (result.action.id !== foundSourcenode.id) {
continue
}
var parsedresult = result.result
try {
parsedresult = JSON.parse(parsedresult)
} catch (e) {
console.log("Error parsing result: ", e)
}
if (result.status !== "WAITING") {
if (parsedresult.click_info !== undefined && parsedresult.click_info !== null) {
if (parsedresult.click_info.user !== undefined && parsedresult.click_info.user !== null && parsedresult.click_info.user.length > 0) {
setMessage("Already answered by " + parsedresult.click_info.user)
}
} else {
setMessage("Answered.")
}
}
break
}
}, [executionData, foundSourcenode])
const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"
const buttonStyle = {borderRadius: 25, height: 50, fontSize: 18, backgroundImage: handleValidateForm(executionArgument) || executionLoading ? buttonBackground : "grey", color: "white"}
//console.log("execdata: ", executionData)
const disabledButtons = message.length > 0 || executionData.status === "FINISHED" || executionData.status === "ABORTED"
//{disabledButtons ? null :
const organization = selectedOrganization !== undefined && selectedOrganization !== null ? selectedOrganization.name : "Unknown"
const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.org !== undefined && selectedOrganization.org !== null? selectedOrganization.org : "support@shuffler.io"
//const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.contact !== undefined && selectedOrganization.contact !== null? selectedOrganization.contact : "support@shuffler.io"
@@ -724,49 +849,54 @@ const RunWorkflow = (defaultprops) => {
<Typography variant="h6" style={{marginBottom: 10, marginTop: 50, textAlign: "center", }}>
{organization}
</Typography>
<Typography variant="body1" color="textSecondary" style={{marginBottom: 15, marginTop: 0, textAlign: "center",}}>
{contact}
</Typography>
<Divider style={{marginTop: 20, marginBottom: 20, }}/>
<Typography color="textSecondary">{message}</Typography>
{disabledButtons && message.length > 0 ? null :
<Typography color="textSecondary" style={{textAlign: "center", }}>
{message}
</Typography>
}
{answer !== undefined && answer !== null ? null :
<Typography variant="h6" style={{marginBottom: 15, }}><b>{workflow.name}</b></Typography>
<Typography variant="h6" style={{marginBottom: 15, textAlign: "center", }}><b>{workflow.name}</b></Typography>
}
{workflowQuestion.length > 0 ?
<Typography variant="body1" style={{ marginBottom: 35, marginTop: 30, marginRight: 15, textAlign: "center", whiteSpace: "pre-line", }}>
{workflowQuestion}
</Typography>
<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}
{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, }}
multiLine
maxRows={2}
InputProps={{
style:{
height: "50px",
color: "white",
fontSize: "1em",
},
}}
label={question.value}
required
disabled={disabledButtons}
fullWidth={true}
placeholder=""
id="emailfield"
margin="normal"
variant="outlined"
onChange={(e) => {
onBlur={(e) => {
//setExecutionArgument(e.target.value)
executionArgument[question.value] = e.target.value
setUpdate(Math.random())
}}
/>
</div>
@@ -813,20 +943,27 @@ const RunWorkflow = (defaultprops) => {
</Typography>
: null}
</span>
:
answer !== undefined && answer !== null ?
((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) ?
<span style={{marginTop: 20, }}>
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
{disabledButtons ? "Already answered. Nothing to do." : ""}
</Typography>
{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={{border: answer === "true" ? "2px solid rgba(255,255,255,0.6)" : null, flex: 1,}} onClick={() => {
<Button fullWidth id="continue_execution" variant="contained" disabled={disabledButtons} color="primary" style={{flex: 1,}} onClick={() => {
onSubmit(null, execution_id, authorization, true)
setButtonClicked("FINISHED")
@@ -837,7 +974,7 @@ const RunWorkflow = (defaultprops) => {
<Typography variant="body1" style={{marginLeft: 3, marginRight: 3, marginTop: 3, }}>
&nbsp;or&nbsp;
</Typography>
<Button fullWidth id="abort_execution" variant="contained" color="primary" disabled={disabledButtons} style={{border: answer !== "true" ? "2px solid rgba(255,255,255,0.6)" : null, flex: 1, }} onClick={() => {
<Button fullWidth id="abort_execution" variant="contained" color="primary" disabled={disabledButtons} style={{ flex: 1, }} onClick={() => {
onSubmit(null, execution_id, authorization, false)
setButtonClicked("ABORTED")
@@ -856,7 +993,7 @@ const RunWorkflow = (defaultprops) => {
</div>
}
{buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ?
{/*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.")
@@ -868,7 +1005,7 @@ const RunWorkflow = (defaultprops) => {
}}
/>
: null}
: null*/}
<div style={{marginTop: "10px"}}>
{executionInfo}
+7 -6
View File
@@ -1162,10 +1162,11 @@ const Workflows = (props) => {
})
.then((responseJson) => {
if (responseJson !== undefined) {
var newarray = []
for (var key in responseJson) {
const wf = responseJson[key]
if (wf.public === true) {
for (var wfkey in responseJson) {
const wf = responseJson[wfkey]
if (wf.public === true || wf.hidden === true) {
continue
}
@@ -1178,9 +1179,9 @@ const Workflows = (props) => {
var parsedactionlist = [];
for (var key in newarray) {
const workflow = newarray[key]
if (workflow.status === "production") {
setProdFilter = true
}
//if (workflow.status === "production") {
// setProdFilter = true
//}
for (var actionkey in newarray[key].actions) {
const action = newarray[key].actions[actionkey];