internal/worker: jobs/list returns JSON

Change the list endpoint to return JSON, so cmd/jobs can format
it the way it wants.

Change-Id: Ifbc20c1733584a0b12931239d4d011408cbe5c81
Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/498266
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
diff --git a/internal/worker/jobs.go b/internal/worker/jobs.go
index f96e4c2..4f7a09d 100644
--- a/internal/worker/jobs.go
+++ b/internal/worker/jobs.go
@@ -60,10 +60,7 @@
 		if err != nil {
 			return err
 		}
-		enc := json.NewEncoder(w)
-		enc.SetIndent("", "    ")
-		enc.Encode(job)
-		return nil
+		return writeJSON(w, job)
 
 	case "cancel":
 		if jobID == "" {
@@ -75,22 +72,30 @@
 		})
 
 	case "list":
-		var buf bytes.Buffer
-		fmt.Fprintf(&buf, "%-20s\tEnq\tCompl\tCanceled\n", "ID")
+		var joblist []*jobs.Job
 		err := db.ListJobs(ctx, func(j *jobs.Job, _ time.Time) error {
-			_, err := fmt.Fprintf(&buf, "%-20s\t%3d\t%3d\t%t\n",
-				j.ID(), j.NumEnqueued,
-				j.NumSkipped+j.NumFailed+j.NumErrored+j.NumSucceeded,
-				j.Canceled)
-			return err
+			joblist = append(joblist, j)
+			return nil
 		})
 		if err != nil {
 			return err
 		}
-		buf.WriteTo(w)
-		return nil
+		return writeJSON(w, joblist)
 
 	default:
 		return fmt.Errorf("unknown path %q: %w", path, derrors.InvalidArgument)
 	}
 }
+
+// writeJSON JSON-marshals v and writes it to w.
+// Marshal failures do not result in partial writes.
+func writeJSON(w io.Writer, v any) error {
+	var buf bytes.Buffer
+	enc := json.NewEncoder(&buf)
+	enc.SetIndent("", "    ")
+	if err := enc.Encode(v); err != nil {
+		return err
+	}
+	_, err := w.Write(buf.Bytes())
+	return err
+}
diff --git a/internal/worker/jobs_test.go b/internal/worker/jobs_test.go
index 59a4c0d..2a377f5 100644
--- a/internal/worker/jobs_test.go
+++ b/internal/worker/jobs_test.go
@@ -58,10 +58,10 @@
 		t.Fatal(err)
 	}
 	// Don't check for specific output, just make sure there's something
-	// that mentions the job ID.
+	// that mentions the job user.
 	got3 := buf.String()
-	if !strings.Contains(got3, job.ID()) {
-		t.Errorf("got\n%q\nwhich does not contain the job ID %q", got3, job.ID())
+	if !strings.Contains(got3, job.User) {
+		t.Errorf("got\n%q\nwhich does not contain the job user %q", got3, job.User)
 	}
 }