Fixed workflow import/export issues

This commit is contained in:
frikky
2020-09-29 19:01:02 +02:00
parent 62c01b2df8
commit f371d67f03
7 changed files with 189 additions and 112 deletions
+1 -1
View File
@@ -1054,7 +1054,7 @@ class AppBase:
print(f"Failed to execute: {e}") print(f"Failed to execute: {e}")
self.logger.exception(f"Failed to execute {e}-{action['id']}") self.logger.exception(f"Failed to execute {e}-{action['id']}")
action_result["status"] = "FAILURE" action_result["status"] = "FAILURE"
action_result["result"] = "General exception: %s" % e action_result["result"] = f"General exception: {e}"
action_result["completed_at"] = int(time.time()) action_result["completed_at"] = int(time.time())
+1
View File
@@ -2955,6 +2955,7 @@ func getSpecificWebhook(resp http.ResponseWriter, request *http.Request) {
} }
ctx := context.Background() ctx := context.Background()
// FIXME: Schedule = trigger?
schedule, err := getSchedule(ctx, workflowId) schedule, err := getSchedule(ctx, workflowId)
if err != nil { if err != nil {
log.Printf("Failed setting schedule: %s", err) log.Printf("Failed setting schedule: %s", err)
+100 -55
View File
@@ -1601,10 +1601,30 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
for _, trigger := range workflow.Triggers { for _, trigger := range workflow.Triggers {
log.Printf("Trigger %s: %s", trigger.TriggerType, trigger.Status) log.Printf("Trigger %s: %s", trigger.TriggerType, trigger.Status)
// Check if it's actually running
// FIXME: Do this for other triggers too
if trigger.TriggerType == "SCHEDULE" && trigger.Status != "uninitialized" {
schedule, err := getSchedule(ctx, trigger.ID)
if err != nil {
trigger.Status = "stopped"
} else if schedule.Id == "" {
trigger.Status = "stopped"
}
}
//log.Println("TRIGGERS") //log.Println("TRIGGERS")
allNodes = append(allNodes, trigger.ID) allNodes = append(allNodes, trigger.ID)
} }
for _, variable := range workflow.WorkflowVariables {
if len(variable.Value) == 0 {
log.Printf("Can't have an empty variable: %s", variable.Name)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Variable %s can't be empty"}`, variable.Name)))
return
}
}
if len(workflow.Actions) == 0 { if len(workflow.Actions) == 0 {
workflow.Actions = []Action{} workflow.Actions = []Action{}
} }
@@ -1787,70 +1807,77 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
// Check to see if the whole app is valid // Check to see if the whole app is valid
if curapp.Name != action.AppName { if curapp.Name != action.AppName {
log.Printf("App %s doesn't exist.", action.AppName) workflow.Errors = append(workflow.Errors, fmt.Sprintf("App %s doesn't exist", action.AppName))
resp.WriteHeader(401) action.Errors = append(action.Errors, "This app doesn't exist.")
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App %s doesn't exist"}`, action.AppName))) action.IsValid = false
return workflow.IsValid = false
}
// Check tosee if the appaction is valid // Append with errors
curappaction := WorkflowAppAction{} newActions = append(newActions, action)
for _, curAction := range curapp.Actions { log.Printf("App %s doesn't exist. Adding as error.", action.AppName)
if action.Name == curAction.Name { //resp.WriteHeader(401)
curappaction = curAction //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App %s doesn't exist"}`, action.AppName)))
break //return
} } else {
} // Check tosee if the appaction is valid
curappaction := WorkflowAppAction{}
// Check to see if the action is valid for _, curAction := range curapp.Actions {
if curappaction.Name != action.Name { if action.Name == curAction.Name {
log.Printf("Appaction %s doesn't exist.", action.Name) curappaction = curAction
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
// FIXME - check all parameters to see if they're valid
// Includes checking required fields
newParams := []WorkflowAppActionParameter{}
for _, param := range curappaction.Parameters {
found := false
// Handles check for parameter exists + value not empty in used fields
for _, actionParam := range action.Parameters {
if actionParam.Name == param.Name {
found = true
if actionParam.Value == "" && actionParam.Variant == "STATIC_VALUE" && actionParam.Required == true {
log.Printf("Appaction %s with required param '%s' is empty.", action.Name, param.Name)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name)))
return
}
if actionParam.Variant == "" {
actionParam.Variant = "STATIC_VALUE"
}
newParams = append(newParams, actionParam)
break break
} }
} }
// Handles check for required params // Check to see if the action is valid
if !found && param.Required { if curappaction.Name != action.Name {
log.Printf("Appaction %s with required param %s doesn't exist.", action.Name, param.Name) log.Printf("Appaction %s doesn't exist.", action.Name)
resp.WriteHeader(401) resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name))) resp.Write([]byte(`{"success": false}`))
return return
} }
} // FIXME - check all parameters to see if they're valid
// Includes checking required fields
action.Parameters = newParams newParams := []WorkflowAppActionParameter{}
newActions = append(newActions, action) for _, param := range curappaction.Parameters {
found := false
// Handles check for parameter exists + value not empty in used fields
for _, actionParam := range action.Parameters {
if actionParam.Name == param.Name {
found = true
if actionParam.Value == "" && actionParam.Variant == "STATIC_VALUE" && actionParam.Required == true {
log.Printf("Appaction %s with required param '%s' is empty.", action.Name, param.Name)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name)))
return
}
if actionParam.Variant == "" {
actionParam.Variant = "STATIC_VALUE"
}
newParams = append(newParams, actionParam)
break
}
}
// Handles check for required params
if !found && param.Required {
log.Printf("Appaction %s with required param %s doesn't exist.", action.Name, param.Name)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name)))
return
}
}
action.Parameters = newParams
newActions = append(newActions, action)
}
} }
} }
@@ -1873,9 +1900,25 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
log.Printf("Failed to change total actions data: %s", err) log.Printf("Failed to change total actions data: %s", err)
} }
type returnData struct {
Success bool `json:"success"`
Errors []string `json:"errors"`
}
returndata := returnData{
Success: true,
Errors: workflow.Errors,
}
log.Printf("Saved new version of workflow %s (%s)", workflow.Name, fileId) log.Printf("Saved new version of workflow %s (%s)", workflow.Name, fileId)
resp.WriteHeader(200) resp.WriteHeader(200)
resp.Write([]byte(`{"success": true}`)) newBody, err := json.Marshal(returndata)
if err != nil {
resp.Write([]byte(`{"success": true}`))
return
}
resp.Write(newBody)
} }
func getWorkflowLocal(fileId string, request *http.Request) ([]byte, error) { func getWorkflowLocal(fileId string, request *http.Request) ([]byte, error) {
@@ -2627,6 +2670,8 @@ func stopSchedule(resp http.ResponseWriter, request *http.Request) {
err = deleteSchedule(ctx, scheduleId) err = deleteSchedule(ctx, scheduleId)
if err != nil { if err != nil {
log.Printf("Failed deleting schedule: %s", err)
if strings.Contains(err.Error(), "Job not found") { if strings.Contains(err.Error(), "Job not found") {
resp.WriteHeader(200) resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
+71 -49
View File
@@ -576,6 +576,9 @@ const AngularWorkflow = (props) => {
useworkflow.triggers = newTriggers useworkflow.triggers = newTriggers
useworkflow.branches = newBranches useworkflow.branches = newBranches
// Errors are backend defined
useworkflow.errors = []
setLastSaved(true) setLastSaved(true)
fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key, { fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key, {
method: 'PUT', method: 'PUT',
@@ -599,6 +602,15 @@ const AngularWorkflow = (props) => {
alert.error("Failed to save: "+responseJson.reason) alert.error("Failed to save: "+responseJson.reason)
} else { } else {
success = true success = true
if (responseJson.errors !== undefined) {
console.log(responseJson)
workflow.errors = responseJson.errors
if (responseJson.errors.length === 0) {
workflow.isValid = true
}
setWorkflow(workflow)
}
alert.success("Successfully saved workflow") alert.success("Successfully saved workflow")
} }
}) })
@@ -925,7 +937,46 @@ const AngularWorkflow = (props) => {
const curapp = apps.find(a => a.name === curaction.app_name && a.app_version === curaction.app_version) const curapp = apps.find(a => a.name === curaction.app_name && a.app_version === curaction.app_version)
if (!curapp || curapp === undefined) { if (!curapp || curapp === undefined) {
alert.error("App "+curaction.app_name+" not found. Did someone delete it?") alert.error("App "+curaction.app_name+" not found. Did someone delete it?")
return //return
} else {
setRequiresAuthentication(curapp.authentication.required && curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null)
if (curapp.authentication.required) {
// Setup auth here :)
const authenticationOptions = []
var findAuthId = ""
if (curaction.authentication_id !== null && curaction.authentication_id !== undefined && curaction.authentication_id.length > 0) {
findAuthId = curaction.authentication_id
}
var tmpAuth = JSON.parse(JSON.stringify(appAuthentication))
for (var key in tmpAuth) {
var item = tmpAuth[key]
const newfields = {}
for (var filterkey in item.fields) {
newfields[item.fields[filterkey].key] = item.fields[filterkey].value
}
item.fields = newfields
if (item.app.name === curapp.name) {
authenticationOptions.push(item)
if (item.id === findAuthId) {
curaction.selectedAuthentication = item
}
}
}
curaction.authentication = authenticationOptions
if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") {
curaction.selectedAuthentication = {}
}
} else {
curaction.authentication = []
curaction.authentication_id = ""
curaction.selectedAuthentication = {}
}
setSelectedApp(curapp)
} }
var env = environments.find(a => a.Name === curaction.environment) var env = environments.find(a => a.Name === curaction.environment)
@@ -934,48 +985,8 @@ const AngularWorkflow = (props) => {
} }
setSelectedActionEnvironment(env) setSelectedActionEnvironment(env)
setSelectedActionName(curaction.name) setSelectedActionName(curaction.name)
setRequiresAuthentication(curapp.authentication.required && curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null)
if (curapp.authentication.required) {
// Setup auth here :)
const authenticationOptions = []
var findAuthId = ""
if (curaction.authentication_id !== null && curaction.authentication_id !== undefined && curaction.authentication_id.length > 0) {
findAuthId = curaction.authentication_id
}
var tmpAuth = JSON.parse(JSON.stringify(appAuthentication))
for (var key in tmpAuth) {
var item = tmpAuth[key]
const newfields = {}
for (var filterkey in item.fields) {
newfields[item.fields[filterkey].key] = item.fields[filterkey].value
}
item.fields = newfields
if (item.app.name === curapp.name) {
authenticationOptions.push(item)
if (item.id === findAuthId) {
curaction.selectedAuthentication = item
}
}
}
curaction.authentication = authenticationOptions
if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") {
curaction.selectedAuthentication = {}
}
} else {
curaction.authentication = []
curaction.authentication_id = ""
curaction.selectedAuthentication = {}
}
setSelectedApp(curapp)
setSelectedAction(curaction) setSelectedAction(curaction)
} else if (data.type === "TRIGGER") { } else if (data.type === "TRIGGER") {
//console.log("Should handle trigger "+data.triggertype) //console.log("Should handle trigger "+data.triggertype)
@@ -1513,10 +1524,17 @@ const AngularWorkflow = (props) => {
return response.json() return response.json()
}) })
.then((responseJson) => { .then((responseJson) => {
// No matter what, it's being stopped.
if (!responseJson.success) { if (!responseJson.success) {
//alert.error("Failed to delete schedule: " + responseJson.reason) alert.error("Failed to stop schedule: " + responseJson.reason)
workflow.triggers[triggerindex].status = "stopped"
trigger.status = "stopped"
setSelectedTrigger(trigger)
setWorkflow(workflow)
saveWorkflow(workflow)
} else { } else {
//alert.success("Successfully stopped schedule") alert.success("Successfully stopped schedule")
workflow.triggers[triggerindex].status = "stopped" workflow.triggers[triggerindex].status = "stopped"
trigger.status = "stopped" trigger.status = "stopped"
setSelectedTrigger(trigger) setSelectedTrigger(trigger)
@@ -3255,13 +3273,17 @@ const AngularWorkflow = (props) => {
} }
function sortByKey(array, key) { function sortByKey(array, key) {
if (array === undefined) {
return []
}
return array.sort(function(a, b) { return array.sort(function(a, b) {
var x = a[key]; var y = b[key] var x = a[key]; var y = b[key]
return ((x < y) ? -1 : ((x > y) ? 1 : 0)) return ((x < y) ? -1 : ((x > y) ? 1 : 0))
}) })
} }
const appApiView = Object.getOwnPropertyNames(selectedAction).length > 0 && Object.getOwnPropertyNames(selectedApp).length > 0 ? const appApiView = Object.getOwnPropertyNames(selectedAction).length > 0 ?
<div style={appApiViewStyle}> <div style={appApiViewStyle}>
<div style={{display: "flex", minHeight: 40, marginBottom: 30}}> <div style={{display: "flex", minHeight: 40, marginBottom: 30}}>
<div style={{flex: 1}}> <div style={{flex: 1}}>
@@ -3300,7 +3322,7 @@ const AngularWorkflow = (props) => {
placeholder={selectedAction.label} placeholder={selectedAction.label}
onChange={selectedNameChange} onChange={selectedNameChange}
/> />
{selectedAction.authentication.length === 0 && requiresAuthentication ? {selectedApp.name !== undefined && selectedAction.authentication !== undefined && selectedAction.authentication.length === 0 && requiresAuthentication ?
<div style={{marginTop: 15}}> <div style={{marginTop: 15}}>
Authenticate {selectedApp.name}: Authenticate {selectedApp.name}:
<Tooltip color="primary" title={"Add authentication option"} placement="top"> <Tooltip color="primary" title={"Add authentication option"} placement="top">
@@ -3312,7 +3334,7 @@ const AngularWorkflow = (props) => {
</Tooltip> </Tooltip>
</div> </div>
: null} : null}
{selectedAction.authentication.length > 0 ? {selectedAction.authentication !== undefined && selectedAction.authentication.length > 0 ?
<div style={{marginTop: "20px"}}> <div style={{marginTop: "20px"}}>
Authentication Authentication
<div style={{display: "flex"}}> <div style={{display: "flex"}}>
@@ -5136,7 +5158,7 @@ const AngularWorkflow = (props) => {
</Tooltip> </Tooltip>
: :
<Tooltip color="primary" title="Test execution" placement="top"> <Tooltip color="primary" title="Test execution" placement="top">
<Button disabled={executionRequestStarted} style={{height: boxSize, width: boxSize}} color="primary" variant="contained" onClick={() => { <Button disabled={executionRequestStarted || !workflow.isValid} style={{height: boxSize, width: boxSize}} color="primary" variant="contained" onClick={() => {
executeWorkflow() executeWorkflow()
}}> }}>
<PlayArrowIcon style={{ fontSize: 60}} /> <PlayArrowIcon style={{ fontSize: 60}} />
@@ -5204,7 +5226,7 @@ const AngularWorkflow = (props) => {
return null return null
} }
if (Object.getOwnPropertyNames(selectedAction).length > 0 && Object.getOwnPropertyNames(selectedApp).length > 0) { if (Object.getOwnPropertyNames(selectedAction).length > 0) {
//console.time('ACTIONSTART') //console.time('ACTIONSTART')
return( return(
<div style={rightsidebarStyle}> <div style={rightsidebarStyle}>
-2
View File
@@ -577,8 +577,6 @@ const AppCreator = (props) => {
} }
} }
console.log("SCHEMES: ", securitySchemes)
setActions(newActions) setActions(newActions)
setIsAppLoaded(true) setIsAppLoaded(true)
} }
+7 -4
View File
@@ -588,7 +588,7 @@ const Workflows = (props) => {
} }
return ( return (
<Paper key={data.name} square style={paperAppStyle} onClick={() => { <Paper key={data.execution_id} square style={paperAppStyle} onClick={() => {
setSelectedExecution(data) setSelectedExecution(data)
}}> }}>
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: boxWidth, backgroundColor: boxColor}} /> <div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: boxWidth, backgroundColor: boxColor}} />
@@ -683,7 +683,7 @@ const Workflows = (props) => {
} }
return ( return (
<Paper key={data.name} square style={resultPaperAppStyle} onClick={() => {}}> <Paper key={data.execution_id} square style={resultPaperAppStyle} onClick={() => {}}>
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", marginRight: "5px", width: boxWidth, backgroundColor: boxColor}}> <div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", marginRight: "5px", width: boxWidth, backgroundColor: boxColor}}>
</div> </div>
<Grid container style={{margin: "10px 10px 10px 10px", flex: "1"}}> <Grid container style={{margin: "10px 10px 10px 10px", flex: "1"}}>
@@ -716,9 +716,11 @@ const Workflows = (props) => {
const resultsHandler = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ? const resultsHandler = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ?
<div> <div>
{selectedExecution.results.sort((a, b) => a.started_at - b.started_at).map(data => { {selectedExecution.results.sort((a, b) => a.started_at - b.started_at).map((data, index) => {
return ( return (
resultsPaper(data) <div key={index}>
{resultsPaper(data)}
</div>
) )
})} })}
</div> </div>
@@ -913,6 +915,7 @@ const Workflows = (props) => {
const importFiles = (event) => { const importFiles = (event) => {
console.log("Importing!")
const file = event.target.value const file = event.target.value
if (event.target.files.length > 0) { if (event.target.files.length > 0) {
for (var key in event.target.files) { for (var key in event.target.files) {
+9 -1
View File
@@ -694,7 +694,15 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
// FIXME: Force killing a worker should result in a notification somewhere // FIXME: Force killing a worker should result in a notification somewhere
if len(nextActions) == 0 { if len(nextActions) == 0 {
log.Printf("No next action. Finished? Result vs Actions: %d - %d", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)) log.Printf("No next action. Finished? Result vs Actions: %d - %d", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions))
if len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions) { //exit := true
//for _, item := range workflowExecution.Results {
// if item == "EXECUTING" {
// exit = false
// break
// }
//}
if exit && len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions) {
shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID) shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
} }