Added single app testing from GUI

This commit is contained in:
frikky
2021-06-02 23:39:20 +02:00
parent 2c9bac9eb0
commit 24fd96fcf0
10 changed files with 83 additions and 58 deletions
+6 -6
View File
@@ -1967,12 +1967,12 @@ class AppBase:
params[parameter["name"]] = resultarray params[parameter["name"]] = resultarray
multi_parameters[parameter["name"]] = resultarray multi_parameters[parameter["name"]] = resultarray
if len(resultarray) == 0: #if len(resultarray) == 0:
print("[WARNING] Returning empty array because the array length to be looped is 0 (1)") # print("[WARNING] Returning empty array because the array length to be looped is 0 (1)")
action_result["status"] = "SUCCESS" # action_result["status"] = "SUCCESS"
action_result["result"] = "[]" # action_result["result"] = "[]"
self.send_result(action_result, headers, stream_path) # self.send_result(action_result, headers, stream_path)
return # return
multi_execution_lists.append(new_replacement) multi_execution_lists.append(new_replacement)
#print("MULTI finished: %s" % json_replacement) #print("MULTI finished: %s" % json_replacement)
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
NAME=shuffle-app_sdk NAME=shuffle-app_sdk
VERSION=0.8.97 VERSION=0.8.98
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
docker build . -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION docker build . -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
+14 -18
View File
@@ -3177,7 +3177,7 @@ func handleSwaggerValidation(body []byte) (shuffle.ParsedOpenApi, error) {
//log.Printf("Json err: %s", err) //log.Printf("Json err: %s", err)
err = yaml.Unmarshal(body, &version) err = yaml.Unmarshal(body, &version)
if err != nil { if err != nil {
log.Printf("Yaml error (1): %s", err) log.Printf("[WARNING] Yaml error (1): %s", err)
} else { } else {
//log.Printf("Successfully parsed YAML!") //log.Printf("Successfully parsed YAML!")
} }
@@ -3220,7 +3220,7 @@ func handleSwaggerValidation(body []byte) (shuffle.ParsedOpenApi, error) {
//log.Printf("Json error? %s", err) //log.Printf("Json error? %s", err)
err = yaml.Unmarshal(body, &swagger) err = yaml.Unmarshal(body, &swagger)
if err != nil { if err != nil {
log.Printf("Yaml error (2): %s", err) log.Printf("[WARNING] Yaml error (2): %s", err)
return shuffle.ParsedOpenApi{}, err return shuffle.ParsedOpenApi{}, err
} else { } else {
//log.Printf("Valid yaml!") //log.Printf("Valid yaml!")
@@ -4181,14 +4181,14 @@ func runInitEs(ctx context.Context) {
for _, org := range activeOrgs { for _, org := range activeOrgs {
if !org.CloudSync { if !org.CloudSync {
log.Printf("Skipping org %s because sync isn't set (1).", org.Id) log.Printf("[WARNING] Skipping org %s because sync isn't set (1).", org.Id)
continue continue
} }
//interval := int(org.SyncConfig.Interval) //interval := int(org.SyncConfig.Interval)
interval := 15 interval := 15
if interval == 0 { if interval == 0 {
log.Printf("Skipping org %s because sync isn't set (0).", org.Id) log.Printf("[WARNING] Skipping org %s because sync isn't set (0).", org.Id)
continue continue
} }
@@ -4204,7 +4204,7 @@ func runInitEs(ctx context.Context) {
if err != nil { if err != nil {
log.Printf("[CRITICAL] Failed to schedule org: %s", err) log.Printf("[CRITICAL] Failed to schedule org: %s", err)
} else { } else {
log.Printf("Started sync on interval %d for org %s", interval, org.Name) log.Printf("[INFO] Started sync on interval %d for org %s (%s)", interval, org.Name, org.Id)
scheduledOrgs[org.Id] = jobret scheduledOrgs[org.Id] = jobret
} }
} }
@@ -5035,7 +5035,7 @@ func handleStopCloudSync(syncUrl string, org shuffle.Org) (*shuffle.Org, error)
return &org, errors.New(fmt.Sprintf("Couldn't find any sync key to disable org %s", org.Id)) return &org, errors.New(fmt.Sprintf("Couldn't find any sync key to disable org %s", org.Id))
} }
log.Printf("Should run cloud sync disable for org %s with URL %s and sync key %s", org.Id, syncUrl, org.SyncConfig.Apikey) log.Printf("[INFO] Should run cloud sync disable for org %s with URL %s and sync key %s", org.Id, syncUrl, org.SyncConfig.Apikey)
client := &http.Client{} client := &http.Client{}
req, err := http.NewRequest( req, err := http.NewRequest(
@@ -5071,7 +5071,7 @@ func handleStopCloudSync(syncUrl string, org shuffle.Org) (*shuffle.Org, error)
return &org, errors.New(responseData.Reason) return &org, errors.New(responseData.Reason)
} }
log.Printf("Everything is success. Should disable org sync for %s", org.Id) log.Printf("[INFO] Everything is success. Should disable org sync for %s", org.Id)
ctx := context.Background() ctx := context.Background()
org.CloudSync = false org.CloudSync = false
@@ -5080,15 +5080,14 @@ func handleStopCloudSync(syncUrl string, org shuffle.Org) (*shuffle.Org, error)
err = shuffle.SetOrg(ctx, org, org.Id) err = shuffle.SetOrg(ctx, org, org.Id)
if err != nil { if err != nil {
newerror := fmt.Sprintf("ERROR: Failed updating even though there was success: %s", err) newerror := fmt.Sprintf("[WARNING] ERROR: Failed updating even though there was success: %s", err)
log.Printf(newerror) log.Printf(newerror)
return &org, errors.New(newerror) return &org, errors.New(newerror)
} }
var environments []shuffle.Environment environments, err := shuffle.GetEnvironments(ctx, org.Id)
q := datastore.NewQuery("Environments").Filter("org_id =", org.Id)
_, err = dbclient.GetAll(ctx, q, &environments)
if err != nil { if err != nil {
log.Printf("[WARNING] Failed getting envs in stop sync: %s", err)
return &org, err return &org, err
} }
@@ -5237,9 +5236,9 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
// Everything below here is to SET UP CLOUD SYNC. // Everything below here is to SET UP CLOUD SYNC.
// If you want to disable cloud sync, see previous section. // If you want to disable cloud sync, see previous section.
if org.CloudSync { if org.CloudSync {
log.Printf("Org %s is already syncing. Skip", org.Id) log.Printf("[WARNING] Org %s is already syncing. Skip", org.Id)
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Org is already syncing. Nothing to set up."}`))) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Your org is already syncing. Nothing to set up."}`)))
return return
} }
@@ -5332,18 +5331,15 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
scheduledOrgs[org.Id] = jobret scheduledOrgs[org.Id] = jobret
} }
// FIXME: Add this for every feature // ONLY checked added if workflows are allow huh
if org.SyncFeatures.Workflows.Active { if org.SyncFeatures.Workflows.Active {
log.Printf("[INFO] Should activate cloud workflows for org %s!", org.Id) log.Printf("[INFO] Should activate cloud workflows for org %s!", org.Id)
// 1. Find environment // 1. Find environment
// 2. If cloud env found, enable it (un-archive) // 2. If cloud env found, enable it (un-archive)
// 3. If it doesn't create it // 3. If it doesn't create it
//var environments []shuffle.Environment
//q := datastore.NewQuery("Environments").Filter("org_id =", org.Id)
//_, err = dbclient.GetAll(ctx, q, &environments)
environments, err := shuffle.GetEnvironments(ctx, org.Id) environments, err := shuffle.GetEnvironments(ctx, org.Id)
log.Printf("GETTING ENVS: %#s", environments)
if err == nil { if err == nil {
// Don't disable, this will be deleted entirely // Don't disable, this will be deleted entirely
+6 -6
View File
@@ -1179,10 +1179,10 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) {
} }
} }
err = increaseStatisticsField(ctx, "total_workflow_triggers", workflow.ID, -1, workflow.OrgId) //err = increaseStatisticsField(ctx, "total_workflow_triggers", workflow.ID, -1, workflow.OrgId)
if err != nil { //if err != nil {
log.Printf("Failed to increase total workflows: %s", err) // log.Printf("Failed to increase total workflows: %s", err)
} //}
} }
// FIXME - maybe delete workflow executions // FIXME - maybe delete workflow executions
@@ -3408,14 +3408,14 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string,
readFile, err := ioutil.ReadAll(fileReader) readFile, err := ioutil.ReadAll(fileReader)
if err != nil { if err != nil {
log.Printf("Filereader error yaml for %s: %s", filename, err) log.Printf("[WARNING] Filereader error yaml for %s: %s", filename, err)
continue continue
} }
// 1. This parses OpenAPI v2 to v3 etc, for use. // 1. This parses OpenAPI v2 to v3 etc, for use.
parsedOpenApi, err := handleSwaggerValidation(readFile) parsedOpenApi, err := handleSwaggerValidation(readFile)
if err != nil { if err != nil {
log.Printf("Validation error for %s: %s", filename, err) log.Printf("[WARNING] Validation error for %s: %s", filename, err)
continue continue
} }
+1 -1
View File
@@ -115,8 +115,8 @@ const App = (message, props) => {
<Route exact path="/webhooks/:key" render={props => <EditWebhook isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} /> <Route exact path="/webhooks/:key" render={props => <EditWebhook isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
<Route exact path="/schedules" render={props => <Schedules globalUrl={globalUrl} {...props} />} /> <Route exact path="/schedules" render={props => <Schedules globalUrl={globalUrl} {...props} />} />
<Route exact path="/dashboard" render={props => <Dashboard isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} /> <Route exact path="/dashboard" render={props => <Dashboard isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
<Route exact path="/apps" render={props => <Apps isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} userdata={userdata} {...props} />} />
<Route exact path="/apps/new" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} /> <Route exact path="/apps/new" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
<Route exact path="/apps" render={props => <Apps isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} userdata={userdata} {...props} />} />
<Route exact path="/apps/edit/:appid" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} /> <Route exact path="/apps/edit/:appid" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
<Route exact path="/schedules/:key" render={props => <EditSchedule globalUrl={globalUrl} {...props} />} /> <Route exact path="/schedules/:key" render={props => <EditSchedule globalUrl={globalUrl} {...props} />} />
<Route exact path="/workflows" render={props => <Workflows cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} userdata={userdata} {...props} />} /> <Route exact path="/workflows" render={props => <Workflows cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} userdata={userdata} {...props} />} />
+25
View File
@@ -50,6 +50,7 @@ const ParsedAction = (props) => {
const theme = useTheme(); const theme = useTheme();
const classes = useStyles() const classes = useStyles()
const keywords = ["len(", "lower(", "upper(", "trim(", "split(", "length(", "number(", "parse(", "join("]
const getParents = (action) => { const getParents = (action) => {
if (cy === undefined) { if (cy === undefined) {
return [] return []
@@ -757,6 +758,23 @@ const ParsedAction = (props) => {
}} }}
/> />
console.log("FIELD VALUE: ", data.value)
//const regexp = new RegExp("\W+\.", "g")
//let match
//while ((match = regexp.exec(data.value)) !== null) {
// console.log(`Found ${match[0]} start=${match.index} end=${regexp.lastIndex}.`);
//}
//const str = = data.value.search(submatch)
//console.log("FOUND? ", n)
for (var key in keywords) {
const keyword = keywords[key]
if (data.value.includes(keyword)) {
console.log("INCLUDED: ", keyword)
}
}
//const keywords = ["len", "lower", "upper", "trim", "split", "length", "number", "parse", "join"]
if (selectedActionParameters[count].schema !== undefined && selectedActionParameters[count].schema !== null && selectedActionParameters[count].schema.type === "file") { if (selectedActionParameters[count].schema !== undefined && selectedActionParameters[count].schema !== null && selectedActionParameters[count].schema.type === "file") {
datafield = datafield =
<TextField <TextField
@@ -875,7 +893,11 @@ const ParsedAction = (props) => {
return return
} }
console.log("AUTOCOMPLETE1: ", values)
var toComplete = selectedActionParameters[count].value.trim().endsWith("$") ? values[0].autocomplete : "$"+values[0].autocomplete var toComplete = selectedActionParameters[count].value.trim().endsWith("$") ? values[0].autocomplete : "$"+values[0].autocomplete
toComplete = toComplete.toLowerCase().replaceAll(" ", "_")
console.log("AUTOCOMPLETE: ", toComplete)
for (var key in values) { for (var key in values) {
if (key == 0 || values[key].autocomplete.length === 0) { if (key == 0 || values[key].autocomplete.length === 0) {
continue continue
@@ -1353,6 +1375,9 @@ const ParsedAction = (props) => {
} }
} }
} }
console.log("DID REPLACE ACTUALLY WORK?? - Something is buggy.")
setWorkflow(workflow)
}} }}
/> />
</span> </span>
+10 -9
View File
@@ -154,7 +154,7 @@ const Admin = (props) => {
// Need to wait because query in ES is too fast // Need to wait because query in ES is too fast
setTimeout(() => { setTimeout(() => {
getAppAuthentication() getAppAuthentication()
}, 1000) }, 500)
alert.success("Successfully deleted authentication!") alert.success("Successfully deleted authentication!")
} }
}), }),
@@ -228,7 +228,7 @@ const Admin = (props) => {
.then((responseJson) => { .then((responseJson) => {
setTimeout(() => { setTimeout(() => {
handleGetOrg(org_id) handleGetOrg(org_id)
}, 1000) }, 500)
}) })
.catch(error => { .catch(error => {
alert.error("Err: " + error.toString()) alert.error("Err: " + error.toString())
@@ -264,12 +264,13 @@ const Admin = (props) => {
console.log("Cloud sync fail?") console.log("Cloud sync fail?")
} }
setTimeout(() => {
return response.json() return response.json()
}, 1000) //setTimeout(() => {
//}, 1000)
}) })
.then((responseJson) => { .then((responseJson) => {
if (!responseJson.success && responseJson.reason !== undefined) { console.log("RESP: ", responseJson)
if (responseJson.success === false && responseJson.reason !== undefined) {
setOrgSyncResponse(responseJson.reason) setOrgSyncResponse(responseJson.reason)
alert.error("Failed to handle sync: "+responseJson.reason) alert.error("Failed to handle sync: "+responseJson.reason)
} else if (!responseJson.success) { } else if (!responseJson.success) {
@@ -355,7 +356,7 @@ const Admin = (props) => {
setSelectedUserModalOpen(false) setSelectedUserModalOpen(false)
setTimeout(() => { setTimeout(() => {
getAppAuthentication() getAppAuthentication()
}, 1000) }, 500)
} }
}), }),
) )
@@ -513,7 +514,7 @@ const Admin = (props) => {
setModalOpen(false) setModalOpen(false)
setTimeout(() => { setTimeout(() => {
getUsers() getUsers()
}, 1000) }, 500)
} }
}), }),
) )
@@ -548,7 +549,7 @@ const Admin = (props) => {
setModalOpen(false) setModalOpen(false)
setTimeout(() => { setTimeout(() => {
getUsers() getUsers()
}, 1000) }, 500)
} }
}), }),
) )
@@ -1649,7 +1650,7 @@ const Admin = (props) => {
</div> </div>
{orgSyncResponse.length > 0 ? {orgSyncResponse.length > 0 ?
<Typography style={{marginTop: 5, marginBottom: 10}}> <Typography style={{marginTop: 5, marginBottom: 10}}>
Message from Shuffle: <b>{orgSyncResponse}</b> Message from Shuffle Cloud: <b>{orgSyncResponse}</b>
</Typography> </Typography>
: null : null
} }
+11 -11
View File
@@ -1571,17 +1571,13 @@ const AngularWorkflow = (props) => {
} }
} }
console.log("ID ETC: ", idnumber, paramname)
if (idnumber >= 0 && paramname.length > 0) { if (idnumber >= 0 && paramname.length > 0) {
const exampledata = GetExampleResult(nodedata) const exampledata = GetExampleResult(nodedata)
const parsedname = paramname.toLowerCase().trim().replaceAll("_", " ") const parsedname = paramname.toLowerCase().trim().replaceAll("_", " ")
console.log("EX: ", exampledata)
console.log("NAME: ", parsedname)
const foundresult = GetParamMatch(parsedname, exampledata, "") const foundresult = GetParamMatch(parsedname, exampledata, "")
console.log("RESULT: ", foundresult)
if (foundresult.length > 0) { if (foundresult.length > 0) {
console.log("FOUND: ", paramname, foundresult) console.log("FOUND RESULT: ", paramname, foundresult)
newValue = `${newValue}${foundresult}` newValue = `${newValue}${foundresult}`
} }
@@ -2100,9 +2096,13 @@ const AngularWorkflow = (props) => {
return "" return ""
} }
if (exampledata === null) {
return ""
}
// Basically just a stupid if-else :) // Basically just a stupid if-else :)
const synonyms = { const synonyms = {
"id": ["id", "ref", "sourceref", "reference", "sourcereference", "alert id", "case id", "incident id", "service id",], "id": ["id", "ref", "sourceref", "reference", "sourcereference", "alert id", "case id", "incident id", "service id", "sid", "uid", "uuid"],
"title": ["title", "name", "message"], "title": ["title", "name", "message"],
"description": ["description", "explanation", "story", "details",], "description": ["description", "explanation", "story", "details",],
"email": ["mail", "email", "sender", "receiver", "recipient"], "email": ["mail", "email", "sender", "receiver", "recipient"],
@@ -7535,7 +7535,7 @@ const AngularWorkflow = (props) => {
/* Copy the text inside the text field */ /* Copy the text inside the text field */
document.execCommand("copy") document.execCommand("copy")
alert.success("Copied data") //alert.success("Copied data")
} else { } else {
console.log("Failed to copy from "+elementName+": ", copyText) console.log("Failed to copy from "+elementName+": ", copyText)
} }
@@ -7544,7 +7544,7 @@ const AngularWorkflow = (props) => {
const HandleJsonCopy = (base, copy, base_node_name) => { const HandleJsonCopy = (base, copy, base_node_name) => {
console.log("COPY: ", copy) console.log("COPY: ", copy)
var newitem = JSON.parse(base) var newitem = JSON.parse(base)
to_be_copied = "$"+base_node_name to_be_copied = "$"+base_node_name.toLowerCase().replaceAll(" ", "_")
for (var key in copy.namespace) { for (var key in copy.namespace) {
if (copy.namespace[key].includes("Results for")) { if (copy.namespace[key].includes("Results for")) {
continue continue
@@ -7591,7 +7591,7 @@ const AngularWorkflow = (props) => {
/* Copy the text inside the text field */ /* Copy the text inside the text field */
document.execCommand("copy"); document.execCommand("copy");
alert.success("Copied "+to_be_copied) //alert.success("Copied "+to_be_copied)
console.log("COPYING!") console.log("COPYING!")
} else { } else {
console.log("Couldn't find element ", elementName) console.log("Couldn't find element ", elementName)
@@ -8097,7 +8097,7 @@ const AngularWorkflow = (props) => {
{validate.valid ? <ReactJson {validate.valid ? <ReactJson
src={validate.result} src={validate.result}
theme="solarized" theme="solarized"
collapsed={false} collapsed={selectedResult.result.length < 10000 ? false : true}
displayDataTypes={false} displayDataTypes={false}
enableClipboard={(copy) => { enableClipboard={(copy) => {
handleReactJsonClipboard(copy) handleReactJsonClipboard(copy)
@@ -8131,7 +8131,7 @@ const AngularWorkflow = (props) => {
/* Copy the text inside the text field */ /* Copy the text inside the text field */
document.execCommand("copy"); document.execCommand("copy");
alert.success("Copied "+to_be_copied) //alert.success("Copied "+to_be_copied)
} else { } else {
console.log("Failed to copy. copy_element_shuffle is undefined") console.log("Failed to copy. copy_element_shuffle is undefined")
} }
+4 -3
View File
@@ -2443,7 +2443,7 @@ const AppCreator = (props) => {
version: selectedAction.app_version, version: selectedAction.app_version,
}) })
const [requiresAuthentication, setRequiresAuthentication] = useState(app.authentication.required && app.authentication.parameters !== undefined && app.authentication.parameters !== null) const [requiresAuthentication, setRequiresAuthentication] = useState(app.authentication !== null && app.authentication !== undefined && app.authentication.required && app.authentication.parameters !== undefined && app.authentication.parameters !== null ? true : false)
const [workflow, setWorkflow] = useState({ const [workflow, setWorkflow] = useState({
name: "", name: "",
description: "", description: "",
@@ -2699,7 +2699,7 @@ const AppCreator = (props) => {
for (var key in selectedApp.authentication.parameters) { for (var key in selectedApp.authentication.parameters) {
if (authenticationOption.fields[selectedApp.authentication.parameters[key].name].length === 0) { if (authenticationOption.fields[selectedApp.authentication.parameters[key].name].length === 0) {
alert.info("Field "+selectedApp.authentication.parameters[key].name+" can't be empty") alert.info("Field "+selectedApp.authentication.parameters[key].name+" can't be empty")
return return null
} }
} }
@@ -2744,6 +2744,7 @@ const AppCreator = (props) => {
authenticationOption.label = selectedApp.name+" authentication" authenticationOption.label = selectedApp.name+" authentication"
} }
console.log("PRE RETURN")
return ( return (
<div> <div>
<DialogContent> <DialogContent>
@@ -3032,7 +3033,7 @@ const AppCreator = (props) => {
Continue Continue
</Button> </Button>
</DialogActions> </DialogActions>
<ParsedActionHandler /> {/*<ParsedActionHandler />*/}
</FormControl> </FormControl>
</Dialog> </Dialog>
: null; : null;
+3 -1
View File
@@ -531,7 +531,7 @@ const Workflows = (props) => {
deleteWorkflow(selectedWorkflowId) deleteWorkflow(selectedWorkflowId)
setTimeout(() => { setTimeout(() => {
getAvailableWorkflows() getAvailableWorkflows()
}, 1000) }, 500)
} }
setDeleteModalOpen(false) setDeleteModalOpen(false)
}} color="primary"> }} color="primary">
@@ -1489,7 +1489,9 @@ const Workflows = (props) => {
window.location.pathname = "/workflows/"+responseJson["id"] window.location.pathname = "/workflows/"+responseJson["id"]
} else if (!redirect) { } else if (!redirect) {
// Update :) // Update :)
setTimeout(() => {
getAvailableWorkflows() getAvailableWorkflows()
}, 500)
setImportLoading(false) setImportLoading(false)
} else { } else {
alert.info("Successfully changed basic info for workflow") alert.info("Successfully changed basic info for workflow")