diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index f9ea4397..7d162334 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -1054,7 +1054,7 @@ class AppBase: print(f"Failed to execute: {e}") self.logger.exception(f"Failed to execute {e}-{action['id']}") 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()) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 14add147..b799a89f 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -2955,6 +2955,7 @@ func getSpecificWebhook(resp http.ResponseWriter, request *http.Request) { } ctx := context.Background() + // FIXME: Schedule = trigger? schedule, err := getSchedule(ctx, workflowId) if err != nil { log.Printf("Failed setting schedule: %s", err) diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 995c8fc1..1e891291 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1601,10 +1601,30 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { for _, trigger := range workflow.Triggers { 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") 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 { workflow.Actions = []Action{} } @@ -1787,70 +1807,77 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { // Check to see if the whole app is valid if curapp.Name != action.AppName { - log.Printf("App %s doesn't exist.", action.AppName) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App %s doesn't exist"}`, action.AppName))) - return - } + workflow.Errors = append(workflow.Errors, fmt.Sprintf("App %s doesn't exist", action.AppName)) + action.Errors = append(action.Errors, "This app doesn't exist.") + action.IsValid = false + workflow.IsValid = false - // Check tosee if the appaction is valid - curappaction := WorkflowAppAction{} - for _, curAction := range curapp.Actions { - if action.Name == curAction.Name { - curappaction = curAction - break - } - } - - // Check to see if the action is valid - if curappaction.Name != action.Name { - log.Printf("Appaction %s doesn't exist.", action.Name) - 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) + // Append with errors + newActions = append(newActions, action) + log.Printf("App %s doesn't exist. Adding as error.", action.AppName) + //resp.WriteHeader(401) + //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App %s doesn't exist"}`, action.AppName))) + //return + } else { + // Check tosee if the appaction is valid + curappaction := WorkflowAppAction{} + for _, curAction := range curapp.Actions { + if action.Name == curAction.Name { + curappaction = curAction 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) + // Check to see if the action is valid + if curappaction.Name != action.Name { + log.Printf("Appaction %s doesn't exist.", action.Name) 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 } - } + // FIXME - check all parameters to see if they're valid + // Includes checking required fields - action.Parameters = newParams - newActions = append(newActions, action) + 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 + } + } + + // 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) } + 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) 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) { @@ -2627,6 +2670,8 @@ func stopSchedule(resp http.ResponseWriter, request *http.Request) { err = deleteSchedule(ctx, scheduleId) if err != nil { + log.Printf("Failed deleting schedule: %s", err) + if strings.Contains(err.Error(), "Job not found") { resp.WriteHeader(200) resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 353d36c9..89d28eab 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -576,6 +576,9 @@ const AngularWorkflow = (props) => { useworkflow.triggers = newTriggers useworkflow.branches = newBranches + // Errors are backend defined + useworkflow.errors = [] + setLastSaved(true) fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key, { method: 'PUT', @@ -599,6 +602,15 @@ const AngularWorkflow = (props) => { alert.error("Failed to save: "+responseJson.reason) } else { 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") } }) @@ -925,7 +937,46 @@ const AngularWorkflow = (props) => { const curapp = apps.find(a => a.name === curaction.app_name && a.app_version === curaction.app_version) if (!curapp || curapp === undefined) { 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) @@ -934,48 +985,8 @@ const AngularWorkflow = (props) => { } 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) } else if (data.type === "TRIGGER") { //console.log("Should handle trigger "+data.triggertype) @@ -1513,10 +1524,17 @@ const AngularWorkflow = (props) => { return response.json() }) .then((responseJson) => { + // No matter what, it's being stopped. 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 { - //alert.success("Successfully stopped schedule") + alert.success("Successfully stopped schedule") workflow.triggers[triggerindex].status = "stopped" trigger.status = "stopped" setSelectedTrigger(trigger) @@ -3255,13 +3273,17 @@ const AngularWorkflow = (props) => { } function sortByKey(array, key) { + if (array === undefined) { + return [] + } + return array.sort(function(a, b) { var x = a[key]; var y = b[key] 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 ?
@@ -3300,7 +3322,7 @@ const AngularWorkflow = (props) => { placeholder={selectedAction.label} onChange={selectedNameChange} /> - {selectedAction.authentication.length === 0 && requiresAuthentication ? + {selectedApp.name !== undefined && selectedAction.authentication !== undefined && selectedAction.authentication.length === 0 && requiresAuthentication ?
Authenticate {selectedApp.name}: @@ -3312,7 +3334,7 @@ const AngularWorkflow = (props) => {
: null} - {selectedAction.authentication.length > 0 ? + {selectedAction.authentication !== undefined && selectedAction.authentication.length > 0 ?
Authentication
@@ -5136,7 +5158,7 @@ const AngularWorkflow = (props) => { : -