DataSourceApi: convert interface to abstract class (#16979)

* DataSourceApi as class

* add diff signature

* Prometheus: moved directUrl to jsonData
This commit is contained in:
Ryan McKinley 2019-05-10 02:37:43 -07:00 committed by Torkel Ödegaard
parent 5573d28582
commit 1d7bb2a763
21 changed files with 188 additions and 125 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,8 +9,16 @@ import { logStreamToSeriesData } from './result_transformer';
import { formatQuery, parseQuery } from './query_utils'; import { formatQuery, parseQuery } from './query_utils';
// Types // Types
import { PluginMeta, DataQueryRequest, SeriesData } from '@grafana/ui/src/types'; import {
import { LokiQuery } from './types'; 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; export const DEFAULT_MAX_LINES = 1000;
@ -30,12 +38,17 @@ function serializeParams(data: any) {
.join('&'); .join('&');
} }
export class LokiDatasource { export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
languageProvider: LanguageProvider; languageProvider: LanguageProvider;
maxLines: number; maxLines: number;
/** @ngInject */ /** @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); this.languageProvider = new LanguageProvider(this);
const settingsData = instanceSettings.jsonData || {}; const settingsData = instanceSettings.jsonData || {};
this.maxLines = parseInt(settingsData.maxLines, 10) || DEFAULT_MAX_LINES; this.maxLines = parseInt(settingsData.maxLines, 10) || DEFAULT_MAX_LINES;

View File

@ -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 { export interface LokiQuery extends DataQuery {
expr: string; expr: string;
} }
export interface LokiOptions extends DataSourceJsonData {
maxLines?: string;
}
export interface LokiLogsStream { export interface LokiLogsStream {
labels: string; labels: string;
entries: LokiLogsStreamEntry[]; entries: LokiLogsStreamEntry[];

View File

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

View File

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

View File

@ -1,6 +1,10 @@
import { PromCompleter } from '../completer'; import { PromCompleter } from '../completer';
import { PrometheusDatasource } from '../datasource'; import { PrometheusDatasource } from '../datasource';
import { BackendSrv } from 'app/core/services/backend_srv'; 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('../datasource');
jest.mock('app/core/services/backend_srv'); jest.mock('app/core/services/backend_srv');
@ -16,7 +20,13 @@ describe('Prometheus editor completer', () => {
const editor = {}; const editor = {};
const backendSrv = {} as BackendSrv; 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(() => datasourceStub.metadataRequest = jest.fn(() =>
Promise.resolve({ data: { data: [{ metric: { job: 'node', instance: 'localhost:9100' } }] } }) Promise.resolve({ data: { data: [{ metric: { job: 'node', instance: 'localhost:9100' } }] } })

View File

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

View File

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

View File

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

View File

@ -2,11 +2,13 @@ import { stackdriverUnitMappings } from './constants';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import _ from 'lodash'; import _ from 'lodash';
import StackdriverMetricFindQuery from './StackdriverMetricFindQuery'; import StackdriverMetricFindQuery from './StackdriverMetricFindQuery';
import { StackdriverQuery, MetricDescriptor } from './types'; import { StackdriverQuery, MetricDescriptor, StackdriverOptions } from './types';
import { DataSourceApi, DataQueryRequest } from '@grafana/ui/src/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> { export default class StackdriverDatasource extends DataSourceApi<StackdriverQuery, StackdriverOptions> {
id: number;
url: string; url: string;
baseUrl: string; baseUrl: string;
projectName: string; projectName: string;
@ -15,10 +17,15 @@ export default class StackdriverDatasource implements DataSourceApi<StackdriverQ
metricTypes: any[]; metricTypes: any[];
/** @ngInject */ /** @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.baseUrl = `/stackdriver/`;
this.url = instanceSettings.url; this.url = instanceSettings.url;
this.id = instanceSettings.id;
this.projectName = instanceSettings.jsonData.defaultProject || ''; this.projectName = instanceSettings.jsonData.defaultProject || '';
this.authenticationType = instanceSettings.jsonData.authenticationType || 'jwt'; this.authenticationType = instanceSettings.jsonData.authenticationType || 'jwt';
this.metricTypes = []; 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 (filters || []).map(f => {
return this.templateSrv.replace(f, scopedVars || {}, 'regex'); return this.templateSrv.replace(f, scopedVars || {}, 'regex');
}); });

View File

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

View File

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

View File

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

View File

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