internal/relui: simplify rendering of homepage
This refactors the homepage rendering to use a nested template for each
task row. This will help simplify the template as we add more complex
layout to the outputs of workflows and tasks.
Updates golang/go#51797
Updates golang/go#40279
For golang/go#53382
Change-Id: I85a86b82bdc79c7fb4e837d884af922c7028295d
Reviewed-on: https://go-review.googlesource.com/c/build/+/412176
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Alex Rakoczy <alex@golang.org>
diff --git a/internal/relui/templates/home.html b/internal/relui/templates/home.html
index d5534f1..e160560 100644
--- a/internal/relui/templates/home.html
+++ b/internal/relui/templates/home.html
@@ -3,6 +3,8 @@
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
+{{template "layout" .}}
+
{{define "content"}}
<section class="Workflows">
<div class="Workflows-header">
@@ -10,7 +12,9 @@
<a href="{{baseLink "/workflows/new"}}" class="Button">New</a>
</div>
<ul class="WorkflowList">
- {{range $workflow := .Workflows}}
+ {{range $wfid := .WorkflowIDs}}
+ {{$detail := index $.WorkflowDetails $wfid}}
+ {{$workflow := $detail.Workflow}}
<li class="WorkflowList-item">
<h3 class="WorkflowList-title">
{{$workflow.Name.String}}
@@ -19,8 +23,8 @@
</span>
{{if not (or $workflow.Finished $workflow.Error)}}
<div class="WorkflowList-titleStop">
- <form action="{{baseLink (printf "/workflows/%s/stop" $workflow.ID) }}" method="post">
- <input type="hidden" id="workflow.id" name="workflow.id" value="{{$workflow.ID}}" />
+ <form action="{{baseLink (printf "/workflows/%s/stop" $wfid)}}" method="post">
+ <input type="hidden" id="workflow.id" name="workflow.id" value="{{$wfid}}" />
<input name="workflow.stop" class="Button Button--red" type="submit" value="STOP" onclick="return this.form.reportValidity() && confirm('This will stop the workflow and all in-flight tasks.\n\nAre you sure you want to proceed?')" />
</form>
</div>
@@ -60,87 +64,7 @@
</tbody>
</table>
<h4 class="WorkflowList-sectionTitle">Tasks</h4>
- <table class="TaskList">
- <thead>
- <tr class="TaskList-item TaskList-itemHeader">
- <th class="TaskList-itemHeaderCol TaskList-itemExpand"></th>
- <th class="TaskList-itemHeaderCol TaskList-itemState">State</th>
- <th class="TaskList-itemHeaderCol TaskList-itemName">Name</th>
- <th class="TaskList-itemHeaderCol TaskList-itemStarted">Started</th>
- <th class="TaskList-itemHeaderCol TaskList-itemUpdated">Updated</th>
- <th class="TaskList-itemHeaderCol TaskList-itemResult">Result</th>
- <th class="TaskList-itemHeaderCol TaskList-itemActions">Actions</th>
- </tr>
- </thead>
- {{$tasks := index $.WorkflowTasks $workflow.ID}}
- {{range $task := $tasks}}
- <tbody>
- <tr class="TaskList-item TaskList-itemSummary TaskList-expandableItem">
- <td class="TaskList-itemCol TaskList-itemExpand">
- <span class="TaskList-itemExpandClosed">
- <img class="TaskList-itemExpandControl" alt="unfold more" src="{{baseLink "/static/images/chevron_right_black_24dp.svg"}}" />
- </span>
- <span class="TaskList-ItemExpandOpened">
- <img class="TaskList-itemExpandControl" alt="unfold less" src="{{baseLink "/static/images/expand_more_black_24dp.svg"}}" />
- </span>
- </td>
- <td class="TaskList-itemCol TaskList-itemState">
- {{if $task.Error.Valid}}
- <img class="TaskList-itemStateIcon" alt="error" src="{{baseLink "/static/images/error_red_24dp.svg"}}" />
- {{else if $task.Finished}}
- <img class="TaskList-itemStateIcon" alt="finished" src="{{baseLink "/static/images/check_circle_green_24dp.svg"}}" />
- {{else}}
- <img class="TaskList-itemStateIcon" alt="pending" src="{{baseLink "/static/images/pending_yellow_24dp.svg"}}" />
- {{end}}
- </td>
- <td class="TaskList-itemCol TaskList-itemName">
- {{$task.Name}}
- </td>
- <td class="TaskList-itemCol TaskList-itemStarted">
- {{$task.CreatedAt.UTC.Format "Mon Jan _2 2006 15:04:05"}}
- </td>
- <td class="TaskList-itemCol TaskList-itemUpdated">
- {{$task.UpdatedAt.UTC.Format "Mon Jan _2 2006 15:04:05"}}
- </td>
- <td class="TaskList-itemCol TaskList-itemResult">
- {{$task.Result.String}}
- </td>
- <td class="TaskList-itemCol TaskList-itemAction">
- {{if $task.Error.Valid}}
- <div class="TaskList-retryTask">
- <form action="{{baseLink (printf "/workflows/%s/tasks/%s/retry" $workflow.ID $task.Name) }}" method="post">
- <input type="hidden" id="workflow.id" name="workflow.id" value="{{$workflow.ID}}" />
- <input class="Button Button--small" name="task.reset" type="submit" value="Retry" onclick="return this.form.reportValidity() && confirm('This will retry the task and clear workflow errors.\n\nReady to proceed?')" />
- </form>
- </div>
- {{end}}
- {{if and (not $task.Finished) (hasPrefix $task.Name "APPROVE-")}}
- <div class="TaskList-approveTask">
- <form action="{{baseLink (printf "/workflows/%s/tasks/%s/approve" $workflow.ID $task.Name) }}" method="post">
- <input type="hidden" id="workflow.id" name="workflow.id" value="{{$workflow.ID}}" />
- <input class="Button Button--small" name="task.approve" type="submit" value="Approve" onclick="return this.form.reportValidity() && confirm('This will mark the task approved and resume the workflow.\n\nReady to proceed?')" />
- </form>
- </div>
- {{end}}
- </td>
- </tr>
- <tr class="TaskList-itemLogsRow">
- <td class="TaskList-itemLogs" colspan="7">
- {{if $task.Error.Valid}}
- <div class="TaskList-itemLogLine TaskList-itemLogLineError">
- {{- $task.Error.Value -}}
- </div>
- {{end}}
- {{range $log := $.Logs $workflow.ID $task.Name}}
- <div class="TaskList-itemLogLine">
- {{- $log.CreatedAt.UTC.Format "2006/01/02 15:04:05"}} {{$log.Body -}}
- </div>
- {{end}}
- </td>
- </tr>
- </tbody>
- {{end}}
- </table>
+ {{template "task_list" $detail}}
</li>
{{end}}
</ul>
diff --git a/internal/relui/templates/layout.html b/internal/relui/templates/layout.html
index 9cc777e..a8be183 100644
--- a/internal/relui/templates/layout.html
+++ b/internal/relui/templates/layout.html
@@ -3,6 +3,7 @@
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
+{{define "layout"}}
<!DOCTYPE html>
<html lang="en">
<title>{{.SiteHeader.Title}}</title>
@@ -16,7 +17,8 @@
</div>
</header>
<main class="Site-content">
- {{template "content" .}}
+ {{block "content" .}}{{end}}
</main>
</body>
</html>
+{{end}}
diff --git a/internal/relui/templates/new_workflow.html b/internal/relui/templates/new_workflow.html
index 4810a8f..c0a0a44 100644
--- a/internal/relui/templates/new_workflow.html
+++ b/internal/relui/templates/new_workflow.html
@@ -3,6 +3,8 @@
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
+{{template "layout" .}}
+
{{define "content"}}
<section class="NewWorkflow">
<h2>New Go Release</h2>
diff --git a/internal/relui/templates/task_list.html b/internal/relui/templates/task_list.html
new file mode 100644
index 0000000..428fa36
--- /dev/null
+++ b/internal/relui/templates/task_list.html
@@ -0,0 +1,94 @@
+<!--
+ Copyright 2022 The Go Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style
+ license that can be found in the LICENSE file.
+-->
+{{define "task_list"}}
+ {{$workflow := .Workflow}}
+ <table class="TaskList">
+ <thead>
+ <tr class="TaskList-item TaskList-itemHeader">
+ <th class="TaskList-itemHeaderCol TaskList-itemExpand"></th>
+ <th class="TaskList-itemHeaderCol TaskList-itemState">State</th>
+ <th class="TaskList-itemHeaderCol TaskList-itemName">Name</th>
+ <th class="TaskList-itemHeaderCol TaskList-itemStarted">Started</th>
+ <th class="TaskList-itemHeaderCol TaskList-itemUpdated">Updated</th>
+ <th class="TaskList-itemHeaderCol TaskList-itemResult">Result</th>
+ <th class="TaskList-itemHeaderCol TaskList-itemActions">Actions</th>
+ </tr>
+ </thead>
+ {{range $task := .Tasks}}
+ <tbody>
+ <tr class="TaskList-item TaskList-itemSummary TaskList-expandableItem">
+ <td class="TaskList-itemCol TaskList-itemExpand">
+ <span class="TaskList-itemExpandClosed">
+ <img class="TaskList-itemExpandControl" alt="unfold more" src="{{baseLink "/static/images/chevron_right_black_24dp.svg"}}" />
+ </span>
+ <span class="TaskList-ItemExpandOpened">
+ <img class="TaskList-itemExpandControl" alt="unfold less" src="{{baseLink "/static/images/expand_more_black_24dp.svg"}}" />
+ </span>
+ </td>
+ <td class="TaskList-itemCol TaskList-itemState">
+ {{if $task.Error.Valid}}
+ <img class="TaskList-itemStateIcon" alt="error" src="{{baseLink "/static/images/error_red_24dp.svg"}}" />
+ {{else if $task.Finished}}
+ <img
+ class="TaskList-itemStateIcon"
+ alt="finished"
+ src="{{baseLink "/static/images/check_circle_green_24dp.svg"}}" />
+ {{else}}
+ <img
+ class="TaskList-itemStateIcon"
+ alt="pending"
+ src="{{baseLink "/static/images/pending_yellow_24dp.svg"}}" />
+ {{end}}
+ </td>
+ <td class="TaskList-itemCol TaskList-itemName">
+ {{$task.Name}}
+ </td>
+ <td class="TaskList-itemCol TaskList-itemStarted">
+ {{$task.CreatedAt.UTC.Format "Mon Jan _2 2006 15:04:05"}}
+ </td>
+ <td class="TaskList-itemCol TaskList-itemUpdated">
+ {{$task.UpdatedAt.UTC.Format "Mon Jan _2 2006 15:04:05"}}
+ </td>
+ <td class="TaskList-itemCol TaskList-itemResult">
+ {{$task.Result.String}}
+ </td>
+ <td class="TaskList-itemCol TaskList-itemAction">
+ {{if $task.Error.Valid}}
+ <div class="TaskList-retryTask">
+ <form action="{{baseLink (printf "/workflows/%s/tasks/%s/retry" $workflow.ID $task.Name)}}" method="post">
+ <input type="hidden" id="workflow.id" name="workflow.id" value="{{$workflow.ID}}" />
+ <input class="Button Button--small" name="task.reset" type="submit" value="Retry" onclick="return this.form.reportValidity() && confirm('This will retry the task and clear workflow errors.\n\nReady to proceed?')" />
+ </form>
+ </div>
+ {{end}}
+ {{if and (not $task.Finished) (hasPrefix $task.Name "APPROVE-")}}
+ <div class="TaskList-approveTask">
+ <form action="{{baseLink (printf "/workflows/%s/tasks/%s/approve" $workflow.ID $task.Name)}}" method="post">
+ <input type="hidden" id="workflow.id" name="workflow.id" value="{{$workflow.ID}}" />
+ <input class="Button Button--small" name="task.approve" type="submit" value="Approve" onclick="return this.form.reportValidity() && confirm('This will mark the task approved and resume the workflow.\n\nReady to proceed?')" />
+ </form>
+ </div>
+ {{end}}
+ </td>
+ </tr>
+ <tr class="TaskList-itemLogsRow">
+ <td class="TaskList-itemLogs" colspan="7">
+ {{if $task.Error.Valid}}
+ <div class="TaskList-itemLogLine TaskList-itemLogLineError">
+ {{- $task.Error.Value -}}
+ </div>
+ {{end}}
+ {{range $log := index $.TaskLogs $task.Name}}
+ <div class="TaskList-itemLogLine">
+ {{- $log.CreatedAt.UTC.Format "2006/01/02 15:04:05"}} {{$log.Body -}}
+ </div>
+ {{end}}
+ </td>
+ </tr>
+ </tbody>
+ {{end}}
+ </table>
+{{end}}
diff --git a/internal/relui/web.go b/internal/relui/web.go
index c6acad4..8c765c1 100644
--- a/internal/relui/web.go
+++ b/internal/relui/web.go
@@ -59,6 +59,7 @@
// mux used if baseURL is set
bm *http.ServeMux
+ templates *template.Template
homeTmpl *template.Template
newWorkflowTmpl *template.Template
}
@@ -79,9 +80,9 @@
"baseLink": s.BaseLink,
"hasPrefix": strings.HasPrefix,
}
- layout := template.Must(template.New("layout.html").Funcs(helpers).ParseFS(templates, "templates/layout.html"))
- s.homeTmpl = template.Must(template.Must(layout.Clone()).Funcs(helpers).ParseFS(templates, "templates/home.html"))
- s.newWorkflowTmpl = template.Must(template.Must(layout.Clone()).Funcs(helpers).ParseFS(templates, "templates/new_workflow.html"))
+ s.templates = template.Must(template.New("").Funcs(helpers).ParseFS(templates, "templates/*.html"))
+ s.homeTmpl = s.mustLookup("home.html")
+ s.newWorkflowTmpl = s.mustLookup("new_workflow.html")
s.m.POST("/workflows/:id/stop", s.stopWorkflowHandler)
s.m.POST("/workflows/:id/tasks/:name/retry", s.retryTaskHandler)
s.m.POST("/workflows/:id/tasks/:name/approve", s.approveTaskHandler)
@@ -98,6 +99,14 @@
return s
}
+func (s *Server) mustLookup(name string) *template.Template {
+ t := template.Must(template.Must(s.templates.Clone()).ParseFS(templates, path.Join("templates", name))).Lookup(name)
+ if t == nil {
+ panic(fmt.Errorf("template %q not found", name))
+ }
+ return t
+}
+
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if s.bm != nil {
s.bm.ServeHTTP(w, r)
@@ -124,19 +133,18 @@
return u.String()
}
-type homeResponse struct {
- SiteHeader SiteHeader
- Workflows []db.Workflow
- WorkflowTasks map[uuid.UUID][]db.Task
- TaskLogs map[uuid.UUID]map[string][]db.TaskLog
+type workflowDetail struct {
+ Workflow db.Workflow
+ Tasks []db.Task
+ // TaskLogs is a map of all logs for a db.Task, keyed on
+ // (db.Task).Name
+ TaskLogs map[string][]db.TaskLog
}
-func (h *homeResponse) Logs(workflow uuid.UUID, task string) []db.TaskLog {
- t := h.TaskLogs[workflow]
- if t == nil {
- return nil
- }
- return t[task]
+type homeResponse struct {
+ SiteHeader SiteHeader
+ WorkflowIDs []uuid.UUID
+ WorkflowDetails map[uuid.UUID]*workflowDetail
}
func (h *homeResponse) WorkflowParams(wf db.Workflow) map[string]string {
@@ -172,22 +180,31 @@
if err != nil {
return nil, err
}
- wfTasks := make(map[uuid.UUID][]db.Task, len(ws))
+ hr := &homeResponse{
+ SiteHeader: s.header,
+ WorkflowDetails: make(map[uuid.UUID]*workflowDetail),
+ }
+ for _, w := range ws {
+ hr.WorkflowIDs = append(hr.WorkflowIDs, w.ID)
+ hr.WorkflowDetails[w.ID] = &workflowDetail{Workflow: w}
+ }
for _, t := range tasks {
- wfTasks[t.WorkflowID] = append(wfTasks[t.WorkflowID], t)
+ wd := hr.WorkflowDetails[t.WorkflowID]
+ wd.Tasks = append(hr.WorkflowDetails[t.WorkflowID].Tasks, t)
+ wd.TaskLogs = make(map[string][]db.TaskLog)
}
tlogs, err := q.TaskLogs(ctx)
if err != nil {
return nil, err
}
- wftlogs := make(map[uuid.UUID]map[string][]db.TaskLog)
for _, l := range tlogs {
- if wftlogs[l.WorkflowID] == nil {
- wftlogs[l.WorkflowID] = make(map[string][]db.TaskLog)
+ wd := hr.WorkflowDetails[l.WorkflowID]
+ if wd.TaskLogs == nil {
+ wd.TaskLogs = make(map[string][]db.TaskLog)
}
- wftlogs[l.WorkflowID][l.TaskName] = append(wftlogs[l.WorkflowID][l.TaskName], l)
+ wd.TaskLogs[l.TaskName] = append(wd.TaskLogs[l.TaskName], l)
}
- return &homeResponse{SiteHeader: s.header, Workflows: ws, WorkflowTasks: wfTasks, TaskLogs: wftlogs}, nil
+ return hr, nil
}
type newWorkflowResponse struct {