1import { cloneDeep, upperFirst } from 'lodash'; 2import AzureMonitorDatasource from './azure_monitor/azure_monitor_datasource'; 3import AppInsightsDatasource from './app_insights/app_insights_datasource'; 4import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource'; 5import ResourcePickerData from './resourcePicker/resourcePickerData'; 6import { AzureDataSourceJsonData, AzureMonitorQuery, AzureQueryType, DatasourceValidationResult } from './types'; 7import { 8 DataFrame, 9 DataQueryRequest, 10 DataQueryResponse, 11 DataSourceApi, 12 DataSourceInstanceSettings, 13 LoadingState, 14 ScopedVars, 15} from '@grafana/data'; 16import { forkJoin, Observable, of } from 'rxjs'; 17import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv'; 18import InsightsAnalyticsDatasource from './insights_analytics/insights_analytics_datasource'; 19import { datasourceMigrations } from './utils/migrateQuery'; 20import { map } from 'rxjs/operators'; 21import AzureResourceGraphDatasource from './azure_resource_graph/azure_resource_graph_datasource'; 22import { getAzureCloud } from './credentials'; 23import migrateAnnotation from './utils/migrateAnnotation'; 24import { VariableSupport } from './variables'; 25export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDataSourceJsonData> { 26 annotations = { 27 prepareAnnotation: migrateAnnotation, 28 }; 29 30 azureMonitorDatasource: AzureMonitorDatasource; 31 azureLogAnalyticsDatasource: AzureLogAnalyticsDatasource; 32 resourcePickerData: ResourcePickerData; 33 azureResourceGraphDatasource: AzureResourceGraphDatasource; 34 /** @deprecated */ 35 appInsightsDatasource?: AppInsightsDatasource; 36 /** @deprecated */ 37 insightsAnalyticsDatasource?: InsightsAnalyticsDatasource; 38 39 pseudoDatasource: { 40 [key in AzureQueryType]?: 41 | AzureMonitorDatasource 42 | AzureLogAnalyticsDatasource 43 | AzureResourceGraphDatasource 44 | AppInsightsDatasource 45 | InsightsAnalyticsDatasource; 46 } = {}; 47 48 declare optionsKey: Record<AzureQueryType, string>; 49 50 constructor( 51 instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>, 52 private readonly templateSrv: TemplateSrv = getTemplateSrv() 53 ) { 54 super(instanceSettings); 55 this.azureMonitorDatasource = new AzureMonitorDatasource(instanceSettings); 56 this.azureLogAnalyticsDatasource = new AzureLogAnalyticsDatasource(instanceSettings); 57 this.azureResourceGraphDatasource = new AzureResourceGraphDatasource(instanceSettings); 58 this.resourcePickerData = new ResourcePickerData(instanceSettings); 59 60 this.pseudoDatasource = { 61 [AzureQueryType.AzureMonitor]: this.azureMonitorDatasource, 62 [AzureQueryType.LogAnalytics]: this.azureLogAnalyticsDatasource, 63 [AzureQueryType.AzureResourceGraph]: this.azureResourceGraphDatasource, 64 }; 65 66 const cloud = getAzureCloud(instanceSettings); 67 if (cloud === 'azuremonitor' || cloud === 'chinaazuremonitor') { 68 // AppInsights and InsightAnalytics are only supported for Public and Azure China clouds 69 this.appInsightsDatasource = new AppInsightsDatasource(instanceSettings); 70 this.insightsAnalyticsDatasource = new InsightsAnalyticsDatasource(instanceSettings); 71 this.pseudoDatasource[AzureQueryType.ApplicationInsights] = this.appInsightsDatasource; 72 this.pseudoDatasource[AzureQueryType.InsightsAnalytics] = this.insightsAnalyticsDatasource; 73 } 74 75 this.variables = new VariableSupport(this); 76 } 77 78 filterQuery(item: AzureMonitorQuery): boolean { 79 if (!item.queryType) { 80 return true; 81 } 82 const ds = this.pseudoDatasource[item.queryType]; 83 return ds?.filterQuery?.(item) ?? true; 84 } 85 86 query(options: DataQueryRequest<AzureMonitorQuery>): Observable<DataQueryResponse> { 87 const byType = new Map<AzureQueryType, DataQueryRequest<AzureMonitorQuery>>(); 88 89 for (const baseTarget of options.targets) { 90 // Migrate old query structures 91 const target = datasourceMigrations(baseTarget); 92 93 // Skip hidden or invalid queries or ones without properties 94 if (!target.queryType || target.hide || !hasQueryForType(target)) { 95 continue; 96 } 97 98 // Initialize the list of queries 99 if (!byType.has(target.queryType)) { 100 const queryForType = cloneDeep(options); 101 queryForType.requestId = `${queryForType.requestId}-${target.refId}`; 102 queryForType.targets = []; 103 byType.set(target.queryType, queryForType); 104 } 105 106 const queryForType = byType.get(target.queryType); 107 queryForType?.targets.push(target); 108 } 109 110 const observables: Array<Observable<DataQueryResponse>> = Array.from(byType.entries()).map(([queryType, req]) => { 111 const ds = this.pseudoDatasource[queryType]; 112 if (!ds) { 113 throw new Error('Data source not created for query type ' + queryType); 114 } 115 116 return ds.query(req); 117 }); 118 119 // Single query can skip merge 120 if (observables.length === 1) { 121 return observables[0]; 122 } 123 124 if (observables.length > 1) { 125 return forkJoin(observables).pipe( 126 map((results: DataQueryResponse[]) => { 127 const data: DataFrame[] = []; 128 for (const result of results) { 129 for (const frame of result.data) { 130 data.push(frame); 131 } 132 } 133 134 return { state: LoadingState.Done, data }; 135 }) 136 ); 137 } 138 139 return of({ state: LoadingState.Done, data: [] }); 140 } 141 142 targetContainsTemplate(query: AzureMonitorQuery) { 143 if (query.subscription && this.templateSrv.variableExists(query.subscription)) { 144 return true; 145 } 146 147 let subQuery; 148 if (query.queryType === AzureQueryType.AzureMonitor) { 149 subQuery = JSON.stringify(query.azureMonitor); 150 } else if (query.queryType === AzureQueryType.LogAnalytics) { 151 subQuery = JSON.stringify(query.azureLogAnalytics); 152 } else if (query.queryType === AzureQueryType.AzureResourceGraph) { 153 subQuery = JSON.stringify([query.azureResourceGraph, query.subscriptions]); 154 } 155 156 return !!subQuery && this.templateSrv.variableExists(subQuery); 157 } 158 159 async annotationQuery(options: any) { 160 return this.azureLogAnalyticsDatasource.annotationQuery(options); 161 } 162 163 async testDatasource(): Promise<DatasourceValidationResult> { 164 const promises: Array<Promise<DatasourceValidationResult>> = []; 165 166 promises.push(this.azureMonitorDatasource.testDatasource()); 167 promises.push(this.azureLogAnalyticsDatasource.testDatasource()); 168 169 if (this.appInsightsDatasource?.isConfigured()) { 170 promises.push(this.appInsightsDatasource.testDatasource()); 171 } 172 173 return await Promise.all(promises).then((results) => { 174 let status: 'success' | 'error' = 'success'; 175 let message = ''; 176 177 for (let i = 0; i < results.length; i++) { 178 if (results[i].status !== 'success') { 179 status = results[i].status; 180 } 181 message += `${i + 1}. ${results[i].message} `; 182 } 183 184 return { 185 status: status, 186 message: message, 187 title: upperFirst(status), 188 }; 189 }); 190 } 191 192 /* Azure Monitor REST API methods */ 193 getResourceGroups(subscriptionId: string) { 194 return this.azureMonitorDatasource.getResourceGroups(this.replaceTemplateVariable(subscriptionId)); 195 } 196 197 getMetricDefinitions(subscriptionId: string, resourceGroup: string) { 198 return this.azureMonitorDatasource.getMetricDefinitions( 199 this.replaceTemplateVariable(subscriptionId), 200 this.replaceTemplateVariable(resourceGroup) 201 ); 202 } 203 204 getResourceNames(subscriptionId: string, resourceGroup: string, metricDefinition: string) { 205 return this.azureMonitorDatasource.getResourceNames( 206 this.replaceTemplateVariable(subscriptionId), 207 this.replaceTemplateVariable(resourceGroup), 208 this.replaceTemplateVariable(metricDefinition) 209 ); 210 } 211 212 getMetricNames( 213 subscriptionId: string, 214 resourceGroup: string, 215 metricDefinition: string, 216 resourceName: string, 217 metricNamespace: string 218 ) { 219 return this.azureMonitorDatasource.getMetricNames( 220 this.replaceTemplateVariable(subscriptionId), 221 this.replaceTemplateVariable(resourceGroup), 222 this.replaceTemplateVariable(metricDefinition), 223 this.replaceTemplateVariable(resourceName), 224 this.replaceTemplateVariable(metricNamespace) 225 ); 226 } 227 228 getMetricNamespaces(subscriptionId: string, resourceGroup: string, metricDefinition: string, resourceName: string) { 229 return this.azureMonitorDatasource.getMetricNamespaces( 230 this.replaceTemplateVariable(subscriptionId), 231 this.replaceTemplateVariable(resourceGroup), 232 this.replaceTemplateVariable(metricDefinition), 233 this.replaceTemplateVariable(resourceName) 234 ); 235 } 236 237 getMetricMetadata( 238 subscriptionId: string, 239 resourceGroup: string, 240 metricDefinition: string, 241 resourceName: string, 242 metricNamespace: string, 243 metricName: string 244 ) { 245 return this.azureMonitorDatasource.getMetricMetadata( 246 this.replaceTemplateVariable(subscriptionId), 247 this.replaceTemplateVariable(resourceGroup), 248 this.replaceTemplateVariable(metricDefinition), 249 this.replaceTemplateVariable(resourceName), 250 this.replaceTemplateVariable(metricNamespace), 251 this.replaceTemplateVariable(metricName) 252 ); 253 } 254 255 /* Application Insights API method */ 256 getAppInsightsMetricNames() { 257 return this.appInsightsDatasource?.getMetricNames(); 258 } 259 260 getAppInsightsMetricMetadata(metricName: string) { 261 return this.appInsightsDatasource?.getMetricMetadata(metricName); 262 } 263 264 getAppInsightsColumns(refId: string | number) { 265 return this.appInsightsDatasource?.logAnalyticsColumns[refId]; 266 } 267 268 /*Azure Log Analytics */ 269 getAzureLogAnalyticsWorkspaces(subscriptionId: string) { 270 return this.azureLogAnalyticsDatasource.getWorkspaces(subscriptionId); 271 } 272 273 getSubscriptions() { 274 return this.azureMonitorDatasource.getSubscriptions(); 275 } 276 277 interpolateVariablesInQueries(queries: AzureMonitorQuery[], scopedVars: ScopedVars): AzureMonitorQuery[] { 278 const mapped = queries.map((query) => { 279 if (!query.queryType) { 280 return query; 281 } 282 283 const ds = this.pseudoDatasource[query.queryType]; 284 return ds?.applyTemplateVariables(query, scopedVars) ?? query; 285 }); 286 287 return mapped; 288 } 289 290 replaceTemplateVariable(variable: string) { 291 return this.templateSrv.replace(variable); 292 } 293 294 getVariables() { 295 return this.templateSrv.getVariables().map((v) => `$${v.name}`); 296 } 297 298 isTemplateVariable(value: string) { 299 return this.getVariables().includes(value); 300 } 301} 302 303function hasQueryForType(query: AzureMonitorQuery): boolean { 304 switch (query.queryType) { 305 case AzureQueryType.AzureMonitor: 306 return !!query.azureMonitor; 307 308 case AzureQueryType.LogAnalytics: 309 return !!query.azureLogAnalytics; 310 311 case AzureQueryType.AzureResourceGraph: 312 return !!query.azureResourceGraph; 313 314 case AzureQueryType.GrafanaTemplateVariableFn: 315 return !!query.grafanaTemplateVariableFn; 316 317 case AzureQueryType.ApplicationInsights: 318 return !!query.appInsights; 319 320 case AzureQueryType.InsightsAnalytics: 321 return !!query.insightsAnalytics; 322 323 default: 324 return false; 325 } 326} 327