From 3489721ed66ea9fac42bf94d60c3e07b3b142c93 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 5 Nov 2021 08:12:55 -0700 Subject: [PATCH] api/ds/query: simplify data sources lookup for queries and expressions (#41172) --- .../src/utils/DataSourceWithBackend.test.ts | 18 +- .../src/utils/DataSourceWithBackend.ts | 31 +++- pkg/api/metrics.go | 157 +++++++++--------- pkg/expr/graph.go | 23 ++- pkg/expr/graph_test.go | 70 ++++---- pkg/expr/nodes.go | 68 ++------ pkg/expr/service.go | 36 +++- pkg/expr/transform.go | 26 ++- pkg/expr/translate/translate.go | 2 +- pkg/services/ngalert/models/alert_query.go | 2 +- public/app/core/utils/query.ts | 4 +- .../expressions/ExpressionDatasource.ts | 19 +-- public/app/features/expressions/guards.ts | 4 +- public/app/features/plugins/datasource_srv.ts | 8 +- .../features/query/components/QueryGroup.tsx | 2 +- public/app/features/query/state/runRequest.ts | 9 +- 16 files changed, 255 insertions(+), 224 deletions(-) diff --git a/packages/grafana-runtime/src/utils/DataSourceWithBackend.test.ts b/packages/grafana-runtime/src/utils/DataSourceWithBackend.test.ts index e8ec4935a87..b499008c493 100644 --- a/packages/grafana-runtime/src/utils/DataSourceWithBackend.test.ts +++ b/packages/grafana-runtime/src/utils/DataSourceWithBackend.test.ts @@ -7,6 +7,7 @@ import { DataQueryRequest, DataQueryResponseData, MutableDataFrame, + DataSourceRef, } from '@grafana/data'; import { of } from 'rxjs'; @@ -29,7 +30,7 @@ jest.mock('../services', () => ({ getBackendSrv: () => backendSrv, getDataSourceSrv: () => { return { - getInstanceSettings: () => ({ id: 8674 }), + getInstanceSettings: (ref?: DataSourceRef) => ({ type: ref?.type ?? '?', uid: ref?.uid ?? '?' }), }; }, })); @@ -39,6 +40,8 @@ describe('DataSourceWithBackend', () => { const settings = { name: 'test', id: 1234, + uid: 'abc', + type: 'dummy', jsonData: {}, } as DataSourceInstanceSettings; @@ -49,7 +52,7 @@ describe('DataSourceWithBackend', () => { ds.query({ maxDataPoints: 10, intervalMs: 5000, - targets: [{ refId: 'A' }, { refId: 'B', datasource: 'sample' }], + targets: [{ refId: 'A' }, { refId: 'B', datasource: { type: 'sample' } }], } as DataQueryRequest); const mock = mockDatasourceRequest.mock; @@ -61,14 +64,19 @@ describe('DataSourceWithBackend', () => { "data": Object { "queries": Array [ Object { - "datasourceId": 1234, + "datasource": Object { + "type": "dummy", + "uid": "abc", + }, "intervalMs": 5000, "maxDataPoints": 10, "refId": "A", }, Object { - "datasource": "sample", - "datasourceId": 8674, + "datasource": Object { + "type": "sample", + "uid": "?", + }, "intervalMs": 5000, "maxDataPoints": 10, "refId": "B", diff --git a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts index c4ee525d5f7..de0c63566f8 100644 --- a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts +++ b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts @@ -11,13 +11,32 @@ import { parseLiveChannelAddress, StreamingFrameOptions, StreamingFrameAction, + getDataSourceRef, + DataSourceRef, } from '@grafana/data'; import { merge, Observable, of } from 'rxjs'; import { catchError, switchMap } from 'rxjs/operators'; import { getBackendSrv, getDataSourceSrv, getGrafanaLiveSrv } from '../services'; import { BackendDataSourceResponse, toDataQueryResponse } from './queryResponse'; -const ExpressionDatasourceID = '__expr__'; +/** + * @internal + */ +export const ExpressionDatasourceRef = Object.freeze({ + type: '__expr__', + uid: '__expr__', +}); + +/** + * @internal + */ +export function isExpressionReference(ref?: DataSourceRef | string | null): boolean { + if (!ref) { + return false; + } + const v = (ref as any).type ?? ref; + return v === ExpressionDatasourceRef.type || v === '-100'; // -100 was a legacy accident that should be removed +} class HealthCheckError extends Error { details: HealthCheckResultDetails; @@ -89,12 +108,12 @@ class DataSourceWithBackend< } const queries = targets.map((q) => { - let datasourceId = this.id; + let datasource = this.getRef(); - if (q.datasource === ExpressionDatasourceID) { + if (isExpressionReference(q.datasource)) { return { ...q, - datasourceId, + datasource: ExpressionDatasourceRef, }; } @@ -105,12 +124,12 @@ class DataSourceWithBackend< throw new Error(`Unknown Datasource: ${JSON.stringify(q.datasource)}`); } - datasourceId = ds.id; + datasource = getDataSourceRef(ds); } return { ...this.applyTemplateVariables(q, request.scopedVars), - datasourceId, + datasource, intervalMs, maxDataPoints, }; diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index 13e57b9ad24..edaedc52bf5 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -7,6 +7,7 @@ import ( "net/http" "time" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/tsdb/grafanads" "github.com/grafana/grafana-plugin-sdk-go/backend" @@ -33,42 +34,23 @@ func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDTO dtos.MetricReq Queries: make([]plugins.DataSubQuery, 0, len(reqDTO.Queries)), } - // Loop to see if we have an expression. - prevType := "" - var ds *models.DataSource + // Parse the queries + hasExpression := false + datasources := make(map[string]*models.DataSource, len(reqDTO.Queries)) for _, query := range reqDTO.Queries { - dsType := query.Get("datasource").MustString("") - if dsType == expr.DatasourceName { - return hs.handleExpressions(c, reqDTO) + ds, errRsp := hs.getDataSourceFromQuery(c, query, datasources) + if errRsp != nil { + return errRsp } - if prevType != "" && prevType != dsType { - // For mixed datasource case, each data source is sent in a single request. - // So only the datasource from the first query is needed. As all requests - // should be the same data source. - hs.log.Debug("Can't process query since it's missing data source ID") - return response.Error(http.StatusBadRequest, "All queries must use the same datasource", nil) - } - if ds == nil { - // require ID for everything - dsID, err := query.Get("datasourceId").Int64() - if err != nil { - hs.log.Debug("Can't process query since it's missing data source ID") - return response.Error(http.StatusBadRequest, "Query missing data source ID", nil) - } - if dsID == grafanads.DatasourceID { - ds = grafanads.DataSourceModel(c.OrgId) - } else { - ds, err = hs.DataSourceCache.GetDatasource(dsID, c.SignedInUser, c.SkipCache) - if err != nil { - return hs.handleGetDataSourceError(err, dsID) - } - } + return response.Error(http.StatusBadRequest, "Datasource not found for query", nil) + } + + datasources[ds.Uid] = ds + if expr.IsDataSource(ds.Uid) { + hasExpression = true } - prevType = dsType - } - for _, query := range reqDTO.Queries { hs.log.Debug("Processing metrics query", "query", query) request.Queries = append(request.Queries, plugins.DataSubQuery{ @@ -81,6 +63,24 @@ func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDTO dtos.MetricReq }) } + if hasExpression { + exprService := expr.Service{ + Cfg: hs.Cfg, + DataService: hs.DataService, + } + qdr, err := exprService.WrapTransformData(c.Req.Context(), request) + if err != nil { + return response.Error(500, "expression request error", err) + } + return toMacronResponse(qdr) + } + + ds := request.Queries[0].DataSource + if len(datasources) > 1 { + // We do not (yet) support mixed query type + return response.Error(http.StatusBadRequest, "All queries must use the same datasource", nil) + } + err := hs.PluginRequestValidator.Validate(ds.Url, nil) if err != nil { return response.Error(http.StatusForbidden, "Access denied", err) @@ -99,6 +99,49 @@ func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDTO dtos.MetricReq return toMacronResponse(resp) } +func (hs *HTTPServer) getDataSourceFromQuery(c *models.ReqContext, query *simplejson.Json, history map[string]*models.DataSource) (*models.DataSource, response.Response) { + var err error + uid := query.Get("datasource").Get("uid").MustString() + + // before 8.3 special types could be sent as datasource (expr) + if uid == "" { + uid = query.Get("datasource").MustString() + } + + // check cache value + ds, ok := history[uid] + if ok { + return ds, nil + } + + if expr.IsDataSource(uid) { + return expr.DataSourceModel(), nil + } + + if uid == grafanads.DatasourceUID { + return grafanads.DataSourceModel(c.OrgId), nil + } + + if uid != "" { + ds, err = hs.DataSourceCache.GetDatasourceByUID(uid, c.SignedInUser, c.SkipCache) + if err != nil { + return nil, hs.handleGetDataSourceError(err, uid) + } + return ds, nil + } + + // Fallback to the datasourceId + id, err := query.Get("datasourceId").Int64() + if err != nil { + return nil, response.Error(http.StatusBadRequest, "Query missing data source ID/UID", nil) + } + ds, err = hs.DataSourceCache.GetDatasource(id, c.SignedInUser, c.SkipCache) + if err != nil { + return nil, hs.handleGetDataSourceError(err, id) + } + return ds, nil +} + func toMacronResponse(qdr *backend.QueryDataResponse) response.Response { statusCode := http.StatusOK for _, res := range qdr.Responses { @@ -110,56 +153,8 @@ func toMacronResponse(qdr *backend.QueryDataResponse) response.Response { return response.JSONStreaming(statusCode, qdr) } -// handleExpressions handles POST /api/ds/query when there is an expression. -func (hs *HTTPServer) handleExpressions(c *models.ReqContext, reqDTO dtos.MetricRequest) response.Response { - timeRange := plugins.NewDataTimeRange(reqDTO.From, reqDTO.To) - request := plugins.DataQuery{ - TimeRange: &timeRange, - Debug: reqDTO.Debug, - User: c.SignedInUser, - Queries: make([]plugins.DataSubQuery, 0, len(reqDTO.Queries)), - } - - for _, query := range reqDTO.Queries { - hs.log.Debug("Processing metrics query", "query", query) - name := query.Get("datasource").MustString("") - - datasourceID, err := query.Get("datasourceId").Int64() - if err != nil { - hs.log.Debug("Can't process query since it's missing data source ID") - return response.Error(400, "Query missing data source ID", nil) - } - - if name != expr.DatasourceName { - // Expression requests have everything in one request, so need to check - // all data source queries for possible permission / not found issues. - if _, err = hs.DataSourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache); err != nil { - return hs.handleGetDataSourceError(err, datasourceID) - } - } - - request.Queries = append(request.Queries, plugins.DataSubQuery{ - RefID: query.Get("refId").MustString("A"), - MaxDataPoints: query.Get("maxDataPoints").MustInt64(100), - IntervalMS: query.Get("intervalMs").MustInt64(1000), - QueryType: query.Get("queryType").MustString(""), - Model: query, - }) - } - - exprService := expr.Service{ - Cfg: hs.Cfg, - DataService: hs.DataService, - } - qdr, err := exprService.WrapTransformData(c.Req.Context(), request) - if err != nil { - return response.Error(500, "expression request error", err) - } - return toMacronResponse(qdr) -} - -func (hs *HTTPServer) handleGetDataSourceError(err error, datasourceID int64) *response.NormalResponse { - hs.log.Debug("Encountered error getting data source", "err", err, "id", datasourceID) +func (hs *HTTPServer) handleGetDataSourceError(err error, datasourceRef interface{}) *response.NormalResponse { + hs.log.Debug("Encountered error getting data source", "err", err, "ref", datasourceRef) if errors.Is(err, models.ErrDataSourceAccessDenied) { return response.Error(403, "Access denied to data source", err) } diff --git a/pkg/expr/graph.go b/pkg/expr/graph.go index ab107e2a29c..499d7dcd8f4 100644 --- a/pkg/expr/graph.go +++ b/pkg/expr/graph.go @@ -143,17 +143,30 @@ func (s *Service) buildGraph(req *Request) (*simple.DirectedGraph, error) { RefID: query.RefID, TimeRange: query.TimeRange, QueryType: query.QueryType, - DatasourceUID: query.DatasourceUID, + DatasourceUID: query.GetDatasourceUID(), } - isExpr, err := rn.IsExpressionQuery() - if err != nil { - return nil, err + numericDSID := float64(0) // legacy + if rn.DatasourceUID == "" { + if rv, ok := rn.Query["datasourceId"]; ok { + if sv, ok := rv.(float64); ok { + if sv == DatasourceID { + rn.DatasourceUID = DatasourceUID + } + if sv > 0 { + numericDSID = sv + } + } + } + } + + if rn.DatasourceUID == "" && numericDSID == 0 { + return nil, fmt.Errorf("missing datasource uid in query with refId %v", query.RefID) } var node Node - if isExpr { + if rn.IsExpressionQuery() { node, err = buildCMDNode(dp, rn) } else { node, err = s.buildDSNode(dp, rn, req) diff --git a/pkg/expr/graph_test.go b/pkg/expr/graph_test.go index 98cc34ea77c..4f21d2d27a6 100644 --- a/pkg/expr/graph_test.go +++ b/pkg/expr/graph_test.go @@ -20,7 +20,7 @@ func TestServicebuildPipeLine(t *testing.T) { Queries: []Query{ { RefID: "A", - DatasourceUID: DatasourceUID, + DatasourceUID: OldDatasourceUID, JSON: json.RawMessage(`{ "expression": "B", "reducer": "mean", @@ -41,7 +41,7 @@ func TestServicebuildPipeLine(t *testing.T) { Queries: []Query{ { RefID: "A", - DatasourceUID: DatasourceUID, + DatasourceUID: OldDatasourceUID, JSON: json.RawMessage(`{ "expression": "$B", "type": "math" @@ -49,7 +49,7 @@ func TestServicebuildPipeLine(t *testing.T) { }, { RefID: "B", - DatasourceUID: DatasourceUID, + DatasourceUID: OldDatasourceUID, JSON: json.RawMessage(`{ "expression": "$A", "type": "math" @@ -65,7 +65,7 @@ func TestServicebuildPipeLine(t *testing.T) { Queries: []Query{ { RefID: "A", - DatasourceUID: DatasourceUID, + DatasourceUID: OldDatasourceUID, JSON: json.RawMessage(`{ "expression": "$A", "type": "math" @@ -81,7 +81,7 @@ func TestServicebuildPipeLine(t *testing.T) { Queries: []Query{ { RefID: "A", - DatasourceUID: DatasourceUID, + DatasourceUID: OldDatasourceUID, JSON: json.RawMessage(`{ "expression": "$B", "type": "math" @@ -97,7 +97,7 @@ func TestServicebuildPipeLine(t *testing.T) { Queries: []Query{ { RefID: "A", - DatasourceUID: DatasourceUID, + DatasourceUID: OldDatasourceUID, JSON: json.RawMessage(`{ "type": "classic_conditions", "conditions": [ @@ -128,7 +128,7 @@ func TestServicebuildPipeLine(t *testing.T) { }, { RefID: "B", - DatasourceUID: DatasourceUID, + DatasourceUID: OldDatasourceUID, JSON: json.RawMessage(`{ "expression": "C", "reducer": "mean", @@ -149,7 +149,7 @@ func TestServicebuildPipeLine(t *testing.T) { Queries: []Query{ { RefID: "A", - DatasourceUID: DatasourceUID, + DatasourceUID: OldDatasourceUID, JSON: json.RawMessage(`{ "type": "classic_conditions", "conditions": [ @@ -180,7 +180,7 @@ func TestServicebuildPipeLine(t *testing.T) { }, { RefID: "B", - DatasourceUID: DatasourceUID, + DatasourceUID: OldDatasourceUID, JSON: json.RawMessage(`{ "expression": "A", "reducer": "mean", @@ -195,33 +195,31 @@ func TestServicebuildPipeLine(t *testing.T) { }, expectErrContains: "classic conditions may not be the input for other expressions", }, - //{ - // name: "Queries with new datasource ref object", - // req: &Request{ - // Queries: []Query{ - // { - // RefID: "A", - // JSON: json.RawMessage(`{ - // "datasource": { - // "uid": "MyDS" - // } - // }`), - // }, - // { - // RefID: "B", - // JSON: json.RawMessage(`{ - // "datasource": { - // "uid": "MyDS" - // }, - // "expression": "A", - // "reducer": "mean", - // "type": "reduce" - // }`), - // }, - // }, - // }, - // expectedOrder: []string{"B", "A"}, - //}, + { + name: "Queries with new datasource ref object", + req: &Request{ + Queries: []Query{ + { + RefID: "A", + Datasource: DataSourceRef{ + UID: DatasourceUID, + }, + JSON: json.RawMessage(`{ + "expression": "B", + "reducer": "mean", + "type": "reduce" + }`), + }, + { + RefID: "B", + Datasource: DataSourceRef{ + UID: "Fake", + }, + }, + }, + }, + expectedOrder: []string{"B", "A"}, + }, } s := Service{} for _, tt := range tests { diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index edb7ed25a82..5a8805b8dc1 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -30,62 +30,19 @@ type rawNode struct { Query map[string]interface{} QueryType string TimeRange TimeRange - DatasourceUID string + DatasourceUID string // Gets populated from Either DatasourceUID or Datasource.UID } -func (rn *rawNode) GetDatasourceUID() (string, error) { - if rn.DatasourceUID != "" { - return rn.DatasourceUID, nil +func (rn *rawNode) IsExpressionQuery() bool { + if IsDataSource(rn.DatasourceUID) { + return true } - - rawDs, ok := rn.Query["datasource"] - if !ok { - return "", fmt.Errorf("no datasource property found in query model") + if v, ok := rn.Query["datasourceId"]; ok { + if v == OldDatasourceUID { + return true + } } - - // For old queries with string datasource prop representing data source name - if dsName, ok := rawDs.(string); ok { - return dsName, nil - } - - dsRef, ok := rawDs.(map[string]interface{}) - if !ok { - return "", fmt.Errorf("data source property is not an object nor string, got %T", rawDs) - } - - if dsUid, ok := dsRef["uid"].(string); ok { - return dsUid, nil - } - - return "", fmt.Errorf("no datasource uid found for query, got %T", rn.Query) -} - -func (rn *rawNode) IsExpressionQuery() (bool, error) { - if rn.DatasourceUID != "" { - return rn.DatasourceUID == DatasourceUID, nil - } - - rawDs, ok := rn.Query["datasource"] - if !ok { - return false, fmt.Errorf("no datasource property found in query model") - } - - // For old queries with string datasource prop representing data source name - dsName, ok := rawDs.(string) - if ok && dsName == DatasourceName { - return true, nil - } - - dsRef, ok := rawDs.(map[string]interface{}) - if !ok { - return false, nil - } - - if dsRef["uid"].(string) == DatasourceUID { - return true, nil - } - - return false, nil + return false } func (rn *rawNode) GetCommandType() (c CommandType, err error) { @@ -213,6 +170,7 @@ func (s *Service) buildDSNode(dp *simple.DirectedGraph, rn *rawNode, req *Reques request: *req, } + // support old datasourceId property rawDsID, ok := rn.Query["datasourceId"] if ok { floatDsID, ok := rawDsID.(float64) @@ -221,11 +179,7 @@ func (s *Service) buildDSNode(dp *simple.DirectedGraph, rn *rawNode, req *Reques } dsNode.datasourceID = int64(floatDsID) } else { - dsUid, err := rn.GetDatasourceUID() - if err != nil { - return nil, fmt.Errorf("neither datasourceId or datasourceUid in expression data source request for refId %v", rn.RefID) - } - dsNode.datasourceUID = dsUid + dsNode.datasourceUID = rn.DatasourceUID } var floatIntervalMS float64 diff --git a/pkg/expr/service.go b/pkg/expr/service.go index 1ec8f54bb8e..ede7100ceff 100644 --- a/pkg/expr/service.go +++ b/pkg/expr/service.go @@ -4,21 +4,34 @@ import ( "context" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/setting" ) -// DatasourceName is the string constant used as the datasource name in requests -// to identify it as an expression command. -const DatasourceName = "__expr__" +// DatasourceType is the string constant used as the datasource when the property is in Datasource.Type. +// Type in requests is used to identify what type of data source plugin the request belongs to. +const DatasourceType = "__expr__" + +// DatasourceUID is the string constant used as the datasource name in requests +// to identify it as an expression command when use in Datasource.UID. +const DatasourceUID = DatasourceType // DatasourceID is the fake datasource id used in requests to identify it as an // expression command. const DatasourceID = -100 -// DatasourceUID is the fake datasource uid used in requests to identify it as an -// expression command. -const DatasourceUID = "-100" +// OldDatasourceUID is the datasource uid used in requests to identify it as an +// expression command. It goes with the query root level datasourceUID property. It was accidentally +// set to the Id and is now kept for backwards compatibility. The newer Datasource.UID property +// should be used instead and should be set to "__expr__". +const OldDatasourceUID = "-100" + +// IsDataSource checks if the uid points to an expression query +func IsDataSource(uid string) bool { + return uid == DatasourceUID || uid == OldDatasourceUID +} // Service is service representation for expression handling. type Service struct { @@ -52,3 +65,14 @@ func (s *Service) ExecutePipeline(ctx context.Context, pipeline DataPipeline) (* } return res, nil } + +func DataSourceModel() *models.DataSource { + return &models.DataSource{ + Id: DatasourceID, + Uid: DatasourceUID, + Name: DatasourceUID, + Type: DatasourceType, + JsonData: simplejson.New(), + SecureJsonData: make(map[string][]byte), + } +} diff --git a/pkg/expr/transform.go b/pkg/expr/transform.go index a96a14efafa..e9804d3b79e 100644 --- a/pkg/expr/transform.go +++ b/pkg/expr/transform.go @@ -40,6 +40,9 @@ func (s *Service) WrapTransformData(ctx context.Context, query plugins.DataQuery } for _, q := range query.Queries { + if q.DataSource == nil { + return nil, fmt.Errorf("mising datasource info: " + q.RefID) + } modelJSON, err := q.Model.MarshalJSON() if err != nil { return nil, err @@ -50,6 +53,10 @@ func (s *Service) WrapTransformData(ctx context.Context, query plugins.DataQuery RefID: q.RefID, MaxDataPoints: q.MaxDataPoints, QueryType: q.QueryType, + Datasource: DataSourceRef{ + Type: q.DataSource.Type, + UID: q.DataSource.Uid, + }, TimeRange: TimeRange{ From: query.TimeRange.GetFromAsTimeUTC(), To: query.TimeRange.GetToAsTimeUTC(), @@ -72,13 +79,30 @@ type Request struct { type Query struct { RefID string TimeRange TimeRange - DatasourceUID string + DatasourceUID string // deprecated, value -100 when expressions + Datasource DataSourceRef `json:"datasource"` JSON json.RawMessage Interval time.Duration QueryType string MaxDataPoints int64 } +type DataSourceRef struct { + Type string `json:"type"` // value should be __expr__ + UID string `json:"uid"` // value should be __expr__ +} + +func (q *Query) GetDatasourceUID() string { + if q.DatasourceUID != "" { + return q.DatasourceUID // backwards compatibility gets precedence + } + + if q.Datasource.UID != "" { + return q.Datasource.UID + } + return "" +} + // TimeRange is a time.Time based TimeRange. type TimeRange struct { From time.Time diff --git a/pkg/expr/translate/translate.go b/pkg/expr/translate/translate.go index 592790df758..4e670846c10 100644 --- a/pkg/expr/translate/translate.go +++ b/pkg/expr/translate/translate.go @@ -259,7 +259,7 @@ func (dc *dashConditionsJSON) GetNew(orgID int64) (*ngmodels.Condition, error) { ccAlertQuery := ngmodels.AlertQuery{ RefID: ccRefID, Model: exprModelJSON, - DatasourceUID: expr.DatasourceUID, + DatasourceUID: expr.OldDatasourceUID, } ngCond.Data = append(ngCond.Data, ccAlertQuery) diff --git a/pkg/services/ngalert/models/alert_query.go b/pkg/services/ngalert/models/alert_query.go index c500ca6e3fc..173c50b030f 100644 --- a/pkg/services/ngalert/models/alert_query.go +++ b/pkg/services/ngalert/models/alert_query.go @@ -89,7 +89,7 @@ func (aq *AlertQuery) setModelProps() error { // IsExpression returns true if the alert query is an expression. func (aq *AlertQuery) IsExpression() (bool, error) { - return aq.DatasourceUID == expr.DatasourceUID, nil + return expr.IsDataSource(aq.DatasourceUID), nil } // setMaxDatapoints sets the model maxDataPoints if it's missing or invalid diff --git a/public/app/core/utils/query.ts b/public/app/core/utils/query.ts index e02fb0bea03..51a6e977a2b 100644 --- a/public/app/core/utils/query.ts +++ b/public/app/core/utils/query.ts @@ -1,4 +1,5 @@ import { DataQuery, DataSourceInstanceSettings, DataSourceRef, getDataSourceRef } from '@grafana/data'; +import { isExpressionReference } from '@grafana/runtime/src/utils/DataSourceWithBackend'; export const getNextRefIdChar = (queries: DataQuery[]): string => { for (let num = 0; ; num++) { @@ -24,14 +25,13 @@ export function addQuery(queries: DataQuery[], query?: Partial, datas export function updateQueries( newSettings: DataSourceInstanceSettings, queries: DataQuery[], - extensionID: string, // pass this in because importing it creates a circular dependency dsSettings?: DataSourceInstanceSettings ): DataQuery[] { const datasource = getDataSourceRef(newSettings); if (!newSettings.meta.mixed && dsSettings?.meta.mixed) { return queries.map((q) => { - if (q.datasource !== extensionID) { + if (!isExpressionReference(q.datasource)) { q.datasource = datasource; } return q; diff --git a/public/app/features/expressions/ExpressionDatasource.ts b/public/app/features/expressions/ExpressionDatasource.ts index 9d435b90ec6..2349eec0dc9 100644 --- a/public/app/features/expressions/ExpressionDatasource.ts +++ b/public/app/features/expressions/ExpressionDatasource.ts @@ -2,6 +2,7 @@ import { DataSourceInstanceSettings, DataSourcePluginMeta, PluginType } from '@g import { ExpressionQuery, ExpressionQueryType } from './types'; import { ExpressionQueryEditor } from './ExpressionQueryEditor'; import { DataSourceWithBackend } from '@grafana/runtime'; +import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; /** * This is a singleton instance that just pretends to be a DataSource @@ -25,26 +26,24 @@ export class ExpressionDatasourceApi extends DataSourceWithBackend { @@ -7,7 +7,7 @@ export const isExpressionQuery = (dataQuery?: DataQuery): dataQuery is Expressio return false; } - if (dataQuery.datasource === ExpressionDatasourceID) { + if (isExpressionReference(dataQuery.datasource)) { return true; } diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index 3e0c1ae8537..daa6d897447 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -22,11 +22,11 @@ import { GrafanaRootScope } from 'app/routes/GrafanaCtrl'; // Pretend Datasource import { dataSource as expressionDatasource, - ExpressionDatasourceID, ExpressionDatasourceUID, instanceSettings as expressionInstanceSettings, } from 'app/features/expressions/ExpressionDatasource'; import { DataSourceVariableModel } from '../variables/types'; +import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; export class DatasourceSrv implements DataSourceService { private datasources: Record = {}; // UID @@ -58,9 +58,9 @@ export class DatasourceSrv implements DataSourceService { } // Preload expressions - this.datasources[ExpressionDatasourceID] = expressionDatasource as any; + this.datasources[ExpressionDatasourceRef.type] = expressionDatasource as any; this.datasources[ExpressionDatasourceUID] = expressionDatasource as any; - this.settingsMapByUid[ExpressionDatasourceID] = expressionInstanceSettings; + this.settingsMapByUid[ExpressionDatasourceRef.uid] = expressionInstanceSettings; this.settingsMapByUid[ExpressionDatasourceUID] = expressionInstanceSettings; } @@ -75,7 +75,7 @@ export class DatasourceSrv implements DataSourceService { if (nameOrUid === 'default' || nameOrUid === null || nameOrUid === undefined) { if (!isstring && ref) { const type = (ref as any)?.type as string; - if (type === ExpressionDatasourceID) { + if (type === ExpressionDatasourceRef.type) { return expressionDatasource.instanceSettings; } else if (type) { console.log('FIND Default instance for datasource type?', ref); diff --git a/public/app/features/query/components/QueryGroup.tsx b/public/app/features/query/components/QueryGroup.tsx index 8847055bbd7..039d8d11d03 100644 --- a/public/app/features/query/components/QueryGroup.tsx +++ b/public/app/features/query/components/QueryGroup.tsx @@ -112,7 +112,7 @@ export class QueryGroup extends PureComponent { onChangeDataSource = async (newSettings: DataSourceInstanceSettings) => { const { dsSettings } = this.state; - const queries = updateQueries(newSettings, this.state.queries, expressionDatasource.name, dsSettings); + const queries = updateQueries(newSettings, this.state.queries, dsSettings); const dataSource = await this.dataSourceSrv.get(newSettings.name); this.onChange({ diff --git a/public/app/features/query/state/runRequest.ts b/public/app/features/query/state/runRequest.ts index b3f085129f2..6f47d4acb95 100644 --- a/public/app/features/query/state/runRequest.ts +++ b/public/app/features/query/state/runRequest.ts @@ -22,13 +22,10 @@ import { } from '@grafana/data'; import { toDataQueryError } from '@grafana/runtime'; import { emitDataRequestEvent } from './queryAnalytics'; -import { - dataSource as expressionDatasource, - ExpressionDatasourceID, - ExpressionDatasourceUID, -} from 'app/features/expressions/ExpressionDatasource'; +import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource'; import { ExpressionQuery } from 'app/features/expressions/types'; import { cancelNetworkRequestsOnUnsubscribe } from './processing/canceler'; +import { isExpressionReference } from '@grafana/runtime/src/utils/DataSourceWithBackend'; type MapOfResponsePackets = { [str: string]: DataQueryResponse }; @@ -174,7 +171,7 @@ export function callQueryMethod( ) { // If any query has an expression, use the expression endpoint for (const target of request.targets) { - if (target.datasource === ExpressionDatasourceID || target.datasource === ExpressionDatasourceUID) { + if (isExpressionReference(target.datasource)) { return expressionDatasource.query(request as DataQueryRequest); } }