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