The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/plugins/datasource_srv.ts

352 lines
11 KiB

import {
AppEvents,
DataSourceApi,
DataSourceInstanceSettings,
DataSourceRef,
DataSourceSelectItem,
ScopedVars,
} from '@grafana/data';
import {
GetDataSourceListFilters,
DataSourceSrv as DataSourceService,
getDataSourceSrv as getDataSourceService,
TemplateSrv,
getTemplateSrv,
getLegacyAngularInjector,
getBackendSrv,
} from '@grafana/runtime';
import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend';
import appEvents from 'app/core/app_events';
import config from 'app/core/config';
import {
dataSource as expressionDatasource,
ExpressionDatasourceUID,
instanceSettings as expressionInstanceSettings,
} from 'app/features/expressions/ExpressionDatasource';
import { isDataSource } from '../variables/guard';
import { importDataSourcePlugin } from './plugin_loader';
export class DatasourceSrv implements DataSourceService {
private datasources: Record<string, DataSourceApi> = {}; // UID
private settingsMapByName: Record<string, DataSourceInstanceSettings> = {};
private settingsMapByUid: Record<string, DataSourceInstanceSettings> = {};
private settingsMapById: Record<string, DataSourceInstanceSettings> = {};
private defaultName = ''; // actually UID
constructor(private templateSrv: TemplateSrv = getTemplateSrv()) {}
init(settingsMapByName: Record<string, DataSourceInstanceSettings>, defaultName: string) {
this.datasources = {};
this.settingsMapByUid = {};
this.settingsMapByName = settingsMapByName;
this.defaultName = defaultName;
for (const dsSettings of Object.values(settingsMapByName)) {
if (!dsSettings.uid) {
dsSettings.uid = dsSettings.name; // -- Grafana --, -- Mixed etc
}
this.settingsMapByUid[dsSettings.uid] = dsSettings;
this.settingsMapById[dsSettings.id] = dsSettings;
}
// Preload expressions
this.datasources[ExpressionDatasourceRef.type] = expressionDatasource as any;
this.datasources[ExpressionDatasourceUID] = expressionDatasource as any;
this.settingsMapByUid[ExpressionDatasourceRef.uid] = expressionInstanceSettings;
this.settingsMapByUid[ExpressionDatasourceUID] = expressionInstanceSettings;
}
getDataSourceSettingsByUid(uid: string): DataSourceInstanceSettings | undefined {
return this.settingsMapByUid[uid];
}
getInstanceSettings(
ref: string | null | undefined | DataSourceRef,
scopedVars?: ScopedVars
): DataSourceInstanceSettings | undefined {
const isstring = typeof ref === 'string';
let nameOrUid = isstring ? (ref as string) : ((ref as any)?.uid as string | undefined);
if (nameOrUid === 'default' || nameOrUid === null || nameOrUid === undefined) {
if (!isstring && ref) {
const type = (ref as any)?.type as string;
if (type === ExpressionDatasourceRef.type) {
return expressionDatasource.instanceSettings;
} else if (type) {
console.log('FIND Default instance for datasource type?', ref);
}
}
return this.settingsMapByUid[this.defaultName] ?? this.settingsMapByName[this.defaultName];
}
// Complex logic to support template variable data source names
// For this we just pick the current or first data source in the variable
if (nameOrUid[0] === '$') {
const interpolatedName = this.templateSrv.replace(nameOrUid, scopedVars, variableInterpolation);
let dsSettings;
if (interpolatedName === 'default') {
dsSettings = this.settingsMapByName[this.defaultName];
} else {
dsSettings = this.settingsMapByUid[interpolatedName] ?? this.settingsMapByName[interpolatedName];
}
if (!dsSettings) {
return undefined;
}
// Return an instance with un-interpolated values for name and uid
return {
...dsSettings,
isDefault: false,
name: nameOrUid,
uid: nameOrUid,
rawRef: { type: dsSettings.type, uid: dsSettings.uid },
};
}
return this.settingsMapByUid[nameOrUid] ?? this.settingsMapByName[nameOrUid];
}
get(ref?: string | DataSourceRef | null, scopedVars?: ScopedVars): Promise<DataSourceApi> {
let nameOrUid = typeof ref === 'string' ? (ref as string) : ((ref as any)?.uid as string | undefined);
if (!nameOrUid) {
return this.get(this.defaultName);
}
// Check if nameOrUid matches a uid and then get the name
const byName = this.settingsMapByName[nameOrUid];
if (byName) {
nameOrUid = byName.uid;
}
// 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
nameOrUid = this.templateSrv.replace(nameOrUid, scopedVars, variableInterpolation);
if (nameOrUid === 'default' && this.defaultName !== 'default') {
return this.get(this.defaultName);
}
if (this.datasources[nameOrUid]) {
return Promise.resolve(this.datasources[nameOrUid]);
}
return this.loadDatasource(nameOrUid);
}
async loadDatasource(key: string): Promise<DataSourceApi<any, any>> {
if (this.datasources[key]) {
return Promise.resolve(this.datasources[key]);
}
// find the metadata
const instanceSettings = this.settingsMapByUid[key] ?? this.settingsMapByName[key] ?? this.settingsMapById[key];
if (!instanceSettings) {
return Promise.reject({ message: `Datasource ${key} was not found` });
}
try {
const dsPlugin = await importDataSourcePlugin(instanceSettings.meta);
// check if its in cache now
if (this.datasources[key]) {
return this.datasources[key];
}
// If there is only one constructor argument it is instanceSettings
const useAngular = dsPlugin.DataSourceClass.length !== 1;
let instance: DataSourceApi<any, any>;
if (useAngular) {
instance = getLegacyAngularInjector().instantiate(dsPlugin.DataSourceClass, {
instanceSettings,
});
} else {
instance = new dsPlugin.DataSourceClass(instanceSettings);
}
instance.components = dsPlugin.components;
// Some old plugins does not extend DataSourceApi so we need to manually patch them
if (!(instance instanceof DataSourceApi)) {
const anyInstance = instance as any;
anyInstance.name = instanceSettings.name;
anyInstance.id = instanceSettings.id;
anyInstance.type = instanceSettings.type;
anyInstance.meta = instanceSettings.meta;
anyInstance.uid = instanceSettings.uid;
(instance as any).getRef = DataSourceApi.prototype.getRef;
}
// store in instance cache
this.datasources[key] = instance;
this.datasources[instance.uid] = instance;
return instance;
} catch (err) {
if (err instanceof Error) {
appEvents.emit(AppEvents.alertError, [instanceSettings.name + ' plugin failed', err.toString()]);
}
return Promise.reject({ message: `Datasource: ${key} was not found` });
}
}
getAll(): DataSourceInstanceSettings[] {
return Object.values(this.settingsMapByName);
}
getList(filters: GetDataSourceListFilters = {}): DataSourceInstanceSettings[] {
const base = Object.values(this.settingsMapByName).filter((x) => {
if (x.meta.id === 'grafana' || x.meta.id === 'mixed' || x.meta.id === 'dashboard') {
return false;
}
if (filters.metrics && !x.meta.metrics) {
return false;
}
if (filters.tracing && !x.meta.tracing) {
return false;
}
if (filters.logs && x.meta.category !== 'logging' && !x.meta.logs) {
return false;
}
if (filters.annotations && !x.meta.annotations) {
return false;
}
if (filters.alerting && !x.meta.alerting) {
return false;
}
if (filters.pluginId && x.meta.id !== filters.pluginId) {
return false;
}
if (filters.filter && !filters.filter(x)) {
return false;
}
if (filters.type && (Array.isArray(filters.type) ? !filters.type.includes(x.type) : filters.type !== x.type)) {
return false;
}
if (
!filters.all &&
x.meta.metrics !== true &&
x.meta.annotations !== true &&
x.meta.tracing !== true &&
x.meta.logs !== true &&
x.meta.alerting !== true
) {
return false;
}
return true;
});
if (filters.variables) {
for (const variable of this.templateSrv.getVariables()) {
if (!isDataSource(variable) || variable.multi || variable.includeAll) {
continue;
}
const dsName = variable.current.value === 'default' ? this.defaultName : variable.current.value;
const dsSettings = !Array.isArray(dsName) && this.settingsMapByName[dsName];
if (dsSettings) {
const key = `$\{${variable.name}\}`;
base.push({
...dsSettings,
name: key,
uid: key,
});
}
}
}
const sorted = base.sort((a, b) => {
if (a.name.toLowerCase() > b.name.toLowerCase()) {
return 1;
}
if (a.name.toLowerCase() < b.name.toLowerCase()) {
return -1;
}
return 0;
});
if (!filters.pluginId && !filters.alerting) {
if (filters.mixed) {
const mixedInstanceSettings = this.getInstanceSettings('-- Mixed --');
if (mixedInstanceSettings) {
base.push(mixedInstanceSettings);
}
}
if (filters.dashboard) {
const dashboardInstanceSettings = this.getInstanceSettings('-- Dashboard --');
if (dashboardInstanceSettings) {
base.push(dashboardInstanceSettings);
}
}
if (!filters.tracing) {
const grafanaInstanceSettings = this.getInstanceSettings('-- Grafana --');
if (grafanaInstanceSettings) {
base.push(grafanaInstanceSettings);
}
}
}
return sorted;
}
/**
* @deprecated use getList
* */
getExternal(): DataSourceInstanceSettings[] {
return this.getList();
}
/**
* @deprecated use getList
* */
getAnnotationSources() {
return this.getList({ annotations: true, variables: true }).map((x) => {
return {
name: x.name,
value: x.name,
meta: x.meta,
};
});
}
/**
* @deprecated use getList
* */
getMetricSources(options?: { skipVariables?: boolean }): DataSourceSelectItem[] {
return this.getList({ metrics: true, variables: !options?.skipVariables }).map((x) => {
return {
name: x.name,
value: x.name,
meta: x.meta,
};
});
}
async reload() {
const settings = await getBackendSrv().get('/api/frontend/settings');
config.datasources = settings.datasources;
config.defaultDatasource = settings.defaultDatasource;
this.init(settings.datasources, settings.defaultDatasource);
}
}
export function variableInterpolation(value: any[]) {
if (Array.isArray(value)) {
return value[0];
}
return value;
}
export const getDatasourceSrv = (): DatasourceSrv => {
return getDataSourceService() as DatasourceSrv;
};