Skip to content

Commit 6cc3416

Browse files
authored
experimental: change time-range behavior (#1428)
* Revert "experimental: DataQuery: switch from timeRange to _timeRange (#1420)" This reverts commit 64d18ac. * remove time range from backend.DataQuery.JSON * added explanation comment
1 parent db0b55a commit 6cc3416

File tree

4 files changed

+102
-49
lines changed

4 files changed

+102
-49
lines changed

experimental/apis/data/v0alpha1/conversions.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,36 @@ func ToDataSourceQueries(req QueryDataRequest) ([]backend.DataQuery, *DataSource
4545
return queries, dsRef, nil
4646
}
4747

48+
// when the time range is specified locally, per query,
49+
// it will appear in the JSON field of backend.DataQuery.
50+
// this can cause problems in certain data source plugins,
51+
// that expect that the `timeRange` field does not exist.
52+
// see https://github.com/grafana/grafana-plugin-sdk-go/issues/1419
53+
// for more info.
54+
// to improve compatibility, we remove it from the json bytes
55+
// NOTE: it will still be available in the `.TimeRange` field
56+
// of `backend.DataQuery`.
57+
var timeRangeKey = "timeRange"
58+
59+
func deleteTimeRangeFromQueryJSON(data []byte) ([]byte, error) {
60+
var d map[string]any
61+
err := json.Unmarshal(data, &d)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
_, found := d[timeRangeKey]
67+
68+
if !found {
69+
// we can finish here, return the original
70+
return data, nil
71+
}
72+
73+
delete(d, timeRangeKey)
74+
75+
return json.Marshal(d)
76+
}
77+
4878
// Converts a generic query to a backend one
4979
func toBackendDataQuery(q DataQuery, defaultTimeRange *backend.TimeRange) (backend.DataQuery, error) {
5080
var err error
@@ -65,10 +95,31 @@ func toBackendDataQuery(q DataQuery, defaultTimeRange *backend.TimeRange) (backe
6595
bq.TimeRange = *defaultTimeRange
6696
}
6797

68-
bq.JSON, err = json.Marshal(q)
98+
bytes, err := json.Marshal(q)
99+
if err != nil {
100+
return bq, err
101+
}
102+
103+
// we understand there is a certain inefficiency here
104+
// with re-marshaling the bytes, we chose this approach
105+
// for the following reasons:
106+
// 1. the smallest possible change
107+
// 2. the request object is small, so the performance
108+
// impact should be limited
109+
// 3. we can implement faster solutions later,
110+
// as a purely internal change here, without
111+
// affecting anything.
112+
//
113+
// the alternative approach would be to directly
114+
// serialise the v0alpha1.DataQuery structure into JSON
115+
// without the timeRange field.
116+
fixedBytes, err := deleteTimeRangeFromQueryJSON(bytes)
69117
if err != nil {
70118
return bq, err
71119
}
120+
121+
bq.JSON = fixedBytes
122+
72123
if bq.MaxDataPoints == 0 {
73124
bq.MaxDataPoints = 100
74125
}

experimental/apis/data/v0alpha1/conversions_test.go

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,13 @@ func TestToBackendDataQueryJSON(t *testing.T) {
5656
require.Equal(t, time.UnixMilli(87654321).UTC(), bq.TimeRange.To)
5757

5858
jsonData := `{` +
59-
`"refId":"A",` +
60-
`"_timeRange":{"from":"12345678","to":"87654321"},` +
6159
`"datasource":{"type":"prometheus","uid":"hello-world"},` +
62-
`"queryType":"interesting",` +
63-
`"maxDataPoints":42,` +
6460
`"intervalMs":15,` +
6561
`"key1":"value1",` +
66-
`"key2":"value2"` +
62+
`"key2":"value2",` +
63+
`"maxDataPoints":42,` +
64+
`"queryType":"interesting",` +
65+
`"refId":"A"` +
6766
`}`
6867

6968
require.Equal(t, jsonData, string(bq.JSON))
@@ -126,10 +125,48 @@ func TestToDataSourceQueriesTimeRangeHandling(t *testing.T) {
126125
require.Equal(t, time.UnixMilli(1763114120000).UTC(), b.TimeRange.From)
127126
require.Equal(t, time.UnixMilli(1763114130000).UTC(), b.TimeRange.To)
128127
jsonB := `{` +
129-
`"refId":"B",` +
130-
`"_timeRange":{"from":"1763114120000","to":"1763114130000"},` +
131128
`"datasource":{"type":"prometheus","uid":"prom1"},` +
132-
`"expr":"222"` +
129+
`"expr":"222",` +
130+
`"refId":"B"` +
133131
`}`
134132
require.Equal(t, jsonB, string(b.JSON))
135133
}
134+
135+
func TestDeleteTimeRangeFromQueryJSON(t *testing.T) {
136+
tests := []struct {
137+
name string
138+
data []byte
139+
expected []byte
140+
expectedError bool
141+
}{
142+
{
143+
name: "invalid json",
144+
data: []byte("hello world"),
145+
expectedError: true,
146+
},
147+
{
148+
name: "with time range",
149+
data: []byte(`{"f1":{"f2":42},"timeRange":{"from":"111","to":"222"},"f3":"v3"}`),
150+
expected: []byte(`{"f1":{"f2":42},"f3":"v3"}`),
151+
expectedError: false,
152+
},
153+
{
154+
name: "without time range",
155+
data: []byte(`{"f1":{"f2":42},"f3":"v3"}`),
156+
expected: []byte(`{"f1":{"f2":42},"f3":"v3"}`),
157+
expectedError: false,
158+
},
159+
}
160+
161+
for _, tt := range tests {
162+
t.Run(tt.name, func(t *testing.T) {
163+
result, err := deleteTimeRangeFromQueryJSON(tt.data)
164+
if tt.expectedError {
165+
require.Error(t, err)
166+
} else {
167+
require.NoError(t, err)
168+
require.Equal(t, tt.expected, result)
169+
}
170+
})
171+
}
172+
}

experimental/apis/data/v0alpha1/query.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ func writeQuery(g *DataQuery, stream *j.Stream) {
269269

270270
if q.TimeRange != nil {
271271
stream.WriteMore()
272-
stream.WriteObjectField("_timeRange")
272+
stream.WriteObjectField("timeRange")
273273
stream.WriteVal(g.TimeRange)
274274
}
275275

@@ -347,9 +347,7 @@ func (g *CommonQueryProperties) readQuery(iter *jsoniter.Iterator,
347347
g.RefID, err = iter.ReadString()
348348
case "resultAssertions":
349349
err = iter.ReadVal(&g.ResultAssertions)
350-
case "timeRange": // temporarily kept for backward compatibility
351-
err = iter.ReadVal(&g.TimeRange)
352-
case "_timeRange":
350+
case "timeRange":
353351
err = iter.ReadVal(&g.TimeRange)
354352
case "datasource":
355353
// Old datasource values may just be a string

experimental/apis/data/v0alpha1/query_test.go

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,6 @@ func TestParseQueriesIntoQueryDataRequest(t *testing.T) {
5656
"from": "100",
5757
"to": "200"
5858
}
59-
},
60-
{
61-
"refId": "Q",
62-
"datasource": {
63-
"type": "prometheus",
64-
"uid": "u-i-d"
65-
},
66-
"expr": "42",
67-
"_timeRange": {
68-
"from": "10",
69-
"to": "20"
70-
}
7159
}
7260
],
7361
"from": "1692624667389",
@@ -79,7 +67,7 @@ func TestParseQueriesIntoQueryDataRequest(t *testing.T) {
7967
require.NoError(t, err)
8068

8169
t.Run("verify raw unmarshal", func(t *testing.T) {
82-
require.Len(t, req.Queries, 3)
70+
require.Len(t, req.Queries, 2)
8371
require.Equal(t, "b1808c48-9fc9-4045-82d7-081781f8a553", req.Queries[0].Datasource.UID)
8472
require.Equal(t, "spreadsheetID", req.Queries[0].GetString("spreadsheet"))
8573

@@ -96,42 +84,21 @@ func TestParseQueriesIntoQueryDataRequest(t *testing.T) {
9684
require.Equal(t, int64(10), req.Queries[1].MaxDataPoints) // input was a string
9785

9886
// The second query has an explicit time range, and legacy datasource name
99-
require.NotNil(t, req.Queries[1].TimeRange)
100-
require.Equal(t, "100", req.Queries[1].TimeRange.From)
101-
require.Equal(t, "200", req.Queries[1].TimeRange.To)
10287
out, err = json.MarshalIndent(req.Queries[1], "", " ")
10388
require.NoError(t, err)
89+
// fmt.Printf("%s\n", string(out))
10490
require.JSONEq(t, `{
10591
"datasource": {
10692
"type": "", ` /* NOTE! this implies legacy naming */ +`
10793
"uid": "old"
10894
},
10995
"maxDataPoints": 10,
11096
"refId": "Z",
111-
"_timeRange": {
97+
"timeRange": {
11298
"from": "100",
11399
"to": "200"
114100
}
115101
}`, string(out))
116-
117-
// The third query has a time-range with an underscore prefix
118-
require.NotNil(t, req.Queries[2].TimeRange)
119-
require.Equal(t, "10", req.Queries[2].TimeRange.From)
120-
require.Equal(t, "20", req.Queries[2].TimeRange.To)
121-
out, err = json.MarshalIndent(req.Queries[2], "", " ")
122-
require.NoError(t, err)
123-
require.JSONEq(t, `{
124-
"datasource": {
125-
"type": "prometheus",
126-
"uid": "u-i-d"
127-
},
128-
"expr": "42",
129-
"refId": "Q",
130-
"_timeRange": {
131-
"from": "10",
132-
"to": "20"
133-
}
134-
}`, string(out))
135102
})
136103

137104
t.Run("verify deep copy", func(t *testing.T) {

0 commit comments

Comments
 (0)