DataSourceSrv: Look up data source by uid and name transparently (#29449)

* Datasources: Look up data source by uid or name transparently

* comment tweak

* Removed config

* fixed type issues

* Initialize datasource srv

* Added deprecation notice

* Renamed getSettingsFor to getInstanceSettings

* fixed ts issue
This commit is contained in:
Torkel Ödegaard
2020-12-01 15:17:33 +01:00
committed by GitHub
parent b7aa6fed1d
commit 38caa80acd
9 changed files with 177 additions and 112 deletions

View File

@@ -26,6 +26,7 @@ describe('getAlertingValidationMessage', () => {
getExternal(): DataSourceInstanceSettings[] {
return [];
},
getInstanceSettings: (() => {}) as any,
getAll(): DataSourceInstanceSettings[] {
return [];
},
@@ -66,6 +67,7 @@ describe('getAlertingValidationMessage', () => {
return Promise.resolve(alertingDatasource);
},
getDataSourceSettingsByUid(): any {},
getInstanceSettings: (() => {}) as any,
getExternal(): DataSourceInstanceSettings[] {
return [];
},
@@ -96,6 +98,7 @@ describe('getAlertingValidationMessage', () => {
const datasourceSrv: DataSourceSrv = {
get: getMock,
getDataSourceSettingsByUid(): any {},
getInstanceSettings: (() => {}) as any,
getExternal(): DataSourceInstanceSettings[] {
return [];
},
@@ -128,6 +131,7 @@ describe('getAlertingValidationMessage', () => {
const datasourceSrv: DataSourceSrv = {
get: getMock,
getDataSourceSettingsByUid(): any {},
getInstanceSettings: (() => {}) as any,
getExternal(): DataSourceInstanceSettings[] {
return [];
},
@@ -160,6 +164,7 @@ describe('getAlertingValidationMessage', () => {
const datasourceSrv: DataSourceSrv = {
get: getMock,
getDataSourceSettingsByUid(): any {},
getInstanceSettings: (() => {}) as any,
getExternal(): DataSourceInstanceSettings[] {
return [];
},

View File

@@ -214,7 +214,7 @@ function updateFrontendSettings() {
.then((settings: any) => {
config.datasources = settings.datasources;
config.defaultDatasource = settings.defaultDatasource;
getDatasourceSrv().init();
getDatasourceSrv().init(config.datasources, settings.defaultDatasource);
});
}

View File

@@ -82,7 +82,9 @@ export class GrafanaLiveDataSourceScope extends GrafanaLiveScope {
if (this.names) {
return Promise.resolve(this.names);
}
const names: Array<SelectableValue<string>> = [];
for (const [key, ds] of Object.entries(config.datasources)) {
if (ds.meta.live) {
try {
@@ -99,6 +101,7 @@ export class GrafanaLiveDataSourceScope extends GrafanaLiveScope {
}
}
}
return (this.names = names);
}
}

View File

@@ -2,7 +2,6 @@
import sortBy from 'lodash/sortBy';
import coreModule from 'app/core/core_module';
// Services & Utils
import config from 'app/core/config';
import { importDataSourcePlugin } from './plugin_loader';
import {
DataSourceSrv as DataSourceService,
@@ -18,47 +17,74 @@ import { expressionDatasource } from 'app/features/expressions/ExpressionDatasou
import { DataSourceVariableModel } from '../variables/types';
export class DatasourceSrv implements DataSourceService {
datasources: Record<string, DataSourceApi> = {};
private datasources: Record<string, DataSourceApi> = {};
private settingsMapByName: Record<string, DataSourceInstanceSettings> = {};
private settingsMapByUid: Record<string, DataSourceInstanceSettings> = {};
private defaultName = '';
/** @ngInject */
constructor(
private $injector: auto.IInjectorService,
private $rootScope: GrafanaRootScope,
private templateSrv: TemplateSrv
) {
this.init();
}
) {}
init() {
init(settingsMapByName: Record<string, DataSourceInstanceSettings>, defaultName: string) {
this.datasources = {};
this.settingsMapByUid = {};
this.settingsMapByName = settingsMapByName;
this.defaultName = defaultName;
for (const dsSettings of Object.values(settingsMapByName)) {
this.settingsMapByUid[dsSettings.uid] = dsSettings;
}
}
getDataSourceSettingsByUid(uid: string): DataSourceInstanceSettings | undefined {
return Object.values(config.datasources).find(ds => ds.uid === uid);
return this.settingsMapByUid[uid];
}
get(name?: string | null, scopedVars?: ScopedVars): Promise<DataSourceApi> {
if (!name) {
return this.get(config.defaultDatasource);
getInstanceSettings(nameOrUid: string | null | undefined): DataSourceInstanceSettings | undefined {
if (nameOrUid === 'default' || nameOrUid === null || nameOrUid === undefined) {
return this.settingsMapByName[this.defaultName];
}
return this.settingsMapByUid[nameOrUid] ?? this.settingsMapByName[nameOrUid];
}
get(nameOrUid?: string | null, scopedVars?: ScopedVars): Promise<DataSourceApi> {
if (!nameOrUid) {
return this.get(this.defaultName);
}
// Check if nameOrUid matches a uid and then get the name
const byUid = this.settingsMapByUid[nameOrUid];
if (byUid) {
nameOrUid = byUid.name;
}
// This check is duplicated below, this is here mainly as performance optimization to skip interpolation
if (this.datasources[nameOrUid]) {
return Promise.resolve(this.datasources[nameOrUid]);
}
// Interpolation here is to support template variable in data source selection
name = this.templateSrv.replace(name, scopedVars, (value: any[]) => {
nameOrUid = this.templateSrv.replace(nameOrUid, scopedVars, (value: any[]) => {
if (Array.isArray(value)) {
return value[0];
}
return value;
});
if (name === 'default') {
return this.get(config.defaultDatasource);
if (nameOrUid === 'default') {
return this.get(this.defaultName);
}
if (this.datasources[name]) {
return Promise.resolve(this.datasources[name]);
if (this.datasources[nameOrUid]) {
return Promise.resolve(this.datasources[nameOrUid]);
}
return this.loadDatasource(name);
return this.loadDatasource(nameOrUid);
}
async loadDatasource(name: string): Promise<DataSourceApi<any, any>> {
@@ -68,7 +94,7 @@ export class DatasourceSrv implements DataSourceService {
return Promise.resolve(expressionDatasource);
}
const dsConfig = config.datasources[name];
const dsConfig = this.settingsMapByName[name];
if (!dsConfig) {
return Promise.reject({ message: `Datasource named ${name} was not found` });
}
@@ -101,8 +127,7 @@ export class DatasourceSrv implements DataSourceService {
}
getAll(): DataSourceInstanceSettings[] {
const { datasources } = config;
return Object.keys(datasources).map(name => datasources[name]);
return Object.values(this.settingsMapByName);
}
getExternal(): DataSourceInstanceSettings[] {
@@ -115,7 +140,7 @@ export class DatasourceSrv implements DataSourceService {
this.addDataSourceVariables(sources);
Object.values(config.datasources).forEach(value => {
Object.values(this.settingsMapByName).forEach(value => {
if (value.meta?.annotations) {
sources.push(value);
}
@@ -127,7 +152,7 @@ export class DatasourceSrv implements DataSourceService {
getMetricSources(options?: { skipVariables?: boolean }) {
const metricSources: DataSourceSelectItem[] = [];
Object.entries(config.datasources).forEach(([key, value]) => {
Object.entries(this.settingsMapByName).forEach(([key, value]) => {
if (value.meta?.metrics) {
let metricSource: DataSourceSelectItem = { value: key, name: key, meta: value.meta, sort: key };
@@ -142,7 +167,7 @@ export class DatasourceSrv implements DataSourceService {
metricSources.push(metricSource);
if (key === config.defaultDatasource) {
if (key === this.defaultName) {
metricSource = { value: null, name: 'default', meta: value.meta, sort: key };
metricSources.push(metricSource);
}
@@ -172,9 +197,9 @@ export class DatasourceSrv implements DataSourceService {
.getVariables()
.filter(variable => variable.type === 'datasource')
.forEach((variable: DataSourceVariableModel) => {
const first = variable.current.value === 'default' ? config.defaultDatasource : variable.current.value;
const first = variable.current.value === 'default' ? this.defaultName : variable.current.value;
const index = (first as unknown) as string;
const ds = config.datasources[index];
const ds = this.settingsMapByName[index];
if (ds) {
const key = `$${variable.name}`;

View File

@@ -1,7 +1,6 @@
import config from 'app/core/config';
import 'app/features/plugins/datasource_srv';
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
import { DataSourcePluginMeta, PluginMeta } from '@grafana/data';
import { DataSourceInstanceSettings, DataSourcePlugin, DataSourcePluginMeta, PluginMeta } from '@grafana/data';
// Datasource variable $datasource with current value 'BBB'
const templateSrv: any = {
@@ -14,41 +13,72 @@ const templateSrv: any = {
},
},
],
replace: (v: string) => v,
};
class TestDataSource {
constructor(public instanceSettings: DataSourceInstanceSettings) {}
}
jest.mock('../plugin_loader', () => ({
importDataSourcePlugin: () => {
return Promise.resolve(new DataSourcePlugin(TestDataSource as any));
},
}));
describe('datasource_srv', () => {
const _datasourceSrv = new DatasourceSrv({} as any, {} as any, templateSrv);
const datasources = {
buildIn: {
id: 1,
uid: '1',
type: 'b',
name: 'buildIn',
meta: { builtIn: true } as DataSourcePluginMeta,
jsonData: {},
},
external1: {
id: 2,
uid: '2',
type: 'e',
name: 'external1',
meta: { builtIn: false } as DataSourcePluginMeta,
jsonData: {},
},
external2: {
id: 3,
uid: '3',
type: 'e2',
name: 'external2',
meta: {} as PluginMeta,
jsonData: {},
},
};
describe('when loading external datasources', () => {
beforeEach(() => {
config.datasources = {
buildInDs: {
id: 1,
uid: '1',
type: 'b',
name: 'buildIn',
meta: { builtIn: true } as DataSourcePluginMeta,
jsonData: {},
},
nonBuildIn: {
id: 2,
uid: '2',
type: 'e',
name: 'external1',
meta: { builtIn: false } as DataSourcePluginMeta,
jsonData: {},
},
nonExplore: {
id: 3,
uid: '3',
type: 'e2',
name: 'external2',
meta: {} as PluginMeta,
jsonData: {},
},
};
beforeEach(() => {
_datasourceSrv.init(datasources, 'external1');
});
describe('when getting data source class instance', () => {
it('should load plugin and create instance and set meta', async () => {
const ds = (await _datasourceSrv.get('external1')) as any;
expect(ds.meta).toBe(datasources.external1.meta);
expect(ds.instanceSettings).toBe(datasources.external1);
// validate that it caches instance
const ds2 = await _datasourceSrv.get('external1');
expect(ds).toBe(ds2);
});
it('should be able to load data source using uid as well', async () => {
const dsByUid = await _datasourceSrv.get('2');
const dsByName = await _datasourceSrv.get('external1');
expect(dsByUid.meta).toBe(datasources.external1.meta);
expect(dsByUid).toBe(dsByName);
});
});
describe('when getting external metric sources', () => {
it('should return list of explore sources', () => {
const externalSources = _datasourceSrv.getExternal();
expect(externalSources.length).toBe(2);
@@ -59,45 +89,48 @@ describe('datasource_srv', () => {
describe('when loading metric sources', () => {
let metricSources: any;
const unsortedDatasources = {
mmm: {
type: 'test-db',
meta: { metrics: { m: 1 } },
},
'--Grafana--': {
type: 'grafana',
meta: { builtIn: true, metrics: { m: 1 }, id: 'grafana' },
},
'--Mixed--': {
type: 'test-db',
meta: { builtIn: true, metrics: { m: 1 }, id: 'mixed' },
},
ZZZ: {
type: 'test-db',
meta: { metrics: { m: 1 } },
},
aaa: {
type: 'test-db',
meta: { metrics: { m: 1 } },
},
BBB: {
type: 'test-db',
meta: { metrics: { m: 1 } },
},
};
beforeEach(() => {
config.datasources = unsortedDatasources as any;
_datasourceSrv.init(
{
mmm: {
type: 'test-db',
meta: { metrics: true } as any,
},
'--Grafana--': {
type: 'grafana',
meta: { builtIn: true, metrics: true, id: 'grafana' },
},
'--Mixed--': {
type: 'test-db',
meta: { builtIn: true, metrics: true, id: 'mixed' },
},
ZZZ: {
type: 'test-db',
meta: { metrics: true },
},
aaa: {
type: 'test-db',
meta: { metrics: true },
},
BBB: {
type: 'test-db',
meta: { metrics: true },
},
} as any,
'BBB'
);
metricSources = _datasourceSrv.getMetricSources({});
config.defaultDatasource = 'BBB';
});
it('should return a list of sources sorted case insensitively with builtin sources last', () => {
expect(metricSources[1].name).toBe('aaa');
expect(metricSources[2].name).toBe('BBB');
expect(metricSources[3].name).toBe('mmm');
expect(metricSources[4].name).toBe('ZZZ');
expect(metricSources[5].name).toBe('--Grafana--');
expect(metricSources[6].name).toBe('--Mixed--');
expect(metricSources[3].name).toBe('default');
expect(metricSources[4].name).toBe('mmm');
expect(metricSources[5].name).toBe('ZZZ');
expect(metricSources[6].name).toBe('--Grafana--');
expect(metricSources[7].name).toBe('--Mixed--');
});
it('should set default data source', () => {

View File

@@ -62,6 +62,8 @@ export class GrafanaCtrl {
setDashboardSrv(dashboardSrv);
setLegacyAngularInjector($injector);
datasourceSrv.init(config.datasources, config.defaultDatasource);
locationUtil.initialize({
getConfig: () => config,
getTimeRangeForUrl: getTimeSrv().timeRangeForUrl,