internal/relui: record task errors
This change introduces recording of task failures to the database. It
also improves UX of failures in the UI.
Updates golang/go#53207
Change-Id: Ic00c9a94228d3fc61c5b2e6da6f0b25d1c7d19dc
Reviewed-on: https://go-review.googlesource.com/c/build/+/410236
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Alex Rakoczy <alex@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/internal/relui/db/db.go b/internal/relui/db/db.go
index ba9b109..23b9bf7 100644
--- a/internal/relui/db/db.go
+++ b/internal/relui/db/db.go
@@ -1,4 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.13.0
package db
diff --git a/internal/relui/db/models.go b/internal/relui/db/models.go
index d64109b..c376a03 100644
--- a/internal/relui/db/models.go
+++ b/internal/relui/db/models.go
@@ -1,4 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.13.0
package db
diff --git a/internal/relui/db/workflows.sql.go b/internal/relui/db/workflows.sql.go
index 2d6622b..98ee991 100644
--- a/internal/relui/db/workflows.sql.go
+++ b/internal/relui/db/workflows.sql.go
@@ -1,4 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.13.0
// source: workflows.sql
package db
@@ -297,6 +299,7 @@
name = excluded.name,
finished = excluded.finished,
result = excluded.result,
+ error = excluded.error,
updated_at = excluded.updated_at
RETURNING workflow_id, name, finished, result, error, created_at, updated_at
`
diff --git a/internal/relui/listener.go b/internal/relui/listener.go
index 8d819dc..d57ddd3 100644
--- a/internal/relui/listener.go
+++ b/internal/relui/listener.go
@@ -47,7 +47,7 @@
Name: taskName,
Finished: state.Finished,
Result: sql.NullString{String: string(result), Valid: len(result) > 0},
- Error: sql.NullString{},
+ Error: sql.NullString{String: state.Error, Valid: state.Error != ""},
CreatedAt: updated,
UpdatedAt: updated,
})
diff --git a/internal/relui/listener_test.go b/internal/relui/listener_test.go
index cc1fa93..b014e5a 100644
--- a/internal/relui/listener_test.go
+++ b/internal/relui/listener_test.go
@@ -22,39 +22,74 @@
defer cancel()
dbp := testDB(ctx, t)
q := db.New(dbp)
- wfp := db.CreateWorkflowParams{ID: uuid.New()}
- wf, err := q.CreateWorkflow(ctx, wfp)
- if err != nil {
- t.Fatalf("q.CreateWorkflow(%v, %v) = %v, wanted no error", ctx, wfp, err)
- }
- l := &PGListener{db: dbp}
- state := &workflow.TaskState{
- Name: "TestTask",
- Finished: true,
- Result: struct{ Value int }{5},
- SerializedResult: []byte(`{"Value": 5}`),
- Error: "",
+ cases := []struct {
+ desc string
+ state *workflow.TaskState
+ want []db.Task
+ }{
+ {
+ desc: "records successful tasks",
+ state: &workflow.TaskState{
+ Name: "TestTask",
+ Finished: true,
+ Result: struct{ Value int }{5},
+ SerializedResult: []byte(`{"Value": 5}`),
+ Error: "",
+ },
+ want: []db.Task{
+ {
+ Name: "TestTask",
+ Finished: true,
+ Result: sql.NullString{String: `{"Value": 5}`, Valid: true},
+ CreatedAt: time.Now(), // cmpopts.EquateApproxTime
+ UpdatedAt: time.Now(), // cmpopts.EquateApproxTime
+ },
+ },
+ },
+ {
+ desc: "records failing tasks",
+ state: &workflow.TaskState{
+ Name: "TestTask",
+ Finished: true,
+ Result: struct{ Value int }{5},
+ SerializedResult: []byte(`{"Value": 5}`),
+ Error: "it's completely broken and hopeless",
+ },
+ want: []db.Task{
+ {
+ Name: "TestTask",
+ Finished: true,
+ Result: sql.NullString{String: `{"Value": 5}`, Valid: true},
+ Error: sql.NullString{String: "it's completely broken and hopeless", Valid: true},
+ CreatedAt: time.Now(), // cmpopts.EquateApproxTime
+ UpdatedAt: time.Now(), // cmpopts.EquateApproxTime
+ },
+ },
+ },
}
- err = l.TaskStateChanged(wf.ID, "TestTask", state)
- if err != nil {
- t.Fatalf("l.TaskStateChanged(%v, %q, %v) = %v, wanted no error", wf.ID, "TestTask", state, err)
- }
+ for _, c := range cases {
+ t.Run(c.desc, func(t *testing.T) {
+ wfp := db.CreateWorkflowParams{ID: uuid.New()}
+ wf, err := q.CreateWorkflow(ctx, wfp)
+ if err != nil {
+ t.Fatalf("q.CreateWorkflow(%v, %v) = %v, wanted no error", ctx, wfp, err)
+ }
- tasks, err := q.TasksForWorkflow(ctx, wf.ID)
- if err != nil {
- t.Fatalf("q.TasksForWorkflow(%v, %v) = %v, %v, wanted no error", ctx, wf.ID, tasks, err)
- }
- want := []db.Task{{
- WorkflowID: wf.ID,
- Name: "TestTask",
- Finished: true,
- Result: sql.NullString{String: `{"Value": 5}`, Valid: true},
- CreatedAt: time.Now(), // cmpopts.EquateApproxTime
- UpdatedAt: time.Now(), // cmpopts.EquateApproxTime
- }}
- if diff := cmp.Diff(want, tasks, cmpopts.EquateApproxTime(time.Minute)); diff != "" {
- t.Errorf("q.TasksForWorkflow(_, %q) mismatch (-want +got):\n%s", wf.ID, diff)
+ l := &PGListener{db: dbp}
+ err = l.TaskStateChanged(wf.ID, "TestTask", c.state)
+ if err != nil {
+ t.Fatalf("l.TaskStateChanged(%v, %q, %v) = %v, wanted no error", wf.ID, "TestTask", c.state, err)
+ }
+
+ tasks, err := q.TasksForWorkflow(ctx, wf.ID)
+ if err != nil {
+ t.Fatalf("q.TasksForWorkflow(%v, %v) = %v, %v, wanted no error", ctx, wf.ID, tasks, err)
+ }
+ if diff := cmp.Diff(c.want, tasks, cmpopts.EquateApproxTime(time.Minute), cmpopts.IgnoreFields(db.Task{}, "WorkflowID")); diff != "" {
+ t.Errorf("q.TasksForWorkflow(_, %q) mismatch (-want +got):\n%s", wf.ID, diff)
+ }
+ })
}
}
diff --git a/internal/relui/queries/workflows.sql b/internal/relui/queries/workflows.sql
index 91013ec..bb59c19 100644
--- a/internal/relui/queries/workflows.sql
+++ b/internal/relui/queries/workflows.sql
@@ -30,6 +30,7 @@
name = excluded.name,
finished = excluded.finished,
result = excluded.result,
+ error = excluded.error,
updated_at = excluded.updated_at
RETURNING *;
diff --git a/internal/relui/static/styles.css b/internal/relui/static/styles.css
index 9e28d75..7dce7f7 100644
--- a/internal/relui/static/styles.css
+++ b/internal/relui/static/styles.css
@@ -205,14 +205,12 @@
}
.TaskList-itemLogLine:nth-child(even) {
background-color: #fafafa;
+ font-family: monospace;
}
.TaskList-itemLogLineError {
background-color: #c9483c;
color: white;
-}
-.TaskList-errorBody {
- display: block;
- white-space: pre-wrap;
+ padding: 0.5rem 1rem;
}
.TaskList-item {
}
diff --git a/internal/relui/templates/home.html b/internal/relui/templates/home.html
index fecc52f..a7156aa 100644
--- a/internal/relui/templates/home.html
+++ b/internal/relui/templates/home.html
@@ -77,9 +77,9 @@
<tr class="TaskList-itemLogsRow">
<td class="TaskList-itemLogs" colspan="6">
{{if $task.Error.Valid}}
- <div class="TaskList-itemLogLine TaskList-itemLogLineError">
- <code class="TaskList-errorBody">{{$task.Error.Value}}</code>
- </div>
+ <div class="TaskList-itemLogLine TaskList-itemLogLineError">
+ {{- $task.Error.Value -}}
+ </div>
{{end}}
{{range $log := $.Logs $workflow.ID $task.Name}}
<div class="TaskList-itemLogLine">