diff --git a/pkg/expr/graph.go b/pkg/expr/graph.go index 9f5a59d7415..22937a12b3a 100644 --- a/pkg/expr/graph.go +++ b/pkg/expr/graph.go @@ -143,11 +143,12 @@ func (s *Service) buildGraph(req *Request) (*simple.DirectedGraph, error) { } rn := &rawNode{ - Query: rawQueryProp, - RefID: query.RefID, - TimeRange: query.TimeRange, - QueryType: query.QueryType, - DataSource: query.DataSource, + Query: rawQueryProp, + RefID: query.RefID, + TimeRange: query.TimeRange, + QueryType: query.QueryType, + DataSource: query.DataSource, + QueryEnricher: query.QueryEnricher, } var node Node diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index 3ebdf75a88e..c42c082abe4 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -42,11 +42,12 @@ type baseNode struct { } type rawNode struct { - RefID string `json:"refId"` - Query map[string]interface{} - QueryType string - TimeRange TimeRange - DataSource *datasources.DataSource + RefID string `json:"refId"` + Query map[string]interface{} + QueryType string + TimeRange TimeRange + DataSource *datasources.DataSource + QueryEnricher QueryDataRequestEnricher } func (rn *rawNode) GetCommandType() (c CommandType, err error) { @@ -137,8 +138,9 @@ const ( // DSNode is a DPNode that holds a datasource request. type DSNode struct { baseNode - query json.RawMessage - datasource *datasources.DataSource + query json.RawMessage + datasource *datasources.DataSource + queryEnricher QueryDataRequestEnricher orgID int64 queryType string @@ -164,14 +166,15 @@ func (s *Service) buildDSNode(dp *simple.DirectedGraph, rn *rawNode, req *Reques id: dp.NewNode().ID(), refID: rn.RefID, }, - orgID: req.OrgId, - query: json.RawMessage(encodedQuery), - queryType: rn.QueryType, - intervalMS: defaultIntervalMS, - maxDP: defaultMaxDP, - timeRange: rn.TimeRange, - request: *req, - datasource: rn.DataSource, + orgID: req.OrgId, + query: json.RawMessage(encodedQuery), + queryType: rn.QueryType, + intervalMS: defaultIntervalMS, + maxDP: defaultMaxDP, + timeRange: rn.TimeRange, + request: *req, + datasource: rn.DataSource, + queryEnricher: rn.QueryEnricher, } var floatIntervalMS float64 @@ -205,27 +208,32 @@ func (dn *DSNode) Execute(ctx context.Context, vars mathexp.Vars, s *Service) (m OrgID: dn.orgID, DataSourceInstanceSettings: dsInstanceSettings, PluginID: dn.datasource.Type, + User: dn.request.User, } - q := []backend.DataQuery{ - { - RefID: dn.refID, - MaxDataPoints: dn.maxDP, - Interval: time.Duration(int64(time.Millisecond) * dn.intervalMS), - JSON: dn.query, - TimeRange: backend.TimeRange{ - From: dn.timeRange.From, - To: dn.timeRange.To, - }, - QueryType: dn.queryType, - }, - } - - resp, err := s.dataService.QueryData(ctx, &backend.QueryDataRequest{ + req := &backend.QueryDataRequest{ PluginContext: pc, - Queries: q, - Headers: dn.request.Headers, - }) + Queries: []backend.DataQuery{ + { + RefID: dn.refID, + MaxDataPoints: dn.maxDP, + Interval: time.Duration(int64(time.Millisecond) * dn.intervalMS), + JSON: dn.query, + TimeRange: backend.TimeRange{ + From: dn.timeRange.From, + To: dn.timeRange.To, + }, + QueryType: dn.queryType, + }, + }, + Headers: dn.request.Headers, + } + + if dn.queryEnricher != nil { + ctx = dn.queryEnricher(ctx, req) + } + + resp, err := s.dataService.QueryData(ctx, req) if err != nil { return mathexp.Results{}, err } diff --git a/pkg/expr/transform.go b/pkg/expr/transform.go index b3ae0de2532..40e5ed37b30 100644 --- a/pkg/expr/transform.go +++ b/pkg/expr/transform.go @@ -35,14 +35,19 @@ type Request struct { Debug bool OrgId int64 Queries []Query + User *backend.User } +// QueryDataRequestEnricher function definition for enriching a backend.QueryDataRequest request. +type QueryDataRequestEnricher func(ctx context.Context, req *backend.QueryDataRequest) context.Context + // Query is like plugins.DataSubQuery, but with a a time range, and only the UID // for the data source. Also interval is a time.Duration. type Query struct { RefID string TimeRange TimeRange DataSource *datasources.DataSource `json:"datasource"` + QueryEnricher QueryDataRequestEnricher JSON json.RawMessage Interval time.Duration QueryType string diff --git a/pkg/services/publicdashboards/api/common_test.go b/pkg/services/publicdashboards/api/common_test.go index 0e64857dbe3..22959cfdc33 100644 --- a/pkg/services/publicdashboards/api/common_test.go +++ b/pkg/services/publicdashboards/api/common_test.go @@ -132,7 +132,7 @@ func buildQueryDataService(t *testing.T, cs datasources.CacheService, fpc *fakeP } return query.ProvideService( - nil, + setting.NewCfg(), cs, nil, &fakePluginRequestValidator{}, diff --git a/pkg/services/query/query.go b/pkg/services/query/query.go index de256f72c0a..e4cfced4c5a 100644 --- a/pkg/services/query/query.go +++ b/pkg/services/query/query.go @@ -3,7 +3,6 @@ package query import ( "context" "fmt" - "net/http" "time" "github.com/grafana/grafana/pkg/api/dtos" @@ -112,10 +111,17 @@ func (s *Service) QueryDataMultipleSources(ctx context.Context, user *user.Signe // handleExpressions handles POST /api/ds/query when there is an expression. func (s *Service) handleExpressions(ctx context.Context, user *user.SignedInUser, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) { exprReq := expr.Request{ - OrgId: user.OrgID, Queries: []expr.Query{}, } + if user != nil { // for passthrough authentication, SSE does not authenticate + exprReq.User = adapters.BackendUserFromSignedInUser(user) + exprReq.OrgId = user.OrgID + } + + disallowedCookies := []string{s.cfg.LoginCookieName} + queryEnrichers := parsedReq.createDataSourceQueryEnrichers(ctx, user, s.oAuthTokenService, disallowedCookies) + for _, pq := range parsedReq.parsedQueries { if pq.datasource == nil { return nil, ErrMissingDataSourceInfo.Build(errutil.TemplateData{ @@ -136,6 +142,7 @@ func (s *Service) handleExpressions(ctx context.Context, user *user.SignedInUser From: pq.query.TimeRange.From, To: pq.query.TimeRange.To, }, + QueryEnricher: queryEnrichers[pq.datasource.Uid], }) } @@ -168,10 +175,11 @@ func (s *Service) handleQueryData(ctx context.Context, user *user.SignedInUser, Queries: []backend.DataQuery{}, } + disallowedCookies := []string{s.cfg.LoginCookieName} middlewares := []httpclient.Middleware{} if parsedReq.httpRequest != nil { middlewares = append(middlewares, - httpclientprovider.ForwardedCookiesMiddleware(parsedReq.httpRequest.Cookies(), ds.AllowedCookies(), []string{s.cfg.LoginCookieName}), + httpclientprovider.ForwardedCookiesMiddleware(parsedReq.httpRequest.Cookies(), ds.AllowedCookies(), disallowedCookies), ) } @@ -188,7 +196,7 @@ func (s *Service) handleQueryData(ctx context.Context, user *user.SignedInUser, } if parsedReq.httpRequest != nil { - proxyutil.ClearCookieHeader(parsedReq.httpRequest, ds.AllowedCookies(), []string{s.cfg.LoginCookieName}) + proxyutil.ClearCookieHeader(parsedReq.httpRequest, ds.AllowedCookies(), disallowedCookies) if cookieStr := parsedReq.httpRequest.Header.Get("Cookie"); cookieStr != "" { req.Headers["Cookie"] = cookieStr } @@ -203,17 +211,7 @@ func (s *Service) handleQueryData(ctx context.Context, user *user.SignedInUser, return s.pluginClient.QueryData(ctx, req) } -type parsedQuery struct { - datasource *datasources.DataSource - query backend.DataQuery -} - -type parsedRequest struct { - hasExpression bool - parsedQueries []parsedQuery - httpRequest *http.Request -} - +// parseRequest parses a request into parsed queries grouped by datasource uid func (s *Service) parseMetricRequest(ctx context.Context, user *user.SignedInUser, skipCache bool, reqDTO dtos.MetricRequest) (*parsedRequest, error) { if len(reqDTO.Queries) == 0 { return nil, ErrNoQueriesFound diff --git a/pkg/services/query/query_parsing.go b/pkg/services/query/query_parsing.go new file mode 100644 index 00000000000..515b17ea81f --- /dev/null +++ b/pkg/services/query/query_parsing.go @@ -0,0 +1,91 @@ +package query + +import ( + "context" + "fmt" + "net/http" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana/pkg/expr" + "github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/oauthtoken" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/util/proxyutil" + "golang.org/x/oauth2" +) + +type parsedQuery struct { + datasource *datasources.DataSource + query backend.DataQuery +} + +type parsedRequest struct { + hasExpression bool + parsedQueries []parsedQuery + httpRequest *http.Request +} + +func (pr parsedRequest) createDataSourceQueryEnrichers(ctx context.Context, signedInUser *user.SignedInUser, oAuthTokenService oauthtoken.OAuthTokenService, disallowedCookies []string) map[string]expr.QueryDataRequestEnricher { + datasourcesHeaderProvider := map[string]expr.QueryDataRequestEnricher{} + + if pr.httpRequest == nil { + return datasourcesHeaderProvider + } + + if len(pr.parsedQueries) == 0 || pr.parsedQueries[0].datasource == nil { + return datasourcesHeaderProvider + } + + for _, q := range pr.parsedQueries { + ds := q.datasource + uid := ds.Uid + + if expr.IsDataSource(uid) { + continue + } + + if _, exists := datasourcesHeaderProvider[uid]; exists { + continue + } + + allowedCookies := ds.AllowedCookies() + clonedReq := pr.httpRequest.Clone(pr.httpRequest.Context()) + + var token *oauth2.Token + if oAuthTokenService.IsOAuthPassThruEnabled(ds) { + token = oAuthTokenService.GetCurrentOAuthToken(ctx, signedInUser) + } + + datasourcesHeaderProvider[uid] = func(ctx context.Context, req *backend.QueryDataRequest) context.Context { + if len(req.Headers) == 0 { + req.Headers = map[string]string{} + } + + if len(allowedCookies) > 0 { + proxyutil.ClearCookieHeader(clonedReq, allowedCookies, disallowedCookies) + if cookieStr := clonedReq.Header.Get("Cookie"); cookieStr != "" { + req.Headers["Cookie"] = cookieStr + } + + ctx = httpclient.WithContextualMiddleware(ctx, httpclientprovider.ForwardedCookiesMiddleware(clonedReq.Cookies(), allowedCookies, disallowedCookies)) + } + + if token != nil { + req.Headers["Authorization"] = fmt.Sprintf("%s %s", token.Type(), token.AccessToken) + + idToken, ok := token.Extra("id_token").(string) + if ok && idToken != "" { + req.Headers["X-ID-Token"] = idToken + } + + ctx = httpclient.WithContextualMiddleware(ctx, httpclientprovider.ForwardedOAuthIdentityMiddleware(token)) + } + + return ctx + } + } + + return datasourcesHeaderProvider +} diff --git a/pkg/services/query/query_test.go b/pkg/services/query/query_test.go index 3e4ab7427a9..dc7a2900815 100644 --- a/pkg/services/query/query_test.go +++ b/pkg/services/query/query_test.go @@ -1,18 +1,18 @@ -package query_test +package query import ( "context" "errors" "net/http" + "net/http/httptest" "testing" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana/pkg/expr" - "github.com/stretchr/testify/require" - "golang.org/x/oauth2" - + "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/expr" + "github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" @@ -20,15 +20,243 @@ import ( fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes" dsSvc "github.com/grafana/grafana/pkg/services/datasources/service" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/services/secrets/fakes" secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore" secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/oauth2" ) +func TestParseMetricRequest(t *testing.T) { + t.Run("Test a simple single datasource query", func(t *testing.T) { + tc := setup(t) + json, err := simplejson.NewJson([]byte(`{ + "keepCookies": [ "cookie1", "cookie3", "login" ] + }`)) + require.NoError(t, err) + tc.dataSourceCache.dsByUid = func(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) { + if datasourceUID == "gIEkMvIVz" { + return &datasources.DataSource{ + Uid: "gIEkMvIVz", + JsonData: json, + }, nil + } + + return nil, nil + } + + token := &oauth2.Token{ + TokenType: "bearer", + AccessToken: "access-token", + } + token = token.WithExtra(map[string]interface{}{"id_token": "id-token"}) + + tc.oauthTokenService.passThruEnabled = true + tc.oauthTokenService.token = token + + mr := metricRequestWithQueries(t, `{ + "refId": "A", + "datasource": { + "uid": "gIEkMvIVz", + "type": "postgres" + } + }`, `{ + "refId": "B", + "datasource": { + "uid": "gIEkMvIVz", + "type": "postgres" + } + }`) + parsedReq, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr) + require.NoError(t, err) + require.NotNil(t, parsedReq) + assert.False(t, parsedReq.hasExpression) + assert.Len(t, parsedReq.parsedQueries, 2) + assert.Equal(t, "gIEkMvIVz", parsedReq.parsedQueries[0].datasource.Uid) + assert.Equal(t, "gIEkMvIVz", parsedReq.parsedQueries[1].datasource.Uid) + + t.Run("createDataSourceQueryEnrichers should return 0 enrichers when no HTTP request", func(t *testing.T) { + enrichers := parsedReq.createDataSourceQueryEnrichers(context.Background(), nil, tc.oauthTokenService, []string{}) + require.Empty(t, enrichers) + }) + + t.Run("createDataSourceQueryEnrichers should return 1 enricher", func(t *testing.T) { + parsedReq.httpRequest = httptest.NewRequest(http.MethodGet, "/", nil) + parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "cookie1"}) + parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "cookie2"}) + parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "cookie3"}) + parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "login"}) + + enrichers := parsedReq.createDataSourceQueryEnrichers(context.Background(), nil, tc.oauthTokenService, []string{"login"}) + require.Len(t, enrichers, 1) + require.NotNil(t, enrichers["gIEkMvIVz"]) + req := &backend.QueryDataRequest{} + ctx := enrichers["gIEkMvIVz"](context.Background(), req) + require.Len(t, req.Headers, 3) + require.Equal(t, "Bearer access-token", req.Headers["Authorization"]) + require.Equal(t, "id-token", req.Headers["X-ID-Token"]) + require.Equal(t, "cookie1=; cookie3=", req.Headers["Cookie"]) + middlewares := httpclient.ContextualMiddlewareFromContext(ctx) + require.Len(t, middlewares, 2) + require.Equal(t, httpclientprovider.ForwardedCookiesMiddlewareName, middlewares[0].(httpclient.MiddlewareName).MiddlewareName()) + require.Equal(t, httpclientprovider.ForwardedOAuthIdentityMiddlewareName, middlewares[1].(httpclient.MiddlewareName).MiddlewareName()) + }) + }) + + t.Run("Test a single datasource query with expressions", func(t *testing.T) { + tc := setup(t) + json, err := simplejson.NewJson([]byte(`{ + "keepCookies": [ "cookie1", "cookie3", "login" ] + }`)) + require.NoError(t, err) + tc.dataSourceCache.dsByUid = func(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) { + if datasourceUID == "gIEkMvIVz" { + return &datasources.DataSource{ + Uid: "gIEkMvIVz", + JsonData: json, + }, nil + } + + return nil, nil + } + + token := &oauth2.Token{ + TokenType: "bearer", + AccessToken: "access-token", + } + token = token.WithExtra(map[string]interface{}{"id_token": "id-token"}) + + tc.oauthTokenService.passThruEnabled = true + tc.oauthTokenService.token = token + + mr := metricRequestWithQueries(t, `{ + "refId": "A", + "datasource": { + "uid": "gIEkMvIVz", + "type": "postgres" + } + }`, `{ + "refId": "B", + "datasource": { + "type": "__expr__", + "uid": "__expr__", + "name": "Expression" + }, + "type": "math", + "expression": "$A - 50" + }`) + parsedReq, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr) + require.NoError(t, err) + require.NotNil(t, parsedReq) + assert.True(t, parsedReq.hasExpression) + assert.Len(t, parsedReq.parsedQueries, 2) + assert.Equal(t, "gIEkMvIVz", parsedReq.parsedQueries[0].datasource.Uid) + assert.Equal(t, expr.DatasourceUID, parsedReq.parsedQueries[1].datasource.Uid) + + // Make sure we end up with something valid + _, err = tc.queryService.handleExpressions(context.Background(), tc.signedInUser, parsedReq) + assert.NoError(t, err) + + t.Run("createDataSourceQueryEnrichers should return 1 enricher", func(t *testing.T) { + parsedReq.httpRequest = httptest.NewRequest(http.MethodGet, "/", nil) + parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "cookie1"}) + parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "cookie2"}) + parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "cookie3"}) + parsedReq.httpRequest.AddCookie(&http.Cookie{Name: "login"}) + + enrichers := parsedReq.createDataSourceQueryEnrichers(context.Background(), nil, tc.oauthTokenService, []string{"login"}) + require.Len(t, enrichers, 1) + require.NotNil(t, enrichers["gIEkMvIVz"]) + + req := &backend.QueryDataRequest{} + ctx := enrichers["gIEkMvIVz"](context.Background(), req) + require.Len(t, req.Headers, 3) + require.Equal(t, "Bearer access-token", req.Headers["Authorization"]) + require.Equal(t, "id-token", req.Headers["X-ID-Token"]) + require.Equal(t, "cookie1=; cookie3=", req.Headers["Cookie"]) + middlewares := httpclient.ContextualMiddlewareFromContext(ctx) + require.Len(t, middlewares, 2) + require.Equal(t, httpclientprovider.ForwardedCookiesMiddlewareName, middlewares[0].(httpclient.MiddlewareName).MiddlewareName()) + require.Equal(t, httpclientprovider.ForwardedOAuthIdentityMiddlewareName, middlewares[1].(httpclient.MiddlewareName).MiddlewareName()) + }) + }) + + t.Run("Test a mixed datasource query with expressions", func(t *testing.T) { + tc := setup(t) + mr := metricRequestWithQueries(t, `{ + "refId": "A", + "datasource": { + "uid": "gIEkMvIVz", + "type": "postgres" + } + }`, `{ + "refId": "B", + "datasource": { + "uid": "sEx6ZvSVk", + "type": "testdata" + } + }`, `{ + "refId": "A_resample", + "datasource": { + "type": "__expr__", + "uid": "__expr__", + "name": "Expression" + }, + "expression": "A", + "type": "resample", + "downsampler": "mean", + "upsampler": "fillna", + "window": "10s" + }`, `{ + "refId": "B_resample", + "datasource": { + "type": "__expr__", + "uid": "__expr__", + "name": "Expression" + }, + "expression": "B", + "type": "resample", + "downsampler": "mean", + "upsampler": "fillna", + "window": "10s" + }`, `{ + "refId": "C", + "datasource": { + "type": "__expr__", + "uid": "__expr__", + "name": "Expression" + }, + "type": "math", + "expression": "$A_resample + $B_resample" + }`) + parsedReq, err := tc.queryService.parseMetricRequest(context.Background(), tc.signedInUser, true, mr) + require.NoError(t, err) + require.NotNil(t, parsedReq) + assert.True(t, parsedReq.hasExpression) + assert.Len(t, parsedReq.parsedQueries, 5) + assert.Equal(t, "gIEkMvIVz", parsedReq.parsedQueries[0].datasource.Uid) + assert.Equal(t, "sEx6ZvSVk", parsedReq.parsedQueries[1].datasource.Uid) + assert.Equal(t, expr.DatasourceUID, parsedReq.parsedQueries[2].datasource.Uid) + assert.Equal(t, expr.DatasourceUID, parsedReq.parsedQueries[3].datasource.Uid) + assert.Equal(t, expr.DatasourceUID, parsedReq.parsedQueries[4].datasource.Uid) + // Make sure we end up with something valid + _, err = tc.queryService.handleExpressions(context.Background(), tc.signedInUser, parsedReq) + assert.NoError(t, err) + + t.Run("createDataSourceQueryEnrichers should return 2 enrichers", func(t *testing.T) { + parsedReq.httpRequest = &http.Request{} + enrichers := parsedReq.createDataSourceQueryEnrichers(context.Background(), nil, tc.oauthTokenService, []string{}) + require.Len(t, enrichers, 2) + require.NotNil(t, enrichers["gIEkMvIVz"]) + require.NotNil(t, enrichers["sEx6ZvSVk"]) + }) + }) +} + func TestQueryDataMultipleSources(t *testing.T) { t.Run("can query multiple datasources", func(t *testing.T) { tc := setup(t) @@ -127,7 +355,12 @@ func TestQueryData(t *testing.T) { tc.oauthTokenService.passThruEnabled = true tc.oauthTokenService.token = token - _, err := tc.queryService.QueryData(context.Background(), nil, true, metricRequest(), false) + metricReq := metricRequest() + httpReq, err := http.NewRequest(http.MethodGet, "/", nil) + require.NoError(t, err) + metricReq.HTTPRequest = httpReq + + _, err = tc.queryService.QueryData(context.Background(), nil, true, metricReq, false) require.Nil(t, err) expected := map[string]string{ @@ -190,7 +423,9 @@ func setup(t *testing.T) *testContext { DataSources: nil, SimulatePluginFailure: false, } - exprService := expr.ProvideService(nil, pc, fakeDatasourceService) + cfg := setting.NewCfg() + cfg.ExpressionsEnabled = true + exprService := expr.ProvideService(cfg, pc, fakeDatasourceService) return &testContext{ pluginContext: pc, @@ -198,7 +433,8 @@ func setup(t *testing.T) *testContext { dataSourceCache: dc, oauthTokenService: tc, pluginRequestValidator: rv, - queryService: query.ProvideService(setting.NewCfg(), dc, exprService, rv, ds, pc, tc), + queryService: ProvideService(setting.NewCfg(), dc, exprService, rv, ds, pc, tc), + signedInUser: &user.SignedInUser{OrgID: 1}, } } @@ -208,7 +444,8 @@ type testContext struct { dataSourceCache *fakeDataSourceCache oauthTokenService *fakeOAuthTokenService pluginRequestValidator *fakePluginRequestValidator - queryService *query.Service + queryService *Service + signedInUser *user.SignedInUser } func metricRequest() dtos.MetricRequest { @@ -221,6 +458,22 @@ func metricRequest() dtos.MetricRequest { } } +func metricRequestWithQueries(t *testing.T, rawQueries ...string) dtos.MetricRequest { + t.Helper() + queries := make([]*simplejson.Json, 0) + for _, q := range rawQueries { + json, err := simplejson.NewJson([]byte(q)) + require.NoError(t, err) + queries = append(queries, json) + } + return dtos.MetricRequest{ + From: "now-1h", + To: "now", + Queries: queries, + Debug: false, + } +} + type fakePluginRequestValidator struct { err error } @@ -243,7 +496,8 @@ func (ts *fakeOAuthTokenService) IsOAuthPassThruEnabled(*datasources.DataSource) } type fakeDataSourceCache struct { - ds *datasources.DataSource + ds *datasources.DataSource + dsByUid func(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) } func (c *fakeDataSourceCache) GetDatasource(ctx context.Context, datasourceID int64, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) { @@ -251,7 +505,13 @@ func (c *fakeDataSourceCache) GetDatasource(ctx context.Context, datasourceID in } func (c *fakeDataSourceCache) GetDatasourceByUID(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) { - return c.ds, nil + if c.dsByUid != nil { + return c.dsByUid(ctx, datasourceUID, user, skipCache) + } + + return &datasources.DataSource{ + Uid: datasourceUID, + }, nil } type fakePluginClient struct {