diff --git a/pkg/services/query/query.go b/pkg/services/query/query.go index 7aaafdea105..55fdb542c4f 100644 --- a/pkg/services/query/query.go +++ b/pkg/services/query/query.go @@ -311,16 +311,21 @@ func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user iden } } -func getTimeRange(query *simplejson.Json, globalFrom string, globalTo string) gtime.TimeRange { - from := query.Get("timeRange").Get("from").MustString("") - to := query.Get("timeRange").Get("to").MustString("") - - if (from == "") && (to == "") { - from = globalFrom - to = globalTo +func getTimeRange(query *simplejson.Json, globalFrom string, globalTo string) (string, string, error) { + tr, ok := query.CheckGet("timeRange") + if !ok { // timeRange json node does not exist, use global from/to + return globalFrom, globalTo, nil + } + from, err := tr.Get("from").String() + if err != nil { + return "", "", errors.New("time range: field 'from' is missing or invalid") + } + to, err := tr.Get("to").String() + if err != nil { + return "", "", errors.New("time range: field 'to' is missing or invalid") } - return gtime.NewTimeRange(from, to) + return from, to, nil } // parseRequest parses a request into parsed queries grouped by datasource uid @@ -359,7 +364,11 @@ func (s *ServiceImpl) parseMetricRequest(ctx context.Context, user identity.Requ var timeRange gtime.TimeRange if supportLocalTimeRange { - timeRange = getTimeRange(query, reqDTO.From, reqDTO.To) + from, to, err := getTimeRange(query, reqDTO.From, reqDTO.To) + if err != nil { + return nil, err + } + timeRange = gtime.NewTimeRange(from, to) } else { timeRange = gtime.NewTimeRange(reqDTO.From, reqDTO.To) } diff --git a/pkg/services/query/query_test.go b/pkg/services/query/query_test.go index 7187312da50..bf2737eab39 100644 --- a/pkg/services/query/query_test.go +++ b/pkg/services/query/query_test.go @@ -391,7 +391,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) { verifyTimestamps(parsedReq2, int64(0), int64(0), int64(0), int64(0)) }) - t.Run("Test a datasource query with malformed local time range", func(t *testing.T) { + t.Run("Test a datasource query with local time range, malformed to-value", func(t *testing.T) { tc := setup(t, false, nil) mr := metricRequestWithQueries(t, `{ "refId": "A", @@ -401,31 +401,69 @@ func TestIntegrationParseMetricRequest(t *testing.T) { }, "timeRange": { "from": "1753944618000", - "not_to": "1753944619000" + "to": 1753944619000 } - }`, `{ - "refId": "B", + }`) + mr.From = "" + mr.To = "" + _, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr, true) + require.Error(t, err) + require.ErrorContains(t, err, "'to'") + }) + t.Run("Test a datasource query with local time range, missing to-value", func(t *testing.T) { + tc := setup(t, false, nil) + mr := metricRequestWithQueries(t, `{ + "refId": "A", "datasource": { "uid": "gIEkMvIVz", "type": "postgres" }, - "timeRange": 42 + "timeRange": { + "from": "1753944618000" + } }`) mr.From = "" mr.To = "" - parsedReq, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr, true) - require.NoError(t, err) - require.NotNil(t, parsedReq) - assert.Len(t, parsedReq.parsedQueries, 1) - assert.Contains(t, parsedReq.parsedQueries, "gIEkMvIVz") - queries := parsedReq.getFlattenedQueries() - assert.Len(t, queries, 2) - - assert.Equal(t, int64(1753944618000), queries[0].query.TimeRange.From.UnixMilli()) - assert.Equal(t, int64(0), queries[0].query.TimeRange.To.UnixMilli()) - - assert.Equal(t, int64(0), queries[1].query.TimeRange.From.UnixMilli()) - assert.Equal(t, int64(0), queries[1].query.TimeRange.To.UnixMilli()) + _, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr, true) + require.Error(t, err) + require.ErrorContains(t, err, "'to'") + }) + t.Run("Test a datasource query with local time range, malformed from-value", func(t *testing.T) { + tc := setup(t, false, nil) + mr := metricRequestWithQueries(t, `{ + "refId": "A", + "datasource": { + "uid": "gIEkMvIVz", + "type": "postgres" + }, + "timeRange": { + "from": 1753944618000, + "to": "1753944619000" + } + }`) + mr.From = "" + mr.To = "" + _, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr, true) + require.Error(t, err) + require.ErrorContains(t, err, "'from'") + }) + t.Run("Test a datasource query with local time range, missing from-value", func(t *testing.T) { + tc := setup(t, false, nil) + mr := metricRequestWithQueries(t, `{ + "refId": "A", + "datasource": { + "uid": "gIEkMvIVz", + "type": "postgres" + }, + "timeRange": { + "to": "1753944619000" + } + }`) + mr.From = "" + mr.To = "" + _, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr, true) + require.Error(t, err) + require.ErrorContains(t, err, "'from'") }) }