aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2023-07-17 12:07:58 +0200
committerAleksandr Nogikh <nogikh@google.com>2023-07-17 11:17:04 +0000
commitc842da34cdd3b56628cb00b5f1f024dabc798716 (patch)
tree8ed37230fa4620f2d4e0f8d87d9e7c7caafda57c
parente5f1088910d12c083d40dd1d9e3f62d4713faa6b (diff)
dashboard: show per-bisect type job lists for admins
Currently we only show running/pending/recent jobs. Let admins also see the list of the latest cause/fix bisections.
-rw-r--r--dashboard/app/admin.html5
-rw-r--r--dashboard/app/index.yaml6
-rw-r--r--dashboard/app/main.go87
-rw-r--r--dashboard/app/main_test.go41
-rw-r--r--dashboard/app/templates.html2
5 files changed, 115 insertions, 26 deletions
diff --git a/dashboard/app/admin.html b/dashboard/app/admin.html
index cc66f7d91..8ada1c64b 100644
--- a/dashboard/app/admin.html
+++ b/dashboard/app/admin.html
@@ -47,8 +47,13 @@ Main page.
{{end}}
{{template "manager_list" $.Managers}}
+ &nbsp;&nbsp;
+ {{if $.FixBisectionsLink}}<a href="{{$.FixBisectionsLink}}">[Fix Bisections]</a>{{end}}
+ {{if $.CauseBisectionsLink}}<a href="{{$.CauseBisectionsLink}}">[Cause Bisections]</a>{{end}}
+ {{if $.JobOverviewLink}}<a href="{{$.JobOverviewLink}}">[Jobs Overview]</a>{{end}}
{{template "job_list" $.RunningJobs}}
{{template "job_list" $.PendingJobs}}
{{template "job_list" $.RecentJobs}}
+ {{template "job_list" $.TypeJobs}}
</body>
</html>
diff --git a/dashboard/app/index.yaml b/dashboard/app/index.yaml
index 7b18993f2..f904e3062 100644
--- a/dashboard/app/index.yaml
+++ b/dashboard/app/index.yaml
@@ -227,3 +227,9 @@ indexes:
- name: Type
- name: Finished
direction: desc
+
+- kind: Job
+ properties:
+ - name: Type
+ - name: Finished
+ direction: desc
diff --git a/dashboard/app/main.go b/dashboard/app/main.go
index d7655fd47..9fe31b0ef 100644
--- a/dashboard/app/main.go
+++ b/dashboard/app/main.go
@@ -179,13 +179,17 @@ type uiSubsystemStats struct {
}
type uiAdminPage struct {
- Header *uiHeader
- Log []byte
- Managers *uiManagerList
- RecentJobs *uiJobList
- PendingJobs *uiJobList
- RunningJobs *uiJobList
- MemcacheStats *memcache.Statistics
+ Header *uiHeader
+ Log []byte
+ Managers *uiManagerList
+ RecentJobs *uiJobList
+ PendingJobs *uiJobList
+ RunningJobs *uiJobList
+ TypeJobs *uiJobList
+ FixBisectionsLink string
+ CauseBisectionsLink string
+ JobOverviewLink string
+ MemcacheStats *memcache.Statistics
}
type uiManager struct {
@@ -644,6 +648,7 @@ func handleAdmin(c context.Context, w http.ResponseWriter, r *http.Request) erro
recentJobs []*uiJob
pendingJobs []*uiJob
runningJobs []*uiJob
+ typeJobs []*uiJob
)
g, _ := errgroup.WithContext(context.Background())
g.Go(func() error {
@@ -661,21 +666,33 @@ func handleAdmin(c context.Context, w http.ResponseWriter, r *http.Request) erro
errorLog, err = fetchErrorLogs(c)
return err
})
- g.Go(func() error {
- var err error
- recentJobs, err = loadRecentJobs(c)
- return err
- })
- g.Go(func() error {
- var err error
- pendingJobs, err = loadPendingJobs(c)
- return err
- })
- g.Go(func() error {
- var err error
- runningJobs, err = loadRunningJobs(c)
- return err
- })
+ if r.FormValue("job_type") != "" {
+ value, err := strconv.Atoi(r.FormValue("job_type"))
+ if err != nil {
+ return fmt.Errorf("%w: %v", ErrClientBadRequest, err)
+ }
+ g.Go(func() error {
+ var err error
+ typeJobs, err = loadJobsOfType(c, JobType(value))
+ return err
+ })
+ } else {
+ g.Go(func() error {
+ var err error
+ recentJobs, err = loadRecentJobs(c)
+ return err
+ })
+ g.Go(func() error {
+ var err error
+ pendingJobs, err = loadPendingJobs(c)
+ return err
+ })
+ g.Go(func() error {
+ var err error
+ runningJobs, err = loadRunningJobs(c)
+ return err
+ })
+ }
err = g.Wait()
if err != nil {
return err
@@ -684,11 +701,18 @@ func handleAdmin(c context.Context, w http.ResponseWriter, r *http.Request) erro
Header: hdr,
Log: errorLog,
Managers: makeManagerList(managers, hdr.Namespace),
- RecentJobs: &uiJobList{Title: "Recent jobs:", Jobs: recentJobs},
- RunningJobs: &uiJobList{Title: "Running jobs:", Jobs: runningJobs},
- PendingJobs: &uiJobList{Title: "Pending jobs:", Jobs: pendingJobs},
MemcacheStats: memcacheStats,
}
+ if r.FormValue("job_type") != "" {
+ data.TypeJobs = &uiJobList{Title: "Last jobs:", Jobs: typeJobs}
+ data.JobOverviewLink = "/admin"
+ } else {
+ data.RecentJobs = &uiJobList{Title: "Recent jobs:", Jobs: recentJobs}
+ data.RunningJobs = &uiJobList{Title: "Running jobs:", Jobs: runningJobs}
+ data.PendingJobs = &uiJobList{Title: "Pending jobs:", Jobs: pendingJobs}
+ data.FixBisectionsLink = html.AmendURL("/admin", "job_type", fmt.Sprintf("%d", JobBisectFix))
+ data.CauseBisectionsLink = html.AmendURL("/admin", "job_type", fmt.Sprintf("%d", JobBisectCause))
+ }
return serveTemplate(w, "admin.html", data)
}
@@ -1926,6 +1950,19 @@ func loadRunningJobs(c context.Context) ([]*uiJob, error) {
return getUIJobs(c, keys, jobs), nil
}
+func loadJobsOfType(c context.Context, t JobType) ([]*uiJob, error) {
+ var jobs []*Job
+ keys, err := db.NewQuery("Job").
+ Filter("Type=", t).
+ Order("-Finished").
+ Limit(50).
+ GetAll(c, &jobs)
+ if err != nil {
+ return nil, err
+ }
+ return getUIJobs(c, keys, jobs), nil
+}
+
func getUIJobs(c context.Context, keys []*db.Key, jobs []*Job) []*uiJob {
var results []*uiJob
for i, job := range jobs {
diff --git a/dashboard/app/main_test.go b/dashboard/app/main_test.go
index fff94d7f9..efbf60ef6 100644
--- a/dashboard/app/main_test.go
+++ b/dashboard/app/main_test.go
@@ -6,6 +6,7 @@ package main
import (
"bytes"
"testing"
+ "time"
"github.com/google/syzkaller/dashboard/dashapi"
"github.com/stretchr/testify/assert"
@@ -295,3 +296,43 @@ func TestMultiLabelFilter(t *testing.T) {
assert.NotContains(t, string(reply), "/access-public-email?label=subsystems:subsystemA\"")
assert.NotContains(t, string(reply), "/access-public-email?label=prop:low\"")
}
+
+func TestAdminJobList(t *testing.T) {
+ c := NewCtx(t)
+ defer c.Close()
+
+ client := c.client2
+ build := testBuild(1)
+ client.UploadBuild(build)
+
+ crash := testCrash(build, 1)
+ crash.Title = "some bug title"
+ crash.GuiltyFiles = []string{"a.c"}
+ crash.ReproOpts = []byte("repro opts")
+ crash.ReproSyz = []byte("repro syz")
+ crash.ReproC = []byte("repro C")
+ client.ReportCrash(crash)
+ client.pollEmailBug()
+
+ c.advanceTime(24 * time.Hour)
+
+ pollResp := client.pollSpecificJobs(build.Manager, dashapi.ManagerJobs{BisectCause: true})
+ c.expectNE(pollResp.ID, "")
+
+ causeJobsLink := "/admin?job_type=1"
+ fixJobsLink := "/admin?job_type=2"
+ reply, err := c.AuthGET(AccessAdmin, "/admin")
+ c.expectOK(err)
+ assert.Contains(t, string(reply), causeJobsLink)
+ assert.Contains(t, string(reply), fixJobsLink)
+
+ // Verify the bug is in the bisect cause jobs list.
+ reply, err = c.AuthGET(AccessAdmin, causeJobsLink)
+ c.expectOK(err)
+ assert.Contains(t, string(reply), crash.Title)
+
+ // Verify the bug is NOT in the fix jobs list.
+ reply, err = c.AuthGET(AccessAdmin, fixJobsLink)
+ c.expectOK(err)
+ assert.NotContains(t, string(reply), crash.Title)
+}
diff --git a/dashboard/app/templates.html b/dashboard/app/templates.html
index 6278c1f7d..e9738e785 100644
--- a/dashboard/app/templates.html
+++ b/dashboard/app/templates.html
@@ -423,7 +423,7 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the
{{/* List of jobs, invoked with *uiJobList */}}
{{define "job_list"}}
-{{if $.Jobs}}
+{{if and $ $.Jobs}}
<table class="list_table">
<caption id="jobs"><a class="plain" href="#jobs">{{$.Title}}</a></caption>
<thead>