update grafana/gafana-plugin-sdk-go dependency, and adjusted code accordingly (#113922)

* update dependencies

* query: stop handling _timeRange

* fixed unit test
This commit is contained in:
Gábor Farkas
2025-11-19 12:44:13 +01:00
committed by GitHub
parent 9dd68dbbcc
commit fc18c9315e
13 changed files with 171 additions and 90 deletions
+38 -7
View File
@@ -333,12 +333,9 @@ func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user iden
}
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
tr, ok = query.CheckGet("timeRange") // try the old name for backward compatibility
if !ok { // timeRange json node does not exist, use global from/to
return globalFrom, globalTo, nil
}
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 {
@@ -352,6 +349,40 @@ func getTimeRange(query *simplejson.Json, globalFrom string, globalTo string) (s
return from, to, nil
}
const timeRangeKey = "timeRange"
func queryToJson(query *simplejson.Json, supportLocalTimeRange bool) ([]byte, error) {
if !supportLocalTimeRange {
return query.MarshalJSON()
}
// we need to remove the `timeRange` attribute from the JSON, if it exists there,
// because it might cause complications with certain data sources.
_, has := query.CheckGet(timeRangeKey)
if !has {
return query.MarshalJSON()
}
qMap, err := query.Map()
if err != nil {
return nil, err
}
// we do not want to modify the query,
// so we create a copy without
// the timeRange attribute
d := make(map[string]any)
// without the `timeRange` attribute
for k, v := range qMap {
if k != timeRangeKey {
d[k] = v
}
}
return simplejson.NewFromAny(d).MarshalJSON()
}
// parseRequest parses a request into parsed queries grouped by datasource uid
func (s *ServiceImpl) parseMetricRequest(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest, supportLocalTimeRange bool) (*parsedRequest, error) {
if len(reqDTO.Queries) == 0 {
@@ -397,7 +428,7 @@ func (s *ServiceImpl) parseMetricRequest(ctx context.Context, user identity.Requ
timeRange = gtime.NewTimeRange(reqDTO.From, reqDTO.To)
}
modelJSON, err := query.MarshalJSON()
modelJSON, err := queryToJson(query, supportLocalTimeRange)
if err != nil {
return nil, err
}
+117 -67
View File
@@ -334,62 +334,6 @@ func TestIntegrationParseMetricRequest(t *testing.T) {
assert.Equal(t, int64(1753944629000), q.query.TimeRange.To.UnixMilli())
}
})
t.Run("Test a datasource query with local time range, old attribute-name", func(t *testing.T) {
tc := setup(t, false, nil)
mr := metricRequestWithQueries(t, `{
"refId": "A",
"datasource": {
"uid": "gIEkMvIVz",
"type": "postgres"
},
"timeRange": {
"from": "1753944618000",
"to": "1753944619000"
}
}`, `{
"refId": "B",
"datasource": {
"uid": "gIEkMvIVz",
"type": "postgres"
},
"timeRange": {
"from": "1753944628000",
"to": "1753944629000"
}
}`)
mr.From = ""
mr.To = ""
verifyTimestamps := func(parsedReq *parsedRequest, ts1 int64, ts2 int64, ts3 int64, ts4 int64) {
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, ts1, queries[0].query.TimeRange.From.UnixMilli())
assert.Equal(t, ts2, queries[0].query.TimeRange.To.UnixMilli())
assert.Equal(t, ts3, queries[1].query.TimeRange.From.UnixMilli())
assert.Equal(t, ts4, queries[1].query.TimeRange.To.UnixMilli())
}
// with flag enabled
parsedReq, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr, true)
require.NoError(t, err)
verifyTimestamps(parsedReq,
int64(1753944618000),
int64(1753944619000),
int64(1753944628000),
int64(1753944629000))
// with flag disabled
parsedReq2, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr, false)
require.NoError(t, err)
verifyTimestamps(parsedReq2, int64(0), int64(0), int64(0), int64(0))
})
t.Run("Test a datasource query with local time range", func(t *testing.T) {
tc := setup(t, false, nil)
mr := metricRequestWithQueries(t, `{
@@ -408,43 +352,86 @@ func TestIntegrationParseMetricRequest(t *testing.T) {
"uid": "gIEkMvIVz",
"type": "postgres"
},
"_timeRange": {
"timeRange": {
"from": "1753944628000",
"to": "1753944629000"
}
}`, `{
"refId": "C",
"datasource": {
"uid": "gIEkMvIVz",
"type": "postgres"
}
}`)
mr.From = ""
mr.To = ""
mr.From = "1753944638000"
mr.To = "1753944639000"
verifyTimestamps := func(parsedReq *parsedRequest, ts1 int64, ts2 int64, ts3 int64, ts4 int64) {
q0Copy := mr.Queries[0].DeepCopy()
q1Copy := mr.Queries[1].DeepCopy()
verifyTimestamps := func(
parsedReq *parsedRequest,
ts1 int64, ts2 int64, q0JSON string,
ts3 int64, ts4 int64, q1JSON string,
ts5 int64, ts6 int64, q2JSON string,
) {
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.Len(t, queries, 3)
assert.Equal(t, "A", queries[0].query.RefID)
assert.Equal(t, ts1, queries[0].query.TimeRange.From.UnixMilli())
assert.Equal(t, ts2, queries[0].query.TimeRange.To.UnixMilli())
assert.JSONEq(t, q0JSON, string(queries[0].query.JSON))
assert.Equal(t, "B", queries[1].query.RefID)
assert.Equal(t, ts3, queries[1].query.TimeRange.From.UnixMilli())
assert.Equal(t, ts4, queries[1].query.TimeRange.To.UnixMilli())
assert.JSONEq(t, q1JSON, string(queries[1].query.JSON))
assert.Equal(t, "C", queries[2].query.RefID)
assert.Equal(t, ts5, queries[2].query.TimeRange.From.UnixMilli())
assert.Equal(t, ts6, queries[2].query.TimeRange.To.UnixMilli())
assert.JSONEq(t, q2JSON, string(queries[2].query.JSON))
}
// with flag enabled
parsedReq, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr, true)
require.NoError(t, err)
// verify the query-structures were not mutated
require.Equal(t, q0Copy, mr.Queries[0])
require.Equal(t, q1Copy, mr.Queries[1])
verifyTimestamps(parsedReq,
int64(1753944618000),
int64(1753944619000),
`{"datasource":{"type":"postgres","uid":"gIEkMvIVz"},"refId":"A"}`,
int64(1753944628000),
int64(1753944629000))
int64(1753944629000),
`{"datasource":{"type":"postgres","uid":"gIEkMvIVz"},"refId":"B"}`,
int64(1753944638000),
int64(1753944639000),
`{"datasource":{"type":"postgres","uid":"gIEkMvIVz"},"refId":"C"}`,
)
// with flag disabled
parsedReq2, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr, false)
require.NoError(t, err)
verifyTimestamps(parsedReq2, int64(0), int64(0), int64(0), int64(0))
verifyTimestamps(parsedReq2,
int64(1753944638000),
int64(1753944639000),
`{"datasource":{"type":"postgres","uid":"gIEkMvIVz"},"refId":"A", "timeRange":{"from":"1753944618000", "to":"1753944619000"}}`,
int64(1753944638000),
int64(1753944639000),
`{"datasource":{"type":"postgres","uid":"gIEkMvIVz"},"refId":"B", "timeRange":{"from":"1753944628000", "to":"1753944629000"}}`,
int64(1753944638000),
int64(1753944639000),
`{"datasource":{"type":"postgres","uid":"gIEkMvIVz"},"refId":"C"}`,
)
})
t.Run("Test a datasource query with local time range, malformed to-value", func(t *testing.T) {
@@ -455,7 +442,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) {
"uid": "gIEkMvIVz",
"type": "postgres"
},
"_timeRange": {
"timeRange": {
"from": "1753944618000",
"to": 1753944619000
}
@@ -474,7 +461,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) {
"uid": "gIEkMvIVz",
"type": "postgres"
},
"_timeRange": {
"timeRange": {
"from": "1753944618000"
}
}`)
@@ -492,7 +479,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) {
"uid": "gIEkMvIVz",
"type": "postgres"
},
"_timeRange": {
"timeRange": {
"from": 1753944618000,
"to": "1753944619000"
}
@@ -511,7 +498,7 @@ func TestIntegrationParseMetricRequest(t *testing.T) {
"uid": "gIEkMvIVz",
"type": "postgres"
},
"_timeRange": {
"timeRange": {
"to": "1753944619000"
}
}`)
@@ -774,6 +761,69 @@ func TestIntegrationQueryDataWithQSDSClient(t *testing.T) {
})
}
func TestQueryToJson(t *testing.T) {
tests := []struct {
name string
data []byte
expected []byte
expectedError bool
}{
{
name: "simple",
data: []byte(`{"refId":"A", "expr": "42", "timeRange":{"from":"111","to":"112"}}`),
expected: []byte(`{"refId":"A", "expr": "42"}`),
expectedError: false,
},
{
name: "no time range",
data: []byte(`{"refId":"A", "expr": "42"}`),
expected: []byte(`{"refId":"A", "expr": "42"}`),
expectedError: false,
},
{
name: "nested structures, numbers",
data: []byte(`{"a":{"b":["c","d"]},"intervalMs":60000, "timeRange":{"from":"111","to":"112"}}`),
expected: []byte(`{"a":{"b":["c","d"]},"intervalMs":60000}`),
expectedError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q, err := simplejson.NewJson(tt.data)
require.NoError(t, err)
// first we go with supportLocalTimeRange=false
res, err := queryToJson(q, false)
require.NoError(t, err)
// verify that we did not mutate the input
data2, err := q.MarshalJSON()
require.NoError(t, err)
require.JSONEq(t, string(data2), string(tt.data))
// verify the output is the same as the input (because we used supportLocalTimeRange=false)
require.JSONEq(t, string(res), string(tt.data))
// now we go with supportLocalTimeRange=true
res, err = queryToJson(q, true)
if tt.expectedError {
require.Error(t, err)
} else {
require.NoError(t, err)
require.JSONEq(t, string(tt.expected), string(res))
}
// verify that we did not mutate the input
data2, err = q.MarshalJSON()
require.NoError(t, err)
require.JSONEq(t, string(data2), string(tt.data))
})
}
}
func setup(t *testing.T, isMultiTenant bool, mockClient clientapi.QueryDataClient) *testContext {
dss := []*datasources.DataSource{
{UID: "gIEkMvIVz", Type: "postgres"},