diff --git a/web/api/v1/api.go b/web/api/v1/api.go index 7c4ef1908..157786ccb 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -413,7 +413,10 @@ func (api *API) query(r *http.Request) (result apiFuncResult) { defer cancel() } - opts := extractQueryOpts(r) + opts, err := extractQueryOpts(r) + if err != nil { + return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil} + } qry, err := api.QueryEngine.NewInstantQuery(api.Queryable, opts, r.FormValue("query"), ts) if err != nil { return invalidParamError(err, "query") @@ -458,10 +461,18 @@ func (api *API) formatQuery(r *http.Request) (result apiFuncResult) { return apiFuncResult{expr.Pretty(0), nil, nil, nil} } -func extractQueryOpts(r *http.Request) *promql.QueryOpts { - return &promql.QueryOpts{ +func extractQueryOpts(r *http.Request) (*promql.QueryOpts, error) { + opts := &promql.QueryOpts{ EnablePerStepStats: r.FormValue("stats") == "all", } + if strDuration := r.FormValue("lookback_delta"); strDuration != "" { + duration, err := parseDuration(strDuration) + if err != nil { + return nil, fmt.Errorf("error parsing lookback delta duration: %w", err) + } + opts.LookbackDelta = duration + } + return opts, nil } func (api *API) queryRange(r *http.Request) (result apiFuncResult) { @@ -505,7 +516,10 @@ func (api *API) queryRange(r *http.Request) (result apiFuncResult) { defer cancel() } - opts := extractQueryOpts(r) + opts, err := extractQueryOpts(r) + if err != nil { + return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil} + } qry, err := api.QueryEngine.NewRangeQuery(api.Queryable, opts, r.FormValue("query"), start, end, step) if err != nil { return apiFuncResult{nil, &apiError{errorBadData, err}, nil, nil} diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index 80bc11087..4a1e4d67d 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -3410,3 +3410,66 @@ func TestGetGlobalURL(t *testing.T) { }) } } + +func TestExtractQueryOpts(t *testing.T) { + tests := []struct { + name string + form url.Values + expect *promql.QueryOpts + err error + }{ + { + name: "with stats all", + form: url.Values{ + "stats": []string{"all"}, + }, + expect: &promql.QueryOpts{ + EnablePerStepStats: true, + }, + err: nil, + }, + { + name: "with stats none", + form: url.Values{ + "stats": []string{"none"}, + }, + expect: &promql.QueryOpts{ + EnablePerStepStats: false, + }, + err: nil, + }, + { + name: "with lookback delta", + form: url.Values{ + "stats": []string{"all"}, + "lookback_delta": []string{"30s"}, + }, + expect: &promql.QueryOpts{ + EnablePerStepStats: true, + LookbackDelta: 30 * time.Second, + }, + err: nil, + }, + { + name: "with invalid lookback delta", + form: url.Values{ + "lookback_delta": []string{"invalid"}, + }, + expect: nil, + err: errors.New(`error parsing lookback delta duration: cannot parse "invalid" to a valid duration`), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := &http.Request{Form: test.form} + opts, err := extractQueryOpts(req) + require.Equal(t, test.expect, opts) + if test.err == nil { + require.NoError(t, err) + } else { + require.Equal(t, test.err.Error(), err.Error()) + } + }) + } +}