Skip to content

Commit c8c833f

Browse files
committed
syz-cluster: add pagination
Add simple Previous/Next navigation for the list of series. For now, just rely on SQL's LIMIT/OFFSET functionality.
1 parent 9de6e4a commit c8c833f

File tree

7 files changed

+86
-17
lines changed

7 files changed

+86
-17
lines changed

syz-cluster/controller/processor_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func awaitFinishedSessions(t *testing.T, seriesRepo *db.SeriesRepository, wantFi
8787
for i := 0; i < int(deadline/interval); i++ {
8888
time.Sleep(interval)
8989

90-
list, err := seriesRepo.ListLatest(context.Background(), db.SeriesFilter{}, time.Time{}, 0)
90+
list, err := seriesRepo.ListLatest(context.Background(), db.SeriesFilter{}, time.Time{})
9191
assert.NoError(t, err)
9292
withFinishedSeries := 0
9393
for _, item := range list {

syz-cluster/dashboard/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ COPY go.mod ./
77
COPY go.sum ./
88
RUN go mod download
99
COPY pkg/gcs/ pkg/gcs/
10+
COPY pkg/html/urlutil/ pkg/html/urlutil/
1011

1112
# Build the tool.
1213
COPY syz-cluster/dashboard/ syz-cluster/dashboard/

syz-cluster/dashboard/handler.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import (
1111
"io"
1212
"io/fs"
1313
"net/http"
14+
"strconv"
1415
"time"
1516

17+
"github.com/google/syzkaller/pkg/html/urlutil"
1618
"github.com/google/syzkaller/syz-cluster/pkg/app"
1719
"github.com/google/syzkaller/syz-cluster/pkg/blob"
1820
"github.com/google/syzkaller/syz-cluster/pkg/db"
@@ -85,27 +87,63 @@ func (h *dashboardHandler) seriesList(w http.ResponseWriter, r *http.Request) er
8587
List []*db.SeriesWithSession
8688
Filter db.SeriesFilter
8789
Statuses []statusOption
90+
// This is very primitive, but better than nothing.
91+
FilterFormURL string
92+
PrevPageURL string
93+
NextPageURL string
8894
}
95+
const perPage = 100
96+
offset, err := h.getOffset(r)
97+
if err != nil {
98+
return err
99+
}
100+
baseURL := r.URL.RequestURI()
89101
data := MainPageData{
90102
Filter: db.SeriesFilter{
91103
Cc: r.FormValue("cc"),
92104
Status: db.SessionStatus(r.FormValue("status")),
105+
Limit: perPage,
106+
Offset: offset,
93107
},
108+
// If the filters are changed, the old offset value is irrelevant.
109+
FilterFormURL: urlutil.DropParam(baseURL, "offset", ""),
94110
Statuses: []statusOption{
95111
{db.SessionStatusAny, "any"},
96112
{db.SessionStatusWaiting, "waiting"},
97113
{db.SessionStatusInProgress, "in progress"},
98114
{db.SessionStatusFinished, "finished"},
99115
},
100116
}
101-
var err error
102-
data.List, err = h.seriesRepo.ListLatest(r.Context(), data.Filter, time.Time{}, 0)
117+
118+
data.List, err = h.seriesRepo.ListLatest(r.Context(), data.Filter, time.Time{})
103119
if err != nil {
104120
return fmt.Errorf("failed to query the list: %w", err)
105121
}
122+
if data.Filter.Offset > 0 {
123+
data.PrevPageURL = urlutil.SetParam(baseURL, "offset",
124+
fmt.Sprintf("%d", max(0, data.Filter.Offset-perPage)))
125+
}
126+
// TODO: this is not strictly correct (we also need to check whether there actually more rows).
127+
// But let's tolerate it for now.
128+
if len(data.List) == data.Filter.Limit {
129+
data.NextPageURL = urlutil.SetParam(baseURL, "offset",
130+
fmt.Sprintf("%d", data.Filter.Offset+len(data.List)))
131+
}
106132
return h.templates["index.html"].ExecuteTemplate(w, "base.html", data)
107133
}
108134

135+
func (h *dashboardHandler) getOffset(r *http.Request) (int, error) {
136+
val := r.FormValue("offset")
137+
if val == "" {
138+
return 0, nil
139+
}
140+
i, err := strconv.Atoi(val)
141+
if err != nil || i < 0 {
142+
return 0, fmt.Errorf("%w: invalid offset value", errBadRequest)
143+
}
144+
return i, nil
145+
}
146+
109147
func (h *dashboardHandler) seriesInfo(w http.ResponseWriter, r *http.Request) error {
110148
type SessionTest struct {
111149
*db.FullSessionTest

syz-cluster/dashboard/templates/index.html

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{{define "content"}}
22
<div class="mx-3">
3-
<form>
3+
<form action="{{.FilterFormURL}}">
44
<div class="row align-items-center">
55
<div class="col-auto col-sm-3">
66
<label for="inputCc">Cc'd</label>
@@ -13,7 +13,7 @@
1313
{{range .Statuses}}
1414
<option value="{{.Key}}" {{if eq .Key $filter.Status}} selected{{end}}>{{.Value}}</option>
1515
{{end}}
16-
</select>
16+
</select>
1717
</div>
1818
<div class="col-auto">
1919
<button type="submit" class="btn btn-primary">Submit</button>
@@ -22,6 +22,19 @@
2222
</form>
2323
</div>
2424

25+
{{if or .PrevPageURL .NextPageURL}}
26+
<nav class="mx-3">
27+
<ul class="pagination">
28+
<li class="page-item {{if not .PrevPageURL}}disabled{{end}}">
29+
<a class="page-link" href="{{.PrevPageURL}}" tabindex="-1">Previous</a>
30+
</li>
31+
<li class="page-item {{if not .NextPageURL}}disabled{{end}}">
32+
<a class="page-link" href="{{.NextPageURL}}">Next</a>
33+
</li>
34+
</ul>
35+
</nav>
36+
{{end}}
37+
2538
<table class="table">
2639
<thead class="thead-light">
2740
<tr>

syz-cluster/pkg/db/series_repo.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,13 @@ type SeriesWithSession struct {
134134
type SeriesFilter struct {
135135
Cc string
136136
Status SessionStatus
137+
Limit int
138+
Offset int
137139
}
138140

139141
// ListLatest() returns the list of series ordered by the decreasing PublishedAt value.
140142
func (repo *SeriesRepository) ListLatest(ctx context.Context, filter SeriesFilter,
141-
maxPublishedAt time.Time, limit int) ([]*SeriesWithSession, error) {
143+
maxPublishedAt time.Time) ([]*SeriesWithSession, error) {
142144
ro := repo.client.ReadOnlyTransaction()
143145
defer ro.Close()
144146

@@ -171,10 +173,14 @@ func (repo *SeriesRepository) ListLatest(ctx context.Context, filter SeriesFilte
171173
}
172174
stmt.SQL += ")"
173175
}
174-
stmt.SQL += " ORDER BY PublishedAt DESC"
175-
if limit > 0 {
176+
stmt.SQL += " ORDER BY PublishedAt DESC, ID"
177+
if filter.Limit > 0 {
176178
stmt.SQL += " LIMIT @limit"
177-
stmt.Params["limit"] = limit
179+
stmt.Params["limit"] = filter.Limit
180+
}
181+
if filter.Offset > 0 {
182+
stmt.SQL += " OFFSET @offset"
183+
stmt.Params["offset"] = filter.Offset
178184
}
179185
iter := ro.Query(ctx, stmt)
180186
defer iter.Stop()

syz-cluster/pkg/db/series_repo_test.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,31 +84,42 @@ func TestSeriesRepositoryList(t *testing.T) {
8484
})
8585

8686
t.Run("all", func(t *testing.T) {
87-
list, err := repo.ListLatest(ctx, SeriesFilter{}, time.Time{}, 0)
87+
list, err := repo.ListLatest(ctx, SeriesFilter{}, time.Time{})
8888
assert.NoError(t, err)
8989
assert.Len(t, list, 3)
9090
})
9191

9292
t.Run("with_limit", func(t *testing.T) {
93-
list, err := repo.ListLatest(ctx, SeriesFilter{}, time.Time{}, 2)
93+
list, err := repo.ListLatest(ctx, SeriesFilter{
94+
Limit: 2,
95+
}, time.Time{})
9496
assert.NoError(t, err)
9597
assert.Len(t, list, 2)
9698
assert.Equal(t, "Series 3", list[0].Series.Title)
9799
assert.Equal(t, "Series 2", list[1].Series.Title)
98100
})
99101

102+
t.Run("with_offset", func(t *testing.T) {
103+
list, err := repo.ListLatest(ctx, SeriesFilter{
104+
Limit: 1,
105+
Offset: 1,
106+
}, time.Time{})
107+
assert.NoError(t, err)
108+
assert.Len(t, list, 1)
109+
assert.Equal(t, "Series 2", list[0].Series.Title)
110+
})
111+
100112
t.Run("with_from", func(t *testing.T) {
101113
// Skips the latest series.
102-
list, err := repo.ListLatest(ctx, SeriesFilter{}, time.Date(2020, time.January, 1, 3, 0, 0, 0, time.UTC), 0)
114+
list, err := repo.ListLatest(ctx, SeriesFilter{}, time.Date(2020, time.January, 1, 3, 0, 0, 0, time.UTC))
103115
assert.NoError(t, err)
104116
assert.Len(t, list, 2)
105117
assert.Equal(t, "Series 2", list[0].Series.Title)
106118
assert.Equal(t, "Series 1", list[1].Series.Title)
107119
})
108120

109121
t.Run("filter_by_cc", func(t *testing.T) {
110-
list, err := repo.ListLatest(ctx,
111-
SeriesFilter{Cc: "a"}, time.Time{}, 0)
122+
list, err := repo.ListLatest(ctx, SeriesFilter{Cc: "a"}, time.Time{})
112123
assert.NoError(t, err)
113124
assert.Len(t, list, 2)
114125
})
@@ -125,7 +136,7 @@ func TestSeriesRepositoryList(t *testing.T) {
125136
assert.NoError(t, err)
126137

127138
t.Run("filter_status_waiting", func(t *testing.T) {
128-
list, err := repo.ListLatest(ctx, SeriesFilter{Status: SessionStatusWaiting}, time.Time{}, 0)
139+
list, err := repo.ListLatest(ctx, SeriesFilter{Status: SessionStatusWaiting}, time.Time{})
129140
assert.NoError(t, err)
130141
assert.Len(t, list, 1)
131142
})
@@ -134,7 +145,7 @@ func TestSeriesRepositoryList(t *testing.T) {
134145
assert.NoError(t, err)
135146

136147
t.Run("filter_status_in_progress", func(t *testing.T) {
137-
list, err := repo.ListLatest(ctx, SeriesFilter{Status: SessionStatusInProgress}, time.Time{}, 0)
148+
list, err := repo.ListLatest(ctx, SeriesFilter{Status: SessionStatusInProgress}, time.Time{})
138149
assert.NoError(t, err)
139150
assert.Len(t, list, 1)
140151
})

syz-cluster/pkg/db/session_repo_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func TestSeriesInsertSession(t *testing.T) {
2020
assert.NoError(t, err)
2121

2222
withSession := func(need int) {
23-
list, err := seriesRepo.ListLatest(ctx, SeriesFilter{}, time.Time{}, 10)
23+
list, err := seriesRepo.ListLatest(ctx, SeriesFilter{Limit: 10}, time.Time{})
2424
assert.NoError(t, err)
2525
var cnt int
2626
for _, item := range list {

0 commit comments

Comments
 (0)