datasource_srv.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import {
  2. AppEvents,
  3. DataSourceApi,
  4. DataSourceInstanceSettings,
  5. DataSourceRef,
  6. DataSourceSelectItem,
  7. ScopedVars,
  8. } from '@grafana/data';
  9. import {
  10. GetDataSourceListFilters,
  11. DataSourceSrv as DataSourceService,
  12. getDataSourceSrv as getDataSourceService,
  13. TemplateSrv,
  14. getTemplateSrv,
  15. getLegacyAngularInjector,
  16. getBackendSrv,
  17. } from '@grafana/runtime';
  18. import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend';
  19. import appEvents from 'app/core/app_events';
  20. import config from 'app/core/config';
  21. import {
  22. dataSource as expressionDatasource,
  23. ExpressionDatasourceUID,
  24. instanceSettings as expressionInstanceSettings,
  25. } from 'app/features/expressions/ExpressionDatasource';
  26. import { DataSourceVariableModel } from '../variables/types';
  27. import { importDataSourcePlugin } from './plugin_loader';
  28. export class DatasourceSrv implements DataSourceService {
  29. private datasources: Record<string, DataSourceApi> = {}; // UID
  30. private settingsMapByName: Record<string, DataSourceInstanceSettings> = {};
  31. private settingsMapByUid: Record<string, DataSourceInstanceSettings> = {};
  32. private settingsMapById: Record<string, DataSourceInstanceSettings> = {};
  33. private defaultName = ''; // actually UID
  34. constructor(private templateSrv: TemplateSrv = getTemplateSrv()) {}
  35. init(settingsMapByName: Record<string, DataSourceInstanceSettings>, defaultName: string) {
  36. this.datasources = {};
  37. this.settingsMapByUid = {};
  38. this.settingsMapByName = settingsMapByName;
  39. this.defaultName = defaultName;
  40. for (const dsSettings of Object.values(settingsMapByName)) {
  41. if (!dsSettings.uid) {
  42. dsSettings.uid = dsSettings.name; // -- Grafana --, -- Mixed etc
  43. }
  44. this.settingsMapByUid[dsSettings.uid] = dsSettings;
  45. this.settingsMapById[dsSettings.id] = dsSettings;
  46. }
  47. // Preload expressions
  48. this.datasources[ExpressionDatasourceRef.type] = expressionDatasource as any;
  49. this.datasources[ExpressionDatasourceUID] = expressionDatasource as any;
  50. this.settingsMapByUid[ExpressionDatasourceRef.uid] = expressionInstanceSettings;
  51. this.settingsMapByUid[ExpressionDatasourceUID] = expressionInstanceSettings;
  52. }
  53. getDataSourceSettingsByUid(uid: string): DataSourceInstanceSettings | undefined {
  54. return this.settingsMapByUid[uid];
  55. }
  56. getInstanceSettings(
  57. ref: string | null | undefined | DataSourceRef,
  58. scopedVars?: ScopedVars
  59. ): DataSourceInstanceSettings | undefined {
  60. const isstring = typeof ref === 'string';
  61. let nameOrUid = isstring ? (ref as string) : ((ref as any)?.uid as string | undefined);
  62. if (nameOrUid === 'default' || nameOrUid === null || nameOrUid === undefined) {
  63. if (!isstring && ref) {
  64. const type = (ref as any)?.type as string;
  65. if (type === ExpressionDatasourceRef.type) {
  66. return expressionDatasource.instanceSettings;
  67. } else if (type) {
  68. console.log('FIND Default instance for datasource type?', ref);
  69. }
  70. }
  71. return this.settingsMapByUid[this.defaultName] ?? this.settingsMapByName[this.defaultName];
  72. }
  73. // Complex logic to support template variable data source names
  74. // For this we just pick the current or first data source in the variable
  75. if (nameOrUid[0] === '$') {
  76. const interpolatedName = this.templateSrv.replace(nameOrUid, scopedVars, variableInterpolation);
  77. let dsSettings;
  78. if (interpolatedName === 'default') {
  79. dsSettings = this.settingsMapByName[this.defaultName];
  80. } else {
  81. dsSettings = this.settingsMapByUid[interpolatedName] ?? this.settingsMapByName[interpolatedName];
  82. }
  83. if (!dsSettings) {
  84. return undefined;
  85. }
  86. // Return an instance with un-interpolated values for name and uid
  87. return {
  88. ...dsSettings,
  89. isDefault: false,
  90. name: nameOrUid,
  91. uid: nameOrUid,
  92. rawRef: { type: dsSettings.type, uid: dsSettings.uid },
  93. };
  94. }
  95. return this.settingsMapByUid[nameOrUid] ?? this.settingsMapByName[nameOrUid];
  96. }
  97. get(ref?: string | DataSourceRef | null, scopedVars?: ScopedVars): Promise<DataSourceApi> {
  98. let nameOrUid = typeof ref === 'string' ? (ref as string) : ((ref as any)?.uid as string | undefined);
  99. if (!nameOrUid) {
  100. return this.get(this.defaultName);
  101. }
  102. // Check if nameOrUid matches a uid and then get the name
  103. const byName = this.settingsMapByName[nameOrUid];
  104. if (byName) {
  105. nameOrUid = byName.uid;
  106. }
  107. // This check is duplicated below, this is here mainly as performance optimization to skip interpolation
  108. if (this.datasources[nameOrUid]) {
  109. return Promise.resolve(this.datasources[nameOrUid]);
  110. }
  111. // Interpolation here is to support template variable in data source selection
  112. nameOrUid = this.templateSrv.replace(nameOrUid, scopedVars, variableInterpolation);
  113. if (nameOrUid === 'default' && this.defaultName !== 'default') {
  114. return this.get(this.defaultName);
  115. }
  116. if (this.datasources[nameOrUid]) {
  117. return Promise.resolve(this.datasources[nameOrUid]);
  118. }
  119. return this.loadDatasource(nameOrUid);
  120. }
  121. async loadDatasource(key: string): Promise<DataSourceApi<any, any>> {
  122. if (this.datasources[key]) {
  123. return Promise.resolve(this.datasources[key]);
  124. }
  125. // find the metadata
  126. const instanceSettings = this.settingsMapByUid[key] ?? this.settingsMapByName[key] ?? this.settingsMapById[key];
  127. if (!instanceSettings) {
  128. return Promise.reject({ message: `Datasource ${key} was not found` });
  129. }
  130. try {
  131. const dsPlugin = await importDataSourcePlugin(instanceSettings.meta);
  132. // check if its in cache now
  133. if (this.datasources[key]) {
  134. return this.datasources[key];
  135. }
  136. // If there is only one constructor argument it is instanceSettings
  137. const useAngular = dsPlugin.DataSourceClass.length !== 1;
  138. let instance: DataSourceApi<any, any>;
  139. if (useAngular) {
  140. instance = getLegacyAngularInjector().instantiate(dsPlugin.DataSourceClass, {
  141. instanceSettings,
  142. });
  143. } else {
  144. instance = new dsPlugin.DataSourceClass(instanceSettings);
  145. }
  146. instance.components = dsPlugin.components;
  147. // Some old plugins does not extend DataSourceApi so we need to manually patch them
  148. if (!(instance instanceof DataSourceApi)) {
  149. const anyInstance = instance as any;
  150. anyInstance.name = instanceSettings.name;
  151. anyInstance.id = instanceSettings.id;
  152. anyInstance.type = instanceSettings.type;
  153. anyInstance.meta = instanceSettings.meta;
  154. anyInstance.uid = instanceSettings.uid;
  155. (instance as any).getRef = DataSourceApi.prototype.getRef;
  156. }
  157. // store in instance cache
  158. this.datasources[key] = instance;
  159. this.datasources[instance.uid] = instance;
  160. return instance;
  161. } catch (err) {
  162. appEvents.emit(AppEvents.alertError, [instanceSettings.name + ' plugin failed', err.toString()]);
  163. return Promise.reject({ message: `Datasource: ${key} was not found` });
  164. }
  165. }
  166. getAll(): DataSourceInstanceSettings[] {
  167. return Object.values(this.settingsMapByName);
  168. }
  169. getList(filters: GetDataSourceListFilters = {}): DataSourceInstanceSettings[] {
  170. const base = Object.values(this.settingsMapByName).filter((x) => {
  171. if (x.meta.id === 'grafana' || x.meta.id === 'mixed' || x.meta.id === 'dashboard') {
  172. return false;
  173. }
  174. if (filters.metrics && !x.meta.metrics) {
  175. return false;
  176. }
  177. if (filters.tracing && !x.meta.tracing) {
  178. return false;
  179. }
  180. if (filters.logs && x.meta.category !== 'logging' && !x.meta.logs) {
  181. return false;
  182. }
  183. if (filters.annotations && !x.meta.annotations) {
  184. return false;
  185. }
  186. if (filters.alerting && !x.meta.alerting) {
  187. return false;
  188. }
  189. if (filters.pluginId && x.meta.id !== filters.pluginId) {
  190. return false;
  191. }
  192. if (filters.filter && !filters.filter(x)) {
  193. return false;
  194. }
  195. if (filters.type && (Array.isArray(filters.type) ? !filters.type.includes(x.type) : filters.type !== x.type)) {
  196. return false;
  197. }
  198. if (
  199. !filters.all &&
  200. x.meta.metrics !== true &&
  201. x.meta.annotations !== true &&
  202. x.meta.tracing !== true &&
  203. x.meta.logs !== true &&
  204. x.meta.alerting !== true
  205. ) {
  206. return false;
  207. }
  208. return true;
  209. });
  210. if (filters.variables) {
  211. for (const variable of this.templateSrv.getVariables().filter((variable) => variable.type === 'datasource')) {
  212. const dsVar = variable as DataSourceVariableModel;
  213. const first = dsVar.current.value === 'default' ? this.defaultName : dsVar.current.value;
  214. const dsName = first as unknown as string;
  215. const dsSettings = this.settingsMapByName[dsName];
  216. if (dsSettings) {
  217. const key = `$\{${variable.name}\}`;
  218. base.push({
  219. ...dsSettings,
  220. name: key,
  221. uid: key,
  222. });
  223. }
  224. }
  225. }
  226. const sorted = base.sort((a, b) => {
  227. if (a.name.toLowerCase() > b.name.toLowerCase()) {
  228. return 1;
  229. }
  230. if (a.name.toLowerCase() < b.name.toLowerCase()) {
  231. return -1;
  232. }
  233. return 0;
  234. });
  235. if (!filters.pluginId && !filters.alerting) {
  236. if (filters.mixed) {
  237. const mixedInstanceSettings = this.getInstanceSettings('-- Mixed --');
  238. if (mixedInstanceSettings) {
  239. base.push(mixedInstanceSettings);
  240. }
  241. }
  242. if (filters.dashboard) {
  243. const dashboardInstanceSettings = this.getInstanceSettings('-- Dashboard --');
  244. if (dashboardInstanceSettings) {
  245. base.push(dashboardInstanceSettings);
  246. }
  247. }
  248. if (!filters.tracing) {
  249. const grafanaInstanceSettings = this.getInstanceSettings('-- Grafana --');
  250. if (grafanaInstanceSettings) {
  251. base.push(grafanaInstanceSettings);
  252. }
  253. }
  254. }
  255. return sorted;
  256. }
  257. /**
  258. * @deprecated use getList
  259. * */
  260. getExternal(): DataSourceInstanceSettings[] {
  261. return this.getList();
  262. }
  263. /**
  264. * @deprecated use getList
  265. * */
  266. getAnnotationSources() {
  267. return this.getList({ annotations: true, variables: true }).map((x) => {
  268. return {
  269. name: x.name,
  270. value: x.name,
  271. meta: x.meta,
  272. };
  273. });
  274. }
  275. /**
  276. * @deprecated use getList
  277. * */
  278. getMetricSources(options?: { skipVariables?: boolean }): DataSourceSelectItem[] {
  279. return this.getList({ metrics: true, variables: !options?.skipVariables }).map((x) => {
  280. return {
  281. name: x.name,
  282. value: x.name,
  283. meta: x.meta,
  284. };
  285. });
  286. }
  287. async reload() {
  288. const settings = await getBackendSrv().get('/api/frontend/settings');
  289. config.datasources = settings.datasources;
  290. config.defaultDatasource = settings.defaultDatasource;
  291. this.init(settings.datasources, settings.defaultDatasource);
  292. }
  293. }
  294. export function variableInterpolation(value: any[]) {
  295. if (Array.isArray(value)) {
  296. return value[0];
  297. }
  298. return value;
  299. }
  300. export const getDatasourceSrv = (): DatasourceSrv => {
  301. return getDataSourceService() as DatasourceSrv;
  302. };