Browse Source

DataSourceApi: convert interface to abstract class (#16979)

* DataSourceApi as class

* add diff signature

* Prometheus: moved directUrl to jsonData
Ryan McKinley 6 năm trước cách đây
mục cha
commit
1d7bb2a763

+ 19 - 14
packages/grafana-ui/src/types/datasource.ts

@@ -117,15 +117,30 @@ export interface DataSourceConstructor<
 /**
  * The main data source abstraction interface, represents an instance of a data source
  */
-export interface DataSourceApi<
+export abstract class DataSourceApi<
   TQuery extends DataQuery = DataQuery,
   TOptions extends DataSourceJsonData = DataSourceJsonData
 > {
+  /**
+   *  Set in constructor
+   */
+  readonly name: string;
+
+  /**
+   *  Set in constructor
+   */
+  readonly id: number;
+
   /**
    *  min interval range
    */
   interval?: string;
 
+  constructor(instanceSettings: DataSourceInstanceSettings<TOptions>) {
+    this.name = instanceSettings.name;
+    this.id = instanceSettings.id;
+  }
+
   /**
    * Imports queries from a different datasource
    */
@@ -139,12 +154,12 @@ export interface DataSourceApi<
   /**
    * Main metrics / data query action
    */
-  query(options: DataQueryRequest<TQuery>, observer?: DataStreamObserver): Promise<DataQueryResponse>;
+  abstract query(options: DataQueryRequest<TQuery>, observer?: DataStreamObserver): Promise<DataQueryResponse>;
 
   /**
    * Test & verify datasource settings & connection details
    */
-  testDatasource(): Promise<any>;
+  abstract testDatasource(): Promise<any>;
 
   /**
    *  Get hints for query improvements
@@ -156,16 +171,6 @@ export interface DataSourceApi<
    */
   getQueryDisplayText?(query: TQuery): string;
 
-  /**
-   *  Set after constructor is called by Grafana
-   */
-  name?: string;
-
-  /**
-   *  Set after constructor is called by Grafana
-   */
-  id?: number;
-
   /**
    * Set after constructor call, as the data source instance is the most common thing to pass around
    * we attach the components to this instance for easy access
@@ -178,7 +183,7 @@ export interface DataSourceApi<
   meta?: DataSourcePluginMeta;
 }
 
-export interface ExploreDataSourceApi<
+export abstract class ExploreDataSourceApi<
   TQuery extends DataQuery = DataQuery,
   TOptions extends DataSourceJsonData = DataSourceJsonData
 > extends DataSourceApi<TQuery, TOptions> {

+ 1 - 0
packages/grafana-ui/src/utils/moment_wrapper.ts

@@ -59,6 +59,7 @@ export interface DateTime extends Object {
   subtract: (amount?: DateTimeInput, unit?: DurationUnit) => DateTime;
   toDate: () => Date;
   toISOString: () => string;
+  diff: (amount: DateTimeInput, unit?: DurationUnit, truncate?: boolean) => number;
   valueOf: () => number;
   unix: () => number;
   utc: () => DateTime;

+ 7 - 5
pkg/api/frontendsettings.go

@@ -3,6 +3,7 @@ package api
 import (
 	"strconv"
 
+	"github.com/grafana/grafana/pkg/components/simplejson"
 	"github.com/grafana/grafana/pkg/util"
 
 	"github.com/grafana/grafana/pkg/bus"
@@ -86,12 +87,13 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
 			defaultDatasource = ds.Name
 		}
 
-		if ds.JsonData != nil {
-			dsMap["jsonData"] = ds.JsonData
-		} else {
-			dsMap["jsonData"] = make(map[string]string)
+		jsonData := ds.JsonData
+		if jsonData == nil {
+			jsonData = simplejson.New()
 		}
 
+		dsMap["jsonData"] = jsonData
+
 		if ds.Access == m.DS_ACCESS_DIRECT {
 			if ds.BasicAuth {
 				dsMap["basicAuth"] = util.GetBasicAuthHeader(ds.BasicAuthUser, ds.DecryptedBasicAuthPassword())
@@ -123,7 +125,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
 
 		if ds.Type == m.DS_PROMETHEUS {
 			// add unproxied server URL for link to Prometheus web UI
-			dsMap["directUrl"] = ds.Url
+			jsonData.Set("directUrl", ds.Url)
 		}
 
 		datasources[ds.Name] = dsMap

+ 0 - 2
public/app/features/plugins/datasource_srv.ts

@@ -65,8 +65,6 @@ export class DatasourceSrv {
           instanceSettings: dsConfig,
         });
 
-        instance.id = dsConfig.id;
-        instance.name = name;
         instance.components = dsPlugin.components;
         instance.meta = dsConfig.meta;
 

+ 2 - 3
public/app/plugins/datasource/cloudwatch/datasource.ts

@@ -9,9 +9,8 @@ import { TemplateSrv } from 'app/features/templating/template_srv';
 import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
 // import * as moment from 'moment';
 
-export default class CloudWatchDatasource implements DataSourceApi<CloudWatchQuery> {
+export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery> {
   type: any;
-  name: any;
   proxyUrl: any;
   defaultRegion: any;
   standardStatistics: any;
@@ -24,8 +23,8 @@ export default class CloudWatchDatasource implements DataSourceApi<CloudWatchQue
     private templateSrv: TemplateSrv,
     private timeSrv: TimeSrv
   ) {
+    super(instanceSettings);
     this.type = 'cloudwatch';
-    this.name = instanceSettings.name;
     this.proxyUrl = instanceSettings.url;
     this.defaultRegion = instanceSettings.jsonData.defaultRegion;
     this.instanceSettings = instanceSettings;

+ 2 - 5
public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts

@@ -7,9 +7,7 @@ import { DataSourceApi, DataQueryRequest, DataSourceInstanceSettings } from '@gr
 import { BackendSrv } from 'app/core/services/backend_srv';
 import { TemplateSrv } from 'app/features/templating/template_srv';
 
-export default class Datasource implements DataSourceApi<AzureMonitorQuery, AzureDataSourceJsonData> {
-  id: number;
-  name: string;
+export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDataSourceJsonData> {
   azureMonitorDatasource: AzureMonitorDatasource;
   appInsightsDatasource: AppInsightsDatasource;
   azureLogAnalyticsDatasource: AzureLogAnalyticsDatasource;
@@ -21,8 +19,7 @@ export default class Datasource implements DataSourceApi<AzureMonitorQuery, Azur
     private templateSrv: TemplateSrv,
     private $q
   ) {
-    this.name = instanceSettings.name;
-    this.id = instanceSettings.id;
+    super(instanceSettings);
     this.azureMonitorDatasource = new AzureMonitorDatasource(instanceSettings, this.backendSrv, this.templateSrv);
     this.appInsightsDatasource = new AppInsightsDatasource(
       instanceSettings,

+ 3 - 13
public/app/plugins/datasource/input/InputDatasource.ts

@@ -8,23 +8,13 @@ import {
 } from '@grafana/ui/src/types';
 import { InputQuery, InputOptions } from './types';
 
-export class InputDatasource implements DataSourceApi<InputQuery, InputOptions> {
+export class InputDatasource extends DataSourceApi<InputQuery, InputOptions> {
   data: SeriesData[];
 
-  // Filled in by grafana plugin system
-  name?: string;
-
-  // Filled in by grafana plugin system
-  id?: number;
-
   constructor(instanceSettings: DataSourceInstanceSettings<InputOptions>) {
-    if (instanceSettings.jsonData) {
-      this.data = instanceSettings.jsonData.data;
-    }
+    super(instanceSettings);
 
-    if (!this.data) {
-      this.data = [];
-    }
+    this.data = instanceSettings.jsonData.data ? instanceSettings.jsonData.data : [];
   }
 
   getDescription(data: SeriesData[]): string {

+ 20 - 17
public/app/plugins/datasource/loki/datasource.test.ts

@@ -2,6 +2,8 @@ import LokiDatasource from './datasource';
 import { LokiQuery } from './types';
 import { getQueryOptions } from 'test/helpers/getQueryOptions';
 import { SeriesData } from '@grafana/ui';
+import { BackendSrv } from 'app/core/services/backend_srv';
+import { TemplateSrv } from 'app/features/templating/template_srv';
 
 describe('LokiDatasource', () => {
   const instanceSettings: any = {
@@ -21,14 +23,15 @@ describe('LokiDatasource', () => {
 
   describe('when querying', () => {
     const backendSrvMock = { datasourceRequest: jest.fn() };
+    const backendSrv = (backendSrvMock as unknown) as BackendSrv;
 
-    const templateSrvMock = {
+    const templateSrvMock = ({
       getAdhocFilters: () => [],
       replace: a => a,
-    };
+    } as unknown) as TemplateSrv;
 
     test('should use default max lines when no limit given', () => {
-      const ds = new LokiDatasource(instanceSettings, backendSrvMock, templateSrvMock);
+      const ds = new LokiDatasource(instanceSettings, backendSrv, templateSrvMock);
       backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp));
       const options = getQueryOptions<LokiQuery>({ targets: [{ expr: 'foo', refId: 'B' }] });
 
@@ -41,7 +44,7 @@ describe('LokiDatasource', () => {
     test('should use custom max lines if limit is set', () => {
       const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
       const customSettings = { ...instanceSettings, jsonData: customData };
-      const ds = new LokiDatasource(customSettings, backendSrvMock, templateSrvMock);
+      const ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
       backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp));
 
       const options = getQueryOptions<LokiQuery>({ targets: [{ expr: 'foo', refId: 'B' }] });
@@ -54,7 +57,7 @@ describe('LokiDatasource', () => {
     test('should return series data', async done => {
       const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 };
       const customSettings = { ...instanceSettings, jsonData: customData };
-      const ds = new LokiDatasource(customSettings, backendSrvMock, templateSrvMock);
+      const ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock);
       backendSrvMock.datasourceRequest = jest.fn(() => Promise.resolve(testResp));
 
       const options = getQueryOptions<LokiQuery>({
@@ -77,7 +80,7 @@ describe('LokiDatasource', () => {
 
     describe('and call succeeds', () => {
       beforeEach(async () => {
-        const backendSrv = {
+        const backendSrv = ({
           async datasourceRequest() {
             return Promise.resolve({
               status: 200,
@@ -86,8 +89,8 @@ describe('LokiDatasource', () => {
               },
             });
           },
-        };
-        ds = new LokiDatasource(instanceSettings, backendSrv, {});
+        } as unknown) as BackendSrv;
+        ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv);
         result = await ds.testDatasource();
       });
 
@@ -98,7 +101,7 @@ describe('LokiDatasource', () => {
 
     describe('and call fails with 401 error', () => {
       beforeEach(async () => {
-        const backendSrv = {
+        const backendSrv = ({
           async datasourceRequest() {
             return Promise.reject({
               statusText: 'Unauthorized',
@@ -108,8 +111,8 @@ describe('LokiDatasource', () => {
               },
             });
           },
-        };
-        ds = new LokiDatasource(instanceSettings, backendSrv, {});
+        } as unknown) as BackendSrv;
+        ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv);
         result = await ds.testDatasource();
       });
 
@@ -121,7 +124,7 @@ describe('LokiDatasource', () => {
 
     describe('and call fails with 404 error', () => {
       beforeEach(async () => {
-        const backendSrv = {
+        const backendSrv = ({
           async datasourceRequest() {
             return Promise.reject({
               statusText: 'Not found',
@@ -129,8 +132,8 @@ describe('LokiDatasource', () => {
               data: '404 page not found',
             });
           },
-        };
-        ds = new LokiDatasource(instanceSettings, backendSrv, {});
+        } as unknown) as BackendSrv;
+        ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv);
         result = await ds.testDatasource();
       });
 
@@ -142,7 +145,7 @@ describe('LokiDatasource', () => {
 
     describe('and call fails with 502 error', () => {
       beforeEach(async () => {
-        const backendSrv = {
+        const backendSrv = ({
           async datasourceRequest() {
             return Promise.reject({
               statusText: 'Bad Gateway',
@@ -150,8 +153,8 @@ describe('LokiDatasource', () => {
               data: '',
             });
           },
-        };
-        ds = new LokiDatasource(instanceSettings, backendSrv, {});
+        } as unknown) as BackendSrv;
+        ds = new LokiDatasource(instanceSettings, backendSrv, {} as TemplateSrv);
         result = await ds.testDatasource();
       });
 

+ 17 - 4
public/app/plugins/datasource/loki/datasource.ts

@@ -9,8 +9,16 @@ import { logStreamToSeriesData } from './result_transformer';
 import { formatQuery, parseQuery } from './query_utils';
 
 // Types
-import { PluginMeta, DataQueryRequest, SeriesData } from '@grafana/ui/src/types';
-import { LokiQuery } from './types';
+import {
+  PluginMeta,
+  DataQueryRequest,
+  SeriesData,
+  DataSourceApi,
+  DataSourceInstanceSettings,
+} from '@grafana/ui/src/types';
+import { LokiQuery, LokiOptions } from './types';
+import { BackendSrv } from 'app/core/services/backend_srv';
+import { TemplateSrv } from 'app/features/templating/template_srv';
 
 export const DEFAULT_MAX_LINES = 1000;
 
@@ -30,12 +38,17 @@ function serializeParams(data: any) {
     .join('&');
 }
 
-export class LokiDatasource {
+export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
   languageProvider: LanguageProvider;
   maxLines: number;
 
   /** @ngInject */
-  constructor(private instanceSettings, private backendSrv, private templateSrv) {
+  constructor(
+    private instanceSettings: DataSourceInstanceSettings<LokiOptions>,
+    private backendSrv: BackendSrv,
+    private templateSrv: TemplateSrv
+  ) {
+    super(instanceSettings);
     this.languageProvider = new LanguageProvider(this);
     const settingsData = instanceSettings.jsonData || {};
     this.maxLines = parseInt(settingsData.maxLines, 10) || DEFAULT_MAX_LINES;

+ 5 - 1
public/app/plugins/datasource/loki/types.ts

@@ -1,9 +1,13 @@
-import { DataQuery, Labels } from '@grafana/ui/src/types';
+import { DataQuery, Labels, DataSourceJsonData } from '@grafana/ui/src/types';
 
 export interface LokiQuery extends DataQuery {
   expr: string;
 }
 
+export interface LokiOptions extends DataSourceJsonData {
+  maxLines?: string;
+}
+
 export interface LokiLogsStream {
   labels: string;
   entries: LokiLogsStreamEntry[];

+ 5 - 3
public/app/plugins/datasource/mixed/datasource.ts

@@ -1,11 +1,13 @@
 import _ from 'lodash';
 
-import { DataSourceApi, DataQuery, DataQueryRequest } from '@grafana/ui';
+import { DataSourceApi, DataQuery, DataQueryRequest, DataSourceInstanceSettings } from '@grafana/ui';
 import DatasourceSrv from 'app/features/plugins/datasource_srv';
 
-class MixedDatasource implements DataSourceApi<DataQuery> {
+class MixedDatasource extends DataSourceApi<DataQuery> {
   /** @ngInject */
-  constructor(private datasourceSrv: DatasourceSrv) {}
+  constructor(instanceSettings: DataSourceInstanceSettings, private datasourceSrv: DatasourceSrv) {
+    super(instanceSettings);
+  }
 
   query(options: DataQueryRequest<DataQuery>) {
     const sets = _.groupBy(options.targets, 'datasource');

+ 17 - 9
public/app/plugins/datasource/prometheus/datasource.ts

@@ -14,14 +14,15 @@ import { getQueryHints } from './query_hints';
 import { expandRecordingRules } from './language_utils';
 
 // Types
-import { PromQuery } from './types';
-import { DataQueryRequest, DataSourceApi, AnnotationEvent } from '@grafana/ui/src/types';
+import { PromQuery, PromOptions } from './types';
+import { DataQueryRequest, DataSourceApi, AnnotationEvent, DataSourceInstanceSettings } from '@grafana/ui/src/types';
 import { ExploreUrlState } from 'app/types/explore';
+import { TemplateSrv } from 'app/features/templating/template_srv';
+import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
 
-export class PrometheusDatasource implements DataSourceApi<PromQuery> {
+export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions> {
   type: string;
   editorSrc: string;
-  name: string;
   ruleMappings: { [index: string]: string };
   url: string;
   directUrl: string;
@@ -35,25 +36,32 @@ export class PrometheusDatasource implements DataSourceApi<PromQuery> {
   resultTransformer: ResultTransformer;
 
   /** @ngInject */
-  constructor(instanceSettings, private $q, private backendSrv: BackendSrv, private templateSrv, private timeSrv) {
+  constructor(
+    instanceSettings: DataSourceInstanceSettings<PromOptions>,
+    private $q,
+    private backendSrv: BackendSrv,
+    private templateSrv: TemplateSrv,
+    private timeSrv: TimeSrv
+  ) {
+    super(instanceSettings);
+
     this.type = 'prometheus';
     this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
-    this.name = instanceSettings.name;
     this.url = instanceSettings.url;
-    this.directUrl = instanceSettings.directUrl;
     this.basicAuth = instanceSettings.basicAuth;
     this.withCredentials = instanceSettings.withCredentials;
     this.interval = instanceSettings.jsonData.timeInterval || '15s';
     this.queryTimeout = instanceSettings.jsonData.queryTimeout;
     this.httpMethod = instanceSettings.jsonData.httpMethod || 'GET';
+    this.directUrl = instanceSettings.jsonData.directUrl;
     this.resultTransformer = new ResultTransformer(templateSrv);
     this.ruleMappings = {};
     this.languageProvider = new PrometheusLanguageProvider(this);
   }
 
-  init() {
+  init = () => {
     this.loadRules();
-  }
+  };
 
   getQueryDisplayText(query: PromQuery) {
     return query.expr;

+ 11 - 1
public/app/plugins/datasource/prometheus/specs/completer.test.ts

@@ -1,6 +1,10 @@
 import { PromCompleter } from '../completer';
 import { PrometheusDatasource } from '../datasource';
 import { BackendSrv } from 'app/core/services/backend_srv';
+import { DataSourceInstanceSettings } from '@grafana/ui';
+import { PromOptions } from '../types';
+import { TemplateSrv } from 'app/features/templating/template_srv';
+import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
 jest.mock('../datasource');
 jest.mock('app/core/services/backend_srv');
 
@@ -16,7 +20,13 @@ describe('Prometheus editor completer', () => {
   const editor = {};
 
   const backendSrv = {} as BackendSrv;
-  const datasourceStub = new PrometheusDatasource({}, {}, backendSrv, {}, {});
+  const datasourceStub = new PrometheusDatasource(
+    {} as DataSourceInstanceSettings<PromOptions>,
+    {},
+    backendSrv,
+    {} as TemplateSrv,
+    {} as TimeSrv
+  );
 
   datasourceStub.metadataRequest = jest.fn(() =>
     Promise.resolve({ data: { data: [{ metric: { job: 'node', instance: 'localhost:9100' } }] } })

+ 14 - 10
public/app/plugins/datasource/prometheus/specs/datasource.test.ts

@@ -8,6 +8,10 @@ import {
   prometheusSpecialRegexEscape,
 } from '../datasource';
 import { dateTime } from '@grafana/ui/src/utils/moment_wrapper';
+import { DataSourceInstanceSettings } from '@grafana/ui';
+import { PromOptions } from '../types';
+import { TemplateSrv } from 'app/features/templating/template_srv';
+import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
 
 jest.mock('../metric_find_query');
 
@@ -18,13 +22,13 @@ const DEFAULT_TEMPLATE_SRV_MOCK = {
 
 describe('PrometheusDatasource', () => {
   const ctx: any = {};
-  const instanceSettings = {
+  const instanceSettings = ({
     url: 'proxied',
     directUrl: 'direct',
     user: 'test',
     password: 'mupp',
     jsonData: {} as any,
-  };
+  } as unknown) as DataSourceInstanceSettings<PromOptions>;
 
   ctx.backendSrvMock = {};
 
@@ -347,27 +351,27 @@ const HOUR = 60 * MINUTE;
 const time = ({ hours = 0, seconds = 0, minutes = 0 }) => dateTime(hours * HOUR + minutes * MINUTE + seconds * SECOND);
 
 const ctx = {} as any;
-const instanceSettings = {
+const instanceSettings = ({
   url: 'proxied',
   directUrl: 'direct',
   user: 'test',
   password: 'mupp',
   jsonData: { httpMethod: 'GET' },
-};
+} as unknown) as DataSourceInstanceSettings<PromOptions>;
 const backendSrv = {
   datasourceRequest: jest.fn(),
 } as any;
 
-const templateSrv = {
+const templateSrv = ({
   getAdhocFilters: () => [],
   replace: jest.fn(str => str),
-};
+} as unknown) as TemplateSrv;
 
-const timeSrv = {
+const timeSrv = ({
   timeRange: () => {
     return { to: { diff: () => 2000 }, from: '' };
   },
-};
+} as unknown) as TimeSrv;
 
 describe('PrometheusDatasource', () => {
   describe('When querying prometheus with one target using query editor target spec', () => {
@@ -1177,13 +1181,13 @@ describe('PrometheusDatasource', () => {
 
 describe('PrometheusDatasource for POST', () => {
   //   const ctx = new helpers.ServiceTestContext();
-  const instanceSettings = {
+  const instanceSettings = ({
     url: 'proxied',
     directUrl: 'direct',
     user: 'test',
     password: 'mupp',
     jsonData: { httpMethod: 'POST' },
-  };
+  } as unknown) as DataSourceInstanceSettings<PromOptions>;
 
   describe('When querying prometheus with one target using query editor target spec', () => {
     let results;

+ 4 - 2
public/app/plugins/datasource/prometheus/specs/metric_find_query.test.ts

@@ -2,15 +2,17 @@ import { PrometheusDatasource } from '../datasource';
 import PrometheusMetricFindQuery from '../metric_find_query';
 import q from 'q';
 import { toUtc } from '@grafana/ui/src/utils/moment_wrapper';
+import { DataSourceInstanceSettings } from '@grafana/ui';
+import { PromOptions } from '../types';
 
 describe('PrometheusMetricFindQuery', () => {
-  const instanceSettings = {
+  const instanceSettings = ({
     url: 'proxied',
     directUrl: 'direct',
     user: 'test',
     password: 'mupp',
     jsonData: { httpMethod: 'GET' },
-  };
+  } as unknown) as DataSourceInstanceSettings<PromOptions>;
   const raw = {
     from: toUtc('2018-04-25 10:00'),
     to: toUtc('2018-04-25 11:00'),

+ 8 - 1
public/app/plugins/datasource/prometheus/types.ts

@@ -1,5 +1,12 @@
-import { DataQuery } from '@grafana/ui/src/types';
+import { DataQuery, DataSourceJsonData } from '@grafana/ui/src/types';
 
 export interface PromQuery extends DataQuery {
   expr: string;
 }
+
+export interface PromOptions extends DataSourceJsonData {
+  timeInterval: string;
+  queryTimeout: string;
+  httpMethod: string;
+  directUrl: string;
+}

+ 14 - 7
public/app/plugins/datasource/stackdriver/datasource.ts

@@ -2,11 +2,13 @@ import { stackdriverUnitMappings } from './constants';
 import appEvents from 'app/core/app_events';
 import _ from 'lodash';
 import StackdriverMetricFindQuery from './StackdriverMetricFindQuery';
-import { StackdriverQuery, MetricDescriptor } from './types';
-import { DataSourceApi, DataQueryRequest } from '@grafana/ui/src/types';
+import { StackdriverQuery, MetricDescriptor, StackdriverOptions } from './types';
+import { DataSourceApi, DataQueryRequest, DataSourceInstanceSettings, ScopedVars } from '@grafana/ui/src/types';
+import { BackendSrv } from 'app/core/services/backend_srv';
+import { TemplateSrv } from 'app/features/templating/template_srv';
+import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
 
-export default class StackdriverDatasource implements DataSourceApi<StackdriverQuery> {
-  id: number;
+export default class StackdriverDatasource extends DataSourceApi<StackdriverQuery, StackdriverOptions> {
   url: string;
   baseUrl: string;
   projectName: string;
@@ -15,10 +17,15 @@ export default class StackdriverDatasource implements DataSourceApi<StackdriverQ
   metricTypes: any[];
 
   /** @ngInject */
-  constructor(instanceSettings, private backendSrv, private templateSrv, private timeSrv) {
+  constructor(
+    instanceSettings: DataSourceInstanceSettings<StackdriverOptions>,
+    private backendSrv: BackendSrv,
+    private templateSrv: TemplateSrv,
+    private timeSrv: TimeSrv
+  ) {
+    super(instanceSettings);
     this.baseUrl = `/stackdriver/`;
     this.url = instanceSettings.url;
-    this.id = instanceSettings.id;
     this.projectName = instanceSettings.jsonData.defaultProject || '';
     this.authenticationType = instanceSettings.jsonData.authenticationType || 'jwt';
     this.metricTypes = [];
@@ -62,7 +69,7 @@ export default class StackdriverDatasource implements DataSourceApi<StackdriverQ
     }
   }
 
-  interpolateFilters(filters: string[], scopedVars: object) {
+  interpolateFilters(filters: string[], scopedVars: ScopedVars) {
     return (filters || []).map(f => {
       return this.templateSrv.replace(f, scopedVars || {}, 'regex');
     });

+ 24 - 18
public/app/plugins/datasource/stackdriver/specs/datasource.test.ts

@@ -3,26 +3,30 @@ import { metricDescriptors } from './testData';
 import { TemplateSrv } from 'app/features/templating/template_srv';
 import { CustomVariable } from 'app/features/templating/all';
 import { toUtc } from '@grafana/ui/src/utils/moment_wrapper';
+import { DataSourceInstanceSettings } from '@grafana/ui';
+import { StackdriverOptions } from '../types';
+import { BackendSrv } from 'app/core/services/backend_srv';
+import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
 
 describe('StackdriverDataSource', () => {
-  const instanceSettings = {
+  const instanceSettings = ({
     jsonData: {
       defaultProject: 'testproject',
     },
-  };
+  } as unknown) as DataSourceInstanceSettings<StackdriverOptions>;
   const templateSrv = new TemplateSrv();
-  const timeSrv = {};
+  const timeSrv = {} as TimeSrv;
 
   describe('when performing testDataSource', () => {
     describe('and call to stackdriver api succeeds', () => {
       let ds;
       let result;
       beforeEach(async () => {
-        const backendSrv = {
+        const backendSrv = ({
           async datasourceRequest() {
             return Promise.resolve({ status: 200 });
           },
-        };
+        } as unknown) as BackendSrv;
         ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv, timeSrv);
         result = await ds.testDatasource();
       });
@@ -35,9 +39,9 @@ describe('StackdriverDataSource', () => {
       let ds;
       let result;
       beforeEach(async () => {
-        const backendSrv = {
+        const backendSrv = ({
           datasourceRequest: async () => Promise.resolve({ status: 200, data: metricDescriptors }),
-        };
+        } as unknown) as BackendSrv;
         ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv, timeSrv);
         result = await ds.testDatasource();
       });
@@ -50,7 +54,7 @@ describe('StackdriverDataSource', () => {
       let ds;
       let result;
       beforeEach(async () => {
-        const backendSrv = {
+        const backendSrv = ({
           datasourceRequest: async () =>
             Promise.reject({
               statusText: 'Bad Request',
@@ -58,7 +62,7 @@ describe('StackdriverDataSource', () => {
                 error: { code: 400, message: 'Field interval.endTime had an invalid value' },
               },
             }),
-        };
+        } as unknown) as BackendSrv;
         ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv, timeSrv);
         result = await ds.testDatasource();
       });
@@ -103,9 +107,9 @@ describe('StackdriverDataSource', () => {
       };
 
       beforeEach(() => {
-        const backendSrv = {
+        const backendSrv = ({
           datasourceRequest: async () => Promise.resolve({ status: 200, data: response }),
-        };
+        } as unknown) as BackendSrv;
         ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv, timeSrv);
       });
 
@@ -122,7 +126,7 @@ describe('StackdriverDataSource', () => {
     let ds;
     let result;
     beforeEach(async () => {
-      const backendSrv = {
+      const backendSrv = ({
         async datasourceRequest() {
           return Promise.resolve({
             data: {
@@ -139,7 +143,7 @@ describe('StackdriverDataSource', () => {
             },
           });
         },
-      };
+      } as unknown) as BackendSrv;
       ds = new StackdriverDataSource(instanceSettings, backendSrv, templateSrv, timeSrv);
       result = await ds.getMetricTypes();
     });
@@ -155,12 +159,14 @@ describe('StackdriverDataSource', () => {
     });
   });
 
+  const noopBackendSrv = ({} as unknown) as BackendSrv;
+
   describe('when interpolating a template variable for the filter', () => {
     let interpolated;
     describe('and is single value variable', () => {
       beforeEach(() => {
         const filterTemplateSrv = initTemplateSrv('filtervalue1');
-        const ds = new StackdriverDataSource(instanceSettings, {}, filterTemplateSrv, timeSrv);
+        const ds = new StackdriverDataSource(instanceSettings, noopBackendSrv, filterTemplateSrv, timeSrv);
         interpolated = ds.interpolateFilters(['resource.label.zone', '=~', '${test}'], {});
       });
 
@@ -173,7 +179,7 @@ describe('StackdriverDataSource', () => {
     describe('and is multi value variable', () => {
       beforeEach(() => {
         const filterTemplateSrv = initTemplateSrv(['filtervalue1', 'filtervalue2'], true);
-        const ds = new StackdriverDataSource(instanceSettings, {}, filterTemplateSrv, timeSrv);
+        const ds = new StackdriverDataSource(instanceSettings, noopBackendSrv, filterTemplateSrv, timeSrv);
         interpolated = ds.interpolateFilters(['resource.label.zone', '=~', '[[test]]'], {});
       });
 
@@ -189,7 +195,7 @@ describe('StackdriverDataSource', () => {
     describe('and is single value variable', () => {
       beforeEach(() => {
         const groupByTemplateSrv = initTemplateSrv('groupby1');
-        const ds = new StackdriverDataSource(instanceSettings, {}, groupByTemplateSrv, timeSrv);
+        const ds = new StackdriverDataSource(instanceSettings, noopBackendSrv, groupByTemplateSrv, timeSrv);
         interpolated = ds.interpolateGroupBys(['[[test]]'], {});
       });
 
@@ -202,7 +208,7 @@ describe('StackdriverDataSource', () => {
     describe('and is multi value variable', () => {
       beforeEach(() => {
         const groupByTemplateSrv = initTemplateSrv(['groupby1', 'groupby2'], true);
-        const ds = new StackdriverDataSource(instanceSettings, {}, groupByTemplateSrv, timeSrv);
+        const ds = new StackdriverDataSource(instanceSettings, noopBackendSrv, groupByTemplateSrv, timeSrv);
         interpolated = ds.interpolateGroupBys(['[[test]]'], {});
       });
 
@@ -217,7 +223,7 @@ describe('StackdriverDataSource', () => {
   describe('unit parsing', () => {
     let ds, res;
     beforeEach(() => {
-      ds = new StackdriverDataSource(instanceSettings, {}, templateSrv, timeSrv);
+      ds = new StackdriverDataSource(instanceSettings, noopBackendSrv, templateSrv, timeSrv);
     });
     describe('when theres only one target', () => {
       describe('and the stackdriver unit doesnt have a corresponding grafana unit', () => {

+ 6 - 1
public/app/plugins/datasource/stackdriver/types.ts

@@ -1,4 +1,4 @@
-import { DataQuery } from '@grafana/ui/src/types';
+import { DataQuery, DataSourceJsonData } from '@grafana/ui/src/types';
 
 export enum MetricFindQueryTypes {
   Services = 'services',
@@ -40,6 +40,11 @@ export interface StackdriverQuery extends DataQuery {
   view?: string;
 }
 
+export interface StackdriverOptions extends DataSourceJsonData {
+  defaultProject?: string;
+  authenticationType?: string;
+}
+
 export interface AnnotationTarget {
   defaultProject: string;
   metricType: string;

+ 2 - 3
public/app/plugins/datasource/testdata/datasource.ts

@@ -17,13 +17,12 @@ export interface TestDataRegistry {
   [key: string]: TestData[];
 }
 
-export class TestDataDatasource implements DataSourceApi<TestDataQuery> {
-  id: number;
+export class TestDataDatasource extends DataSourceApi<TestDataQuery> {
   streams = new StreamHandler();
 
   /** @ngInject */
   constructor(instanceSettings: DataSourceInstanceSettings) {
-    this.id = instanceSettings.id;
+    super(instanceSettings);
   }
 
   query(options: DataQueryRequest<TestDataQuery>, observer: DataStreamObserver) {

+ 7 - 6
public/test/mocks/datasource_srv.ts

@@ -1,4 +1,4 @@
-import { DataSourceApi, DataQueryRequest, DataQueryResponse } from '@grafana/ui';
+import { DataSourceApi, DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings } from '@grafana/ui';
 
 export class DatasourceSrvMock {
   constructor(private defaultDS: DataSourceApi, private datasources: { [name: string]: DataSourceApi }) {
@@ -17,14 +17,15 @@ export class DatasourceSrvMock {
   }
 }
 
-export class MockDataSourceApi implements DataSourceApi {
-  name: string;
-
+export class MockDataSourceApi extends DataSourceApi {
   result: DataQueryResponse = { data: [] };
   queryResolver: Promise<DataQueryResponse>;
 
-  constructor(DataQueryResponse, name?: string) {
-    this.name = name ? name : 'MockDataSourceApi';
+  constructor(name?: string, result?: DataQueryResponse) {
+    super({ name: name ? name : 'MockDataSourceApi' } as DataSourceInstanceSettings);
+    if (result) {
+      this.result = result;
+    }
   }
 
   query(request: DataQueryRequest): Promise<DataQueryResponse> {