mirror of
https://github.com/grafana/grafana.git
synced 2024-12-02 13:39:19 -06:00
d4bd024951
* Fix expressions instance settings loading * Introduce a new method to get name or uid * Update public/app/features/plugins/datasource_srv.ts Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * Move getNameOrUid method outside the class --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
366 lines
12 KiB
TypeScript
366 lines
12 KiB
TypeScript
import {
|
|
AppEvents,
|
|
DataSourceApi,
|
|
DataSourceInstanceSettings,
|
|
DataSourceRef,
|
|
DataSourceSelectItem,
|
|
ScopedVars,
|
|
} from '@grafana/data';
|
|
import {
|
|
DataSourceSrv as DataSourceService,
|
|
getBackendSrv,
|
|
GetDataSourceListFilters,
|
|
getDataSourceSrv as getDataSourceService,
|
|
getLegacyAngularInjector,
|
|
getTemplateSrv,
|
|
TemplateSrv,
|
|
} from '@grafana/runtime';
|
|
import { ExpressionDatasourceRef, isExpressionReference } from '@grafana/runtime/src/utils/DataSourceWithBackend';
|
|
import appEvents from 'app/core/app_events';
|
|
import config from 'app/core/config';
|
|
import {
|
|
dataSource as expressionDatasource,
|
|
instanceSettings as expressionInstanceSettings,
|
|
} from 'app/features/expressions/ExpressionDatasource';
|
|
import { ExpressionDatasourceUID } from 'app/features/expressions/types';
|
|
|
|
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 {
|
|
let nameOrUid = getNameOrUid(ref);
|
|
|
|
// Expressions has a new UID as __expr__ See: https://github.com/grafana/grafana/pull/62510/
|
|
// But we still have dashboards/panels with old expression UID (-100)
|
|
// To support both UIDs until we migrate them all to new one, this check is necessary
|
|
if (isExpressionReference(nameOrUid)) {
|
|
return expressionInstanceSettings;
|
|
}
|
|
|
|
if (nameOrUid === 'default' || nameOrUid == null) {
|
|
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] ?? this.settingsMapById[nameOrUid];
|
|
}
|
|
|
|
get(ref?: string | DataSourceRef | null, scopedVars?: ScopedVars): Promise<DataSourceApi> {
|
|
let nameOrUid = getNameOrUid(ref);
|
|
if (!nameOrUid) {
|
|
return this.get(this.defaultName);
|
|
}
|
|
|
|
if (isExpressionReference(ref)) {
|
|
return Promise.resolve(this.datasources[ExpressionDatasourceUID]);
|
|
}
|
|
|
|
// 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.getInstanceSettings(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 (variable.type !== 'datasource') {
|
|
continue;
|
|
}
|
|
let dsValue = variable.current.value === 'default' ? this.defaultName : variable.current.value;
|
|
if (Array.isArray(dsValue) && dsValue.length === 1) {
|
|
// Support for multi-value variables with only one selected datasource
|
|
dsValue = dsValue[0];
|
|
}
|
|
const dsSettings = !Array.isArray(dsValue) && this.settingsMapByName[dsValue];
|
|
|
|
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 getNameOrUid(ref?: string | DataSourceRef | null): string | undefined {
|
|
if (isExpressionReference(ref)) {
|
|
return ExpressionDatasourceRef.uid;
|
|
}
|
|
|
|
const isString = typeof ref === 'string';
|
|
return isString ? (ref as string) : ((ref as any)?.uid as string | undefined);
|
|
}
|
|
|
|
export function variableInterpolation(value: any[]) {
|
|
if (Array.isArray(value)) {
|
|
return value[0];
|
|
}
|
|
return value;
|
|
}
|
|
|
|
export const getDatasourceSrv = (): DatasourceSrv => {
|
|
return getDataSourceService() as DatasourceSrv;
|
|
};
|