Minor play button fixes for worker + backend

This commit is contained in:
Frikky
2023-12-07 21:54:52 +01:00
parent 02713941f0
commit bb37e3e55d
5 changed files with 235 additions and 65 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/h2non/filetype v1.1.3
github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.5.29
github.com/shuffle/shuffle-shared v0.5.30
golang.org/x/crypto v0.14.0
google.golang.org/api v0.125.0
google.golang.org/grpc v1.55.0
+3
View File
@@ -4801,6 +4801,7 @@ func initHandlers() {
// App specific
// From here down isnt checked for org specific
r.HandleFunc("/api/v1/apps/{key}/execute", executeSingleAction).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/{key}/run", executeSingleAction).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/categories", shuffle.GetActiveCategories).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/apps/categories/run", shuffle.RunCategoryAction).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/upload", handleAppZipUpload).Methods("POST", "OPTIONS")
@@ -4841,9 +4842,11 @@ func initHandlers() {
r.HandleFunc("/api/v1/workflows/search", shuffle.HandleWorkflowRunSearch).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/workflows/schedules", shuffle.HandleGetSchedules).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/workflows/{key}/executions", shuffle.GetWorkflowExecutions).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/workflows/{key}/executions/{key}/rerun", checkUnfinishedExecution).Methods("GET", "POST", "OPTIONS")
r.HandleFunc("/api/v1/workflows/{key}/executions/{key}/abort", shuffle.AbortExecution).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/workflows/{key}/schedule", scheduleWorkflow).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/workflows/download_remote", loadSpecificWorkflows).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/workflows/{key}/run", executeWorkflow).Methods("GET", "POST", "OPTIONS")
r.HandleFunc("/api/v1/workflows/{key}/execute", executeWorkflow).Methods("GET", "POST", "OPTIONS")
r.HandleFunc("/api/v1/workflows/{key}/schedule/{schedule}", stopSchedule).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/workflows/{key}/stream", shuffle.HandleStreamWorkflow).Methods("GET", "OPTIONS")
+181 -2
View File
@@ -1667,7 +1667,7 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
// FIXME - tmp name based on future companyname-companyId
// This leads to issues with overlaps. Should set limits and such instead
for _, environment := range execInfo.Environments {
log.Printf("[INFO] Execution: %s should execute onprem with execution environment \"%s\". Workflow: %s", workflowExecution.ExecutionId, environment, workflowExecution.Workflow.ID)
log.Printf("[INFO][%s] Execution: should execute onprem with execution environment \"%s\". Workflow: %s", workflowExecution.ExecutionId, environment, workflowExecution.Workflow.ID)
executionRequest := shuffle.ExecutionRequest{
ExecutionId: workflowExecution.ExecutionId,
@@ -3360,11 +3360,21 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
return
}
workflowExecution.Priority = 10
workflowExecution.Priority = 11
environments, err := shuffle.GetEnvironments(ctx, user.ActiveOrg.Id)
environment := "Shuffle"
if len(environments) >= 1 {
// Find default one
environment = environments[0].Name
for _, env := range environments {
if env.Default {
environment = env.Name
break
}
}
} else {
log.Printf("[ERROR] No environments found for org %s. Exiting", user.ActiveOrg.Id)
resp.WriteHeader(401)
@@ -3372,6 +3382,14 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
return
}
// Enforcing same env for job + run to be default
// FIXME: Should use environment that is in the source workflow if it exists
for i, _ := range workflowExecution.Workflow.Actions {
workflowExecution.Workflow.Actions[i].Environment = environment
workflowExecution.Workflow.Actions[i].Label = "TMP"
}
shuffle.SetWorkflowExecution(ctx, workflowExecution, false)
log.Printf("[INFO] Execution (single action): %s should execute onprem with execution environment \"%s\". Workflow: %s", workflowExecution.ExecutionId, environment, workflowExecution.Workflow.ID)
executionRequest := shuffle.ExecutionRequest{
@@ -3379,6 +3397,7 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
WorkflowId: workflowExecution.Workflow.ID,
Authorization: workflowExecution.Authorization,
Environments: []string{environment},
Priority: 11,
}
executionRequest.Priority = workflowExecution.Priority
@@ -3399,6 +3418,9 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
log.Printf("[ERROR] Failed to marshal retStruct in single execution: %s", err)
}
// Deleting as this is a single action and doesn't need to be stored
shuffle.DeleteKey(ctx, "workflowexecution", executionRequest.ExecutionId)
resp.WriteHeader(200)
resp.Write([]byte(returnBytes))
}
@@ -3995,3 +4017,160 @@ func checkWorkflowApp(workflowApp shuffle.WorkflowApp) error {
return nil
}
func checkUnfinishedExecution(resp http.ResponseWriter, request *http.Request) {
cors := shuffle.HandleCors(resp, request)
if cors {
return
}
location := strings.Split(request.URL.String(), "/")
var fileId string
if location[1] == "api" {
if len(location) <= 4 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
fileId = location[4]
}
if len(fileId) != 36 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Workflow ID to abort is not valid"}`))
return
}
executionId := location[6]
if len(executionId) != 36 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "ExecutionID not valid"}`))
return
}
ctx := shuffle.GetContext(request)
exec, err := shuffle.GetWorkflowExecution(ctx, executionId)
if err != nil {
log.Printf("[ERROR] Failed getting execution (rerun workflow - 1) %s: %s", executionId, err)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist (abort)."}`, executionId)))
return
}
apikey := request.Header.Get("Authorization")
parsedKey := ""
if strings.HasPrefix(apikey, "Bearer ") {
apikeyCheck := strings.Split(apikey, " ")
if len(apikeyCheck) == 2 {
parsedKey = apikeyCheck[1]
}
}
// ONLY allowed to run automatically with the same auth (july 2022)
if exec.Authorization != parsedKey {
user, err := shuffle.HandleApiAuthentication(resp, request)
if err != nil {
log.Printf("[ERROR][%s] Bad authorization key for execution (rerun workflow - 3): %s", executionId, err)
resp.WriteHeader(403)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed because you're not authorized to see this workflow (3)."}`)))
return
}
// Check if user is in the correct org
if user.ActiveOrg.Id == exec.ExecutionOrg && user.Role != "org-reader" {
log.Printf("[AUDIT][%s] User %s (%s) is force continuing execution from org access", executionId, user.Username, user.Id)
} else if user.SupportAccess {
log.Printf("[AUDIT][%s] User %s (%s) is force continuing execution with support access", executionId, user.Username, user.Id)
} else {
log.Printf("[ERROR][%s] Bad authorization key for continue execution (rerun workflow - 2): %s", executionId, err)
resp.WriteHeader(403)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed because you're not authorized to see this workflow (2)."}`)))
return
}
}
// Meant as a function that periodically checks whether previous executions have finished or not.
// Should probably be based on executedIds and finishedIds
// Schedule a check in the future instead?
// Auth vs execution check!
extraInputs := 0
for _, trigger := range exec.Workflow.Triggers {
if trigger.Name == "User Input" && trigger.AppName == "User Input" {
extraInputs += 1
//exec.Workflow.Actions = append(exec.Workflow.Actions, shuffle.Action{
// ID: trigger.ID,
// Label: trigger.Label,
// Name: trigger.Name,
//})
} else if trigger.Name == "Shuffle Workflow" && trigger.AppName == "Shuffle Workflow" {
extraInputs += 1
//exec.Workflow.Actions = append(exec.Workflow.Actions, shuffle.Action{
// ID: trigger.ID,
// Label: trigger.Label,
// Name: trigger.Name,
//})
}
}
if exec.Status != "ABORTED" && exec.Status != "FINISHED" && exec.Status != "FAILURE" {
log.Printf("[DEBUG][%s] Rechecking execution and its status to send to backend IF the status is EXECUTING (%s - %d/%d finished)", exec.ExecutionId, exec.Status, len(exec.Results), len(exec.Workflow.Actions)+extraInputs)
}
// Usually caused by issue during startup
if exec.Status == "" {
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No status for the execution"}`)))
return
}
if exec.Status != "EXECUTING" {
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Already finished"}`)))
return
}
// Force it back in the queue to be executed
if len(exec.Workflow.Actions) == 0 {
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Not a cloud env workflow. Only rerunning cloud env."}`)))
return
}
log.Printf("[DEBUG][%s] Workflow: %s (%s)", exec.ExecutionId, exec.Workflow.Name, exec.Workflow.ID)
if exec.Workflow.ID == "" || exec.Workflow.Name == "" {
log.Printf("[ERROR][%s] No workflow ID found for execution", exec.ExecutionId)
shuffle.DeleteKey(ctx, "workflowexecution", exec.ExecutionId)
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "No workflow name / ID found. Can't run. Contact support@shuffler.io if this persists."}`)))
return
}
environment := exec.Workflow.Actions[0].Environment
log.Printf("[DEBUG][%s] Not a cloud env workflow. Re-adding job in queue for env %s.", exec.ExecutionId, environment)
parsedEnv := fmt.Sprintf("%s_%s", strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(environment, " ", "-"), "_", "-")), exec.ExecutionOrg)
log.Printf("[DEBUG][%s] Adding new run job to env (2): %s", exec.ExecutionId, parsedEnv)
executionRequest := shuffle.ExecutionRequest{
ExecutionId: exec.ExecutionId,
WorkflowId: exec.Workflow.ID,
Authorization: exec.Authorization,
Environments: []string{environment},
}
// Increase priority on reruns to catch up
executionRequest.Priority = 11
err = shuffle.SetWorkflowQueue(ctx, executionRequest, parsedEnv)
if err != nil {
log.Printf("[ERROR] Failed adding execution to db: %s", err)
}
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Reran workflow in %s"}`, parsedEnv)))
}
+23 -8
View File
@@ -1597,14 +1597,29 @@ const CodeEditor = (props) => {
Expected Output
</span>
<IconButton disabled={executing} color="secondary" style={{border: `1px solid ${theme.palette.primary.main}`, marginLeft: 250, padding: 8}} variant="contained" onClick={() => {
executeSingleAction(expOutput)
}}>
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' or 'execute python' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top">
{executing ? <CircularProgress style={{height: 18, width: 18, }} /> : <PlayArrowIcon style={{height: 18, width: 18, }} /> }
</Tooltip>
</IconButton>
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' or 'execute python' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top">
<Button
variant="outlined"
disabled={executing}
color="primary"
style={{
border: `1px solid ${theme.palette.primary.main}`,
marginLeft: 200,
maxHeight: 35,
minWidth: 70,
}}
variant="contained"
onClick={() => {
executeSingleAction(expOutput)
}}
>
{executing ?
<CircularProgress style={{height: 18, width: 18, }} />
:
<span>Try it <PlayArrowIcon style={{height: 18, width: 18, marginBottom: -4, marginLeft: 5, }} /> </span>
}
</Button>
</Tooltip>
</DialogTitle>
}
+27 -54
View File
@@ -11151,15 +11151,7 @@ const AngularWorkflow = (defaultprops) => {
style={{ marginTop: 10 }}
label={<div style={{ color: "white" }}>Wait for results</div>}
/>
<Divider
style={{
marginTop: "20px",
height: "1px",
width: "100%",
backgroundColor: "rgb(91, 96, 100)",
}}
/>
<div style={{ flex: "6", marginTop: "20px" }}>
<div style={{ flex: "6", marginTop: 10, }}>
<div>
<div style={{ display: "flex" }}>
<div
@@ -12543,7 +12535,7 @@ const AngularWorkflow = (defaultprops) => {
// email,sms,app ...
workflow.triggers[selectedTriggerIndex].parameters[2] = {
name: "type",
value: "email",
value: "subflow",
};
workflow.triggers[selectedTriggerIndex].parameters[3] = {
@@ -12624,16 +12616,7 @@ const AngularWorkflow = (defaultprops) => {
/>
</div>
*/}
<Divider
style={{
marginTop: "20px",
height: "1px",
width: "100%",
backgroundColor: "rgb(91, 96, 100)",
}}
/>
<div style={{ flex: "6", marginTop: "20px" }}>
<b>Parameters</b>
<div style={{ flex: "6", marginTop: 10, }}>
<div
style={{
marginTop: "20px",
@@ -12641,17 +12624,11 @@ const AngularWorkflow = (defaultprops) => {
display: "flex",
}}
>
<div
style={{
width: "17px",
height: "17px",
borderRadius: 17 / 2,
backgroundColor: "#f85a3e",
marginRight: "10px",
}}
/>
<div style={{ flex: "10" }}>
<b>Information</b>
<Typography variant="body2" color="textSecondary">
The information you want to show the user. Supports variables.
</Typography>
</div>
</div>
<TextField
@@ -12682,17 +12659,11 @@ const AngularWorkflow = (defaultprops) => {
display: "flex",
}}
>
<div
style={{
width: "17px",
height: "17px",
borderRadius: 17 / 2,
backgroundColor: "#f85a3e",
marginRight: "10px",
}}
/>
<div style={{ flex: "10" }}>
<b>Contact options</b>
<b>Input options</b>
<Typography variant="body2" color="textSecondary">
Use subflows to connect to any app you want, or use the default email and sms options
</Typography>
</div>
</div>
<FormGroup
@@ -12702,6 +12673,20 @@ const AngularWorkflow = (defaultprops) => {
<FormControlLabel
control={
<Checkbox
checked={workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow")}
onChange={() => {
setTriggerOptionsWrapper("subflow");
}}
color="primary"
value="subflow"
/>
}
label={<div style={{ color: "white" }}>Subflow</div>}
/>
<FormControlLabel
control={
<Checkbox
disabled={!isCloud}
checked={
workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("email")
}
@@ -12717,6 +12702,7 @@ const AngularWorkflow = (defaultprops) => {
<FormControlLabel
control={
<Checkbox
disabled={!isCloud}
checked={workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers[selectedTriggerIndex].parameters !== undefined && workflow.triggers[selectedTriggerIndex].parameters.length > 0 && workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value !== undefined ? workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("sms") : false}
onChange={() => {
setTriggerOptionsWrapper("sms");
@@ -12728,21 +12714,8 @@ const AngularWorkflow = (defaultprops) => {
}
label={<div style={{ color: "white" }}>SMS</div>}
/>
<FormControlLabel
control={
<Checkbox
checked={workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow")}
onChange={() => {
setTriggerOptionsWrapper("subflow");
}}
color="primary"
value="subflow"
/>
}
label={<div style={{ color: "white" }}>Subflow</div>}
/>
</FormGroup>
{workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow") ? (
{workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow") ? (
<div style={{ }}>
{workflows === undefined ||
workflows === null ||
@@ -12848,7 +12821,7 @@ const AngularWorkflow = (defaultprops) => {
},
}}
fullWidth
label="Email"
label="Email"
color="primary"
required
placeholder={"mail1@company.com,mail2@company.com"}