Skip to content

Commit 9b801d6

Browse files
committed
dashboard/app: ns/cover?jsonl=1 supports manager and subsystem selection
1. Refactor handleHeatmap. 2. Introduce function options. Build them from http.Request.
1 parent fd5e6e6 commit 9b801d6

File tree

5 files changed

+171
-76
lines changed

5 files changed

+171
-76
lines changed

dashboard/app/coverage.go

Lines changed: 131 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ package main
55

66
import (
77
"context"
8+
"errors"
89
"fmt"
910
"html/template"
1011
"net/http"
1112
"os"
1213
"slices"
1314
"strconv"
15+
"strings"
1416

1517
"cloud.google.com/go/civil"
1618
"github.com/google/syzkaller/pkg/cover"
@@ -56,22 +58,135 @@ type funcStyleBodyJS func(
5658
scope *coveragedb.SelectScope, onlyUnique bool, sss, managers []string, dataFilters cover.Format,
5759
) (template.CSS, template.HTML, template.HTML, error)
5860

59-
func handleCoverageHeatmap(c context.Context, w http.ResponseWriter, r *http.Request) error {
60-
if r.FormValue("jsonl") == "1" {
61-
hdr, err := commonHeader(c, r, w, "")
62-
if err != nil {
63-
return err
61+
type coverageHeatmapParams struct {
62+
manager string
63+
subsystem string
64+
onlyUnique bool
65+
periodType string
66+
nPeriods int
67+
dateTo civil.Date
68+
cover.Format
69+
}
70+
71+
const minPeriodsOnThePage = 1
72+
const maxPeriodsOnThePage = 12
73+
74+
func makeHeatmapParams(ctx context.Context, r *http.Request) (*coverageHeatmapParams, error) {
75+
minCoverLinesDrop, err := getIntParam(r, covPageParams[MinCoverLinesDrop], 0)
76+
if err != nil {
77+
return nil, err
78+
}
79+
onlyUnique, err := getBoolParam(r, covPageParams[UniqueOnly], false)
80+
if err != nil {
81+
return nil, err
82+
}
83+
orderByCoverDrop, err := getBoolParam(r, covPageParams[OrderByCoverDrop], false)
84+
if err != nil {
85+
return nil, err
86+
}
87+
periodType, err := getStringParam(r, covPageParams[PeriodType], coveragedb.DayPeriod)
88+
if err != nil {
89+
return nil, err
90+
}
91+
if !slices.Contains(coveragedb.AllPeriods, periodType) {
92+
return nil, fmt.Errorf("only {%s} are allowed, but received %s instead, %w",
93+
strings.Join(coveragedb.AllPeriods, ", "), periodType, ErrClientBadRequest)
94+
}
95+
96+
nPeriods, err := getIntParam(r, covPageParams[PeriodCount], 4)
97+
if err != nil || nPeriods > maxPeriodsOnThePage || nPeriods < minPeriodsOnThePage {
98+
return nil, fmt.Errorf("periods_count is wrong, expected [%d, %d]: %w",
99+
minPeriodsOnThePage, maxPeriodsOnThePage, err)
100+
}
101+
102+
dateTo := civil.DateOf(timeNow(ctx))
103+
if customDate := r.FormValue(covPageParams[DateTo]); customDate != "" {
104+
if dateTo, err = civil.ParseDate(customDate); err != nil {
105+
return nil, fmt.Errorf("civil.ParseDate(%s): %w", customDate, err)
64106
}
107+
}
108+
109+
return &coverageHeatmapParams{
110+
manager: r.FormValue(covPageParams[ManagerName]),
111+
subsystem: r.FormValue(covPageParams[SubsystemName]),
112+
onlyUnique: onlyUnique,
113+
periodType: periodType,
114+
nPeriods: nPeriods,
115+
dateTo: dateTo,
116+
Format: cover.Format{
117+
DropCoveredLines0: onlyUnique,
118+
OrderByCoveredLinesDrop: orderByCoverDrop,
119+
FilterMinCoveredLinesDrop: minCoverLinesDrop,
120+
},
121+
}, nil
122+
}
123+
124+
func getIntParam(r *http.Request, name string, orDefault ...int) (int, error) {
125+
if r.FormValue(name) == "" {
126+
if len(orDefault) > 0 {
127+
return orDefault[0], nil
128+
}
129+
return 0, errors.New("missing parameter " + name)
130+
}
131+
res, err := strconv.Atoi(r.FormValue(name))
132+
if err != nil {
133+
return 0, fmt.Errorf("strconv.Atoi(%s): %w", name, err)
134+
}
135+
return res, nil
136+
}
137+
138+
func getBoolParam(r *http.Request, name string, orDefault ...bool) (bool, error) {
139+
if r.FormValue(name) == "" {
140+
if len(orDefault) > 0 {
141+
return orDefault[0], nil
142+
}
143+
return false, errors.New("missing parameter " + name)
144+
}
145+
res, err := strconv.ParseBool(r.FormValue(name))
146+
if err != nil {
147+
return false, fmt.Errorf("strconv.ParseBool(%s): %w", name, err)
148+
}
149+
return res, nil
150+
}
151+
152+
func getStringParam(r *http.Request, name string, orDefault ...string) (string, error) {
153+
if r.FormValue(name) == "" {
154+
if len(orDefault) > 0 {
155+
return orDefault[0], nil
156+
}
157+
return "", errors.New("missing parameter " + name)
158+
}
159+
return r.FormValue(name), nil
160+
}
161+
162+
func handleCoverageHeatmap(c context.Context, w http.ResponseWriter, r *http.Request) error {
163+
hdr, err := commonHeader(c, r, w, "")
164+
if err != nil {
165+
return err
166+
}
167+
params, err := makeHeatmapParams(c, r)
168+
if err != nil {
169+
return fmt.Errorf("%s: %w", err.Error(), ErrClientBadRequest)
170+
}
171+
if requestJSONL, _ := getBoolParam(r, "jsonl", false); requestJSONL {
65172
ns := hdr.Namespace
66173
repo, _ := getNsConfig(c, ns).mainRepoBranch()
67174
w.Header().Set("Content-Type", "application/json")
68-
return writeExtAPICoverageFor(c, w, ns, repo)
175+
return writeExtAPICoverageFor(c, w, ns, repo, params)
69176
}
70-
return handleHeatmap(c, w, r, cover.DoHeatMapStyleBodyJS)
177+
return handleHeatmap(c, w, hdr, params, cover.DoHeatMapStyleBodyJS)
71178
}
72179

73180
func handleSubsystemsCoverageHeatmap(c context.Context, w http.ResponseWriter, r *http.Request) error {
74-
return handleHeatmap(c, w, r, cover.DoSubsystemsHeatMapStyleBodyJS)
181+
hdr, err := commonHeader(c, r, w, "")
182+
if err != nil {
183+
return err
184+
}
185+
params, err := makeHeatmapParams(c, r)
186+
if err != nil {
187+
return fmt.Errorf("%s: %w", err.Error(), ErrClientBadRequest)
188+
}
189+
return handleHeatmap(c, w, hdr, params, cover.DoSubsystemsHeatMapStyleBodyJS)
75190
}
76191

77192
type covPageParam int
@@ -127,46 +242,14 @@ func coveragePageLink(ns, periodType, dateTo string, minDrop, periodCount int, o
127242
return url
128243
}
129244

130-
func handleHeatmap(c context.Context, w http.ResponseWriter, r *http.Request, f funcStyleBodyJS) error {
131-
hdr, err := commonHeader(c, r, w, "")
132-
if err != nil {
133-
return err
134-
}
245+
func handleHeatmap(c context.Context, w http.ResponseWriter, hdr *uiHeader, p *coverageHeatmapParams,
246+
f funcStyleBodyJS) error {
135247
nsConfig := getNsConfig(c, hdr.Namespace)
136248
if nsConfig.Coverage == nil {
137249
return ErrClientNotFound
138250
}
139-
ss := r.FormValue(covPageParams[SubsystemName])
140-
manager := r.FormValue(covPageParams[ManagerName])
141-
142-
periodType := r.FormValue(covPageParams[PeriodType])
143-
if periodType == "" {
144-
periodType = coveragedb.DayPeriod
145-
}
146-
if periodType != coveragedb.DayPeriod &&
147-
periodType != coveragedb.MonthPeriod &&
148-
periodType != coveragedb.QuarterPeriod {
149-
return fmt.Errorf("only 'day', 'month' and 'quarter' are allowed, but received %s instead, %w",
150-
periodType, ErrClientBadRequest)
151-
}
152-
153-
periodCount := r.FormValue(covPageParams[PeriodCount])
154-
if periodCount == "" {
155-
periodCount = "4"
156-
}
157-
nPeriods, err := strconv.Atoi(periodCount)
158-
if err != nil || nPeriods > 12 || nPeriods < 1 {
159-
return fmt.Errorf("periods_count is wrong, expected [1, 12]: %w", err)
160-
}
161-
162-
dateTo := civil.DateOf(timeNow(c))
163-
if customDate := r.FormValue(covPageParams[DateTo]); customDate != "" {
164-
if dateTo, err = civil.ParseDate(customDate); err != nil {
165-
return fmt.Errorf("civil.ParseDate(%s): %w", customDate, err)
166-
}
167-
}
168251

169-
periods, err := coveragedb.GenNPeriodsTill(nPeriods, dateTo, periodType)
252+
periods, err := coveragedb.GenNPeriodsTill(p.nPeriods, p.dateTo, p.periodType)
170253
if err != nil {
171254
return fmt.Errorf("%s: %w", err.Error(), ErrClientBadRequest)
172255
}
@@ -182,29 +265,17 @@ func handleHeatmap(c context.Context, w http.ResponseWriter, r *http.Request, f
182265
slices.Sort(managers)
183266
slices.Sort(subsystems)
184267

185-
onlyUnique := r.FormValue(covPageParams[UniqueOnly]) == "1"
186-
orderByCoverLinesDrop := r.FormValue(covPageParams[OrderByCoverDrop]) == "1"
187-
// Prefixing "0" we don't fail on empty string.
188-
minCoverLinesDrop, err := strconv.Atoi("0" + r.FormValue(covPageParams[MinCoverLinesDrop]))
189-
if err != nil {
190-
return fmt.Errorf(covPageParams[MinCoverLinesDrop] + " should be integer")
191-
}
192-
193268
var style template.CSS
194269
var body, js template.HTML
195270
if style, body, js, err = f(c, getCoverageDBClient(c),
196271
&coveragedb.SelectScope{
197272
Ns: hdr.Namespace,
198-
Subsystem: ss,
199-
Manager: manager,
273+
Subsystem: p.subsystem,
274+
Manager: p.manager,
200275
Periods: periods,
201276
},
202-
onlyUnique, subsystems, managers,
203-
cover.Format{
204-
FilterMinCoveredLinesDrop: minCoverLinesDrop,
205-
OrderByCoveredLinesDrop: orderByCoverLinesDrop,
206-
DropCoveredLines0: onlyUnique,
207-
}); err != nil {
277+
p.onlyUnique, subsystems, managers, p.Format,
278+
); err != nil {
208279
return fmt.Errorf("failed to generate heatmap: %w", err)
209280
}
210281
return serveTemplate(w, "custom_content.html", struct {
@@ -261,7 +332,7 @@ func handleFileCoverage(c context.Context, w http.ResponseWriter, r *http.Reques
261332
if err != nil {
262333
return fmt.Errorf("coveragedb.MakeTimePeriod: %w", err)
263334
}
264-
onlyUnique := r.FormValue(covPageParams[UniqueOnly]) == "1"
335+
onlyUnique, _ := getBoolParam(r, covPageParams[UniqueOnly], false)
265336
mainNsRepo, _ := nsConfig.mainRepoBranch()
266337
client := getCoverageDBClient(c)
267338
if client == nil {

dashboard/app/public_json_api.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ func GetJSONDescrFor(page interface{}) ([]byte, error) {
178178
return json.MarshalIndent(res, "", "\t")
179179
}
180180

181-
func writeExtAPICoverageFor(ctx context.Context, w io.Writer, ns, repo string) error {
181+
func writeExtAPICoverageFor(ctx context.Context, w io.Writer, ns, repo string, p *coverageHeatmapParams) error {
182182
// By default, return the previous month coverage. It guarantees the good numbers.
183183
//
184184
// The alternative is to return the current month.
@@ -187,13 +187,25 @@ func writeExtAPICoverageFor(ctx context.Context, w io.Writer, ns, repo string) e
187187
if err != nil {
188188
return fmt.Errorf("coveragedb.GenNPeriodsTill: %w", err)
189189
}
190-
defaultTimePeriod := tps[0]
190+
191191
covDBClient := getCoverageDBClient(ctx)
192-
ff, err := coveragedb.MakeFuncFinder(ctx, covDBClient, ns, defaultTimePeriod)
192+
ff, err := coveragedb.MakeFuncFinder(ctx, covDBClient, ns, tps[0])
193193
if err != nil {
194194
return fmt.Errorf("coveragedb.MakeFuncFinder: %w", err)
195195
}
196-
covCh, errCh := coveragedb.FilesCoverageStream(ctx, covDBClient, ns, defaultTimePeriod)
196+
subsystem := ""
197+
manager := ""
198+
if p != nil {
199+
subsystem = p.subsystem
200+
manager = p.manager
201+
}
202+
covCh, errCh := coveragedb.FilesCoverageStream(ctx, covDBClient,
203+
&coveragedb.SelectScope{
204+
Ns: ns,
205+
Subsystem: subsystem,
206+
Manager: manager,
207+
Periods: tps,
208+
})
197209
if err := writeFileCoverage(ctx, w, repo, ff, covCh); err != nil {
198210
return fmt.Errorf("populateFileCoverage: %w", err)
199211
}

dashboard/app/public_json_api_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ func TestWriteExtAPICoverageFor(t *testing.T) {
280280
))
281281

282282
var buf bytes.Buffer
283-
err := writeExtAPICoverageFor(ctx, &buf, "test-ns", "test-repo")
283+
err := writeExtAPICoverageFor(ctx, &buf, "test-ns", "test-repo", nil)
284284
assert.NoError(t, err)
285285
assert.Equal(t, `{
286286
"repo": "test-repo",

pkg/coveragedb/coveragedb.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -409,10 +409,10 @@ type SelectScope struct {
409409

410410
// FilesCoverageStream streams information about all the line coverage.
411411
// It is expensive and better to be used for time insensitive operations.
412-
func FilesCoverageStream(ctx context.Context, client spannerclient.SpannerClient, ns string, timePeriod TimePeriod,
412+
func FilesCoverageStream(ctx context.Context, client spannerclient.SpannerClient, scope *SelectScope,
413413
) (<-chan *FileCoverageWithLineInfo, <-chan error) {
414414
iter := client.Single().Query(ctx,
415-
filesCoverageWithDetailsStmt(ns, "", "", timePeriod, true))
415+
filesCoverageWithDetailsStmt(scope, true))
416416
resCh := make(chan *FileCoverageWithLineInfo)
417417
errCh := make(chan error)
418418
go func() {
@@ -435,14 +435,24 @@ func FilesCoverageWithDetails(
435435
for _, timePeriod := range scope.Periods {
436436
needLinesDetails := onlyUnique
437437
iterManager := client.Single().Query(ctx,
438-
filesCoverageWithDetailsStmt(scope.Ns, scope.Subsystem, scope.Manager, timePeriod, needLinesDetails))
438+
filesCoverageWithDetailsStmt(&SelectScope{
439+
Ns: scope.Ns,
440+
Subsystem: scope.Subsystem,
441+
Manager: scope.Manager,
442+
Periods: []TimePeriod{timePeriod},
443+
}, needLinesDetails))
439444
defer iterManager.Stop()
440445

441446
var err error
442447
var periodRes []*FileCoverageWithDetails
443448
if onlyUnique {
444449
iterAll := client.Single().Query(ctx,
445-
filesCoverageWithDetailsStmt(scope.Ns, scope.Subsystem, "", timePeriod, needLinesDetails))
450+
filesCoverageWithDetailsStmt(&SelectScope{
451+
Ns: scope.Ns,
452+
Subsystem: scope.Subsystem,
453+
Manager: "",
454+
Periods: []TimePeriod{timePeriod},
455+
}, needLinesDetails))
446456
defer iterAll.Stop()
447457
periodRes, err = readCoverageUniq(iterAll, iterManager)
448458
if err != nil {
@@ -462,8 +472,8 @@ func FilesCoverageWithDetails(
462472
return res, nil
463473
}
464474

465-
func filesCoverageWithDetailsStmt(ns, subsystem, manager string, timePeriod TimePeriod, withLines bool,
466-
) spanner.Statement {
475+
func filesCoverageWithDetailsStmt(scope *SelectScope, withLines bool) spanner.Statement {
476+
manager := scope.Manager
467477
if manager == "" {
468478
manager = "*"
469479
}
@@ -481,15 +491,15 @@ from merge_history
481491
where
482492
merge_history.namespace=$1 and dateto=$2 and duration=$3 and manager=$4`,
483493
Params: map[string]interface{}{
484-
"p1": ns,
485-
"p2": timePeriod.DateTo,
486-
"p3": timePeriod.Days,
494+
"p1": scope.Ns,
495+
"p2": scope.Periods[0].DateTo,
496+
"p3": scope.Periods[0].Days,
487497
"p4": manager,
488498
},
489499
}
490-
if subsystem != "" {
500+
if scope.Subsystem != "" {
491501
stmt.SQL += " and $5=ANY(subsystems)"
492-
stmt.Params["p5"] = subsystem
502+
stmt.Params["p5"] = scope.Subsystem
493503
}
494504
stmt.SQL += "\norder by files.filepath"
495505
return stmt

pkg/coveragedb/time_period.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const (
4141
QuarterPeriod = "quarter"
4242
)
4343

44+
var AllPeriods = []string{DayPeriod, MonthPeriod, QuarterPeriod}
45+
4446
var errUnknownTimePeriodType = errors.New("unknown time period type")
4547

4648
func MinMaxDays(periodType string) (int, int, error) {

0 commit comments

Comments
 (0)