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