internal/relui: format task results on home

Render the workflow task results as a table using a css grid layout,
rather than a table, to allow for us to expand rows to show logs and
eventually other results without using javascript.

For golang/go#47401

Change-Id: I1ab1bb3a1b87d2e864b769eeb232336af98a306d
Reviewed-on: https://go-review.googlesource.com/c/build/+/353349
Trust: Alexander Rakoczy <alex@golang.org>
Run-TryBot: Alexander Rakoczy <alex@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/internal/relui/static/styles.css b/internal/relui/static/styles.css
index 0a2ba88..0f2c51c 100644
--- a/internal/relui/static/styles.css
+++ b/internal/relui/static/styles.css
@@ -69,48 +69,94 @@
   padding: 0;
 }
 .WorkflowList-title {
+  font-weight: normal;
+  margin: 0.875rem 0 0.5rem 0;
+}
+.WorkflowList-params {
+  border: none;
+  border-spacing: 0;
+}
+.WorkflowList-titleTime {
+  font-size: 1rem;
+}
+.WorkflowList-paramData:first-child {
+  text-transform: capitalize;
+}
+.WorkflowList-paramData {
+  font-size: 0.875rem;
 }
 .WorkflowList-sectionTitle {
   font-weight: normal;
-  margin-bottom: 0.5rem;
+  letter-spacing: normal;
+  margin: 1rem 0 0.5rem 0;
 }
 .WorkflowList-item {
   background: #fff;
-  margin-top: 1rem;
-  padding: 0 0.5rem;
   border: 0.0625rem solid #d6d6d6;
   border-radius: 0.0625rem;
+  margin-top: 1rem;
+  padding: 0 0.5rem;
 }
 .TaskList {
+  align-items: center;
   border-bottom: 0.0625rem solid #d6d6d6;
-  border-top: 0.0625rem solid #d6d6d6;
+  display: grid;
+  grid-auto-rows: minmax(1.5rem, auto);
+  grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
   margin-bottom: 1rem;
 }
-.TaskList-item + .TaskList-item {
+.TaskList-itemCol {
   border-top: 0.0625rem solid #d6d6d6;
+  overflow: hidden;
+  padding: 0.25rem 0;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 .TaskList-itemSummary {
   align-items: center;
   cursor: pointer;
-  justify-content: space-between;
+  display: contents;
+  font-size: 0.8125rem;
   padding: 0.5rem;
 }
 .TaskList-itemSummary:hover {
   background-color: #fafafa;
 }
+.TaskList-itemSummary:hover > .TaskList-itemCol {
+  background-color: #fafafa;
+}
+.TaskList-itemDetails {
+  display: contents;
+}
+.TaskList-itemDetails .TaskList-itemStatus::before {
+  content: "▸";
+}
+.TaskList-itemDetails[open] .TaskList-itemStatus::before {
+  content: "▾";
+}
 .TaskList-itemLogs {
   background-color: #f5f5f5;
   box-shadow: inset 0 6px 6px -8px #888;
-  font-size: 0.875rem;
+  font-size: 0.8125rem;
+  grid-column: 1/6;
   margin: 0;
   padding: 1rem 0;
 }
 .TaskList-itemLogLine {
+  grid-row: 6;
   padding: 0 1rem;
 }
 .TaskList-itemLogLine:nth-child(even) {
   background-color: #fafafa;
 }
+.TaskList-item {
+  display: contents;
+}
+.TaskList-itemHeader {
+  align-items: center;
+  font-size: 0.8125rem;
+  font-weight: bold;
+}
 .Button {
   background: #375eab;
   border-radius: 0.1875rem;
diff --git a/internal/relui/templates/home.html b/internal/relui/templates/home.html
index 60e17a8..cd8f73c 100644
--- a/internal/relui/templates/home.html
+++ b/internal/relui/templates/home.html
@@ -13,24 +13,55 @@
       {{range $workflow := .Workflows}}
         <li class="WorkflowList-item">
           <h3 class="WorkflowList-title">
-            {{$workflow.Name.String}} -
-            {{index $workflow.ID}}
+            {{$workflow.Name.String}}
+            <span class="WorkflowList-titleTime">
+              {{$workflow.CreatedAt.UTC.Format "2006/01/02 15:04 MST"}}
+            </span>
           </h3>
+          <table class="WorkflowList-params">
+            <tbody>
+              {{range $name, $value := $.WorkflowParams $workflow}}
+                <tr>
+                  <td class="WorkflowList-paramData">{{$name}}:</td>
+                  <td class="WorkflowList-paramData">{{$value}}</td>
+                </tr>
+              {{end}}
+            </tbody>
+          </table>
           <h4 class="WorkflowList-sectionTitle">Tasks</h4>
           <ul class="TaskList">
+            <li class="TaskList-item TaskList-itemHeader">
+              <span class="TaskList-itemStatus">Status</span>
+              <span class="TaskList-itemName">Name</span>
+              <span class="TaskList-itemCreated">Started</span>
+              <span class="TaskList-itemUpdated">Updated</span>
+              <span class="TaskList-itemResult">Result</span>
+            </li>
             {{$tasks := index $.WorkflowTasks $workflow.ID}}
             {{range $task := $tasks}}
               <li class="TaskList-item">
                 <details class="TaskList-itemDetails">
                   <summary class="TaskList-itemSummary">
-                    <span class="TaskList-itemTitle">{{$task.Name}}</span>
-                    Finished: {{$task.Finished}} Result: {{$task.Result.String}} Name:
-                    {{$task.Name}}
+                    <span class="TaskList-itemCol TaskList-itemStatus">
+                      {{$task.Finished}}
+                    </span>
+                    <span class="TaskList-itemCol TaskList-itemName">
+                      {{$task.Name}}
+                    </span>
+                    <span class="TaskList-itemCol TaskList-itemCreated">
+                      {{$task.CreatedAt.UTC.Format "Mon Jan _2 2006 15:04:05"}}
+                    </span>
+                    <span class="TaskList-itemCol TaskList-itemUpdated">
+                      {{$task.UpdatedAt.UTC.Format "Mon Jan _2 2006 15:04:05"}}
+                    </span>
+                    <span class="TaskList-itemCol TaskList-itemResult">
+                      {{$task.Result}}
+                    </span>
                   </summary>
                   <div class="TaskList-itemLogs">
                     {{range $log := $.Logs $workflow.ID  $task.Name}}
                       <div class="TaskList-itemLogLine">
-                        {{$log.CreatedAt.Format "2006/01/02 15:04:05"}}
+                        {{$log.CreatedAt.UTC.Format "2006/01/02 15:04:05"}}
                         {{$log.Body}}
                       </div>
                     {{end}}
diff --git a/internal/relui/web.go b/internal/relui/web.go
index 584ca29..e64a77a 100644
--- a/internal/relui/web.go
+++ b/internal/relui/web.go
@@ -87,6 +87,12 @@
 	return t[task]
 }
 
+func (h *homeResponse) WorkflowParams(wf db.Workflow) map[string]string {
+	params := make(map[string]string)
+	json.Unmarshal([]byte(wf.Params.String), &params)
+	return params
+}
+
 // homeHandler renders the homepage.
 func (s *Server) homeHandler(w http.ResponseWriter, r *http.Request) {
 	resp, err := s.buildHomeResponse(r.Context())
diff --git a/internal/workflow/workflow.go b/internal/workflow/workflow.go
index 11727ed..ff50cd7 100644
--- a/internal/workflow/workflow.go
+++ b/internal/workflow/workflow.go
@@ -339,7 +339,7 @@
 // will be restarted, but tasks that finished in errors will not be retried.
 //
 // The host must create the WorkflowState. TaskStates should be saved from
-// listener callbacks, but for ease of stoage, their Result field does not
+// listener callbacks, but for ease of storage, their Result field does not
 // need to be populated.
 func Resume(def *Definition, state *WorkflowState, taskStates map[string]*TaskState) (*Workflow, error) {
 	w := &Workflow{