datasource.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import { chunk, flatten, isString, isArray } from 'lodash';
  2. import { from, lastValueFrom, Observable, of } from 'rxjs';
  3. import { map, mergeMap } from 'rxjs/operators';
  4. import {
  5. DataQueryRequest,
  6. DataQueryResponse,
  7. DataSourceInstanceSettings,
  8. ScopedVars,
  9. SelectableValue,
  10. } from '@grafana/data';
  11. import { DataSourceWithBackend, getBackendSrv, toDataQueryResponse } from '@grafana/runtime';
  12. import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
  13. import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
  14. import { CloudMonitoringAnnotationSupport } from './annotationSupport';
  15. import {
  16. CloudMonitoringOptions,
  17. CloudMonitoringQuery,
  18. EditorMode,
  19. Filter,
  20. MetricDescriptor,
  21. QueryType,
  22. PostResponse,
  23. Aggregation,
  24. } from './types';
  25. import { CloudMonitoringVariableSupport } from './variables';
  26. export default class CloudMonitoringDatasource extends DataSourceWithBackend<
  27. CloudMonitoringQuery,
  28. CloudMonitoringOptions
  29. > {
  30. authenticationType: string;
  31. intervalMs: number;
  32. constructor(
  33. private instanceSettings: DataSourceInstanceSettings<CloudMonitoringOptions>,
  34. public templateSrv: TemplateSrv = getTemplateSrv(),
  35. private readonly timeSrv: TimeSrv = getTimeSrv()
  36. ) {
  37. super(instanceSettings);
  38. this.authenticationType = instanceSettings.jsonData.authenticationType || 'jwt';
  39. this.variables = new CloudMonitoringVariableSupport(this);
  40. this.intervalMs = 0;
  41. this.annotations = CloudMonitoringAnnotationSupport(this);
  42. }
  43. getVariables() {
  44. return this.templateSrv.getVariables().map((v) => `$${v.name}`);
  45. }
  46. query(request: DataQueryRequest<CloudMonitoringQuery>): Observable<DataQueryResponse> {
  47. request.targets = request.targets.map((t) => ({
  48. ...this.migrateQuery(t),
  49. intervalMs: request.intervalMs,
  50. }));
  51. return super.query(request);
  52. }
  53. applyTemplateVariables(
  54. { metricQuery, refId, queryType, sloQuery, type = 'timeSeriesQuery' }: CloudMonitoringQuery,
  55. scopedVars: ScopedVars
  56. ): Record<string, any> {
  57. return {
  58. datasource: this.getRef(),
  59. refId,
  60. intervalMs: this.intervalMs,
  61. type,
  62. queryType,
  63. metricQuery: {
  64. ...this.interpolateProps(metricQuery, scopedVars),
  65. projectName: this.templateSrv.replace(
  66. metricQuery.projectName ? metricQuery.projectName : this.getDefaultProject(),
  67. scopedVars
  68. ),
  69. filters: this.interpolateFilters(metricQuery.filters || [], scopedVars),
  70. groupBys: this.interpolateGroupBys(metricQuery.groupBys || [], scopedVars),
  71. view: metricQuery.view || 'FULL',
  72. editorMode: metricQuery.editorMode,
  73. },
  74. sloQuery: sloQuery && this.interpolateProps(sloQuery, scopedVars),
  75. };
  76. }
  77. async getLabels(metricType: string, refId: string, projectName: string, aggregation?: Aggregation) {
  78. const options = {
  79. targets: [
  80. {
  81. refId,
  82. datasource: this.getRef(),
  83. queryType: QueryType.METRICS,
  84. metricQuery: {
  85. projectName: this.templateSrv.replace(projectName),
  86. metricType: this.templateSrv.replace(metricType),
  87. groupBys: this.interpolateGroupBys(aggregation?.groupBys || [], {}),
  88. crossSeriesReducer: aggregation?.crossSeriesReducer ?? 'REDUCE_NONE',
  89. view: 'HEADERS',
  90. },
  91. },
  92. ],
  93. range: this.timeSrv.timeRange(),
  94. } as DataQueryRequest<CloudMonitoringQuery>;
  95. const queries = options.targets;
  96. if (!queries.length) {
  97. return lastValueFrom(of({ results: [] }));
  98. }
  99. return lastValueFrom(
  100. from(this.ensureGCEDefaultProject()).pipe(
  101. mergeMap(() => {
  102. return getBackendSrv().fetch<PostResponse>({
  103. url: '/api/ds/query',
  104. method: 'POST',
  105. data: {
  106. from: options.range.from.valueOf().toString(),
  107. to: options.range.to.valueOf().toString(),
  108. queries,
  109. },
  110. });
  111. }),
  112. map(({ data }) => {
  113. const dataQueryResponse = toDataQueryResponse({
  114. data: data,
  115. });
  116. const labels = dataQueryResponse?.data
  117. .map((f) => f.meta?.custom?.labels)
  118. .filter((p) => !!p)
  119. .reduce((acc, labels) => {
  120. for (let key in labels) {
  121. if (!acc[key]) {
  122. acc[key] = new Set<string>();
  123. }
  124. if (labels[key]) {
  125. acc[key].add(labels[key]);
  126. }
  127. }
  128. return acc;
  129. }, {});
  130. return Object.fromEntries(
  131. Object.entries(labels).map((l: any) => {
  132. l[1] = Array.from(l[1]);
  133. return l;
  134. })
  135. );
  136. })
  137. )
  138. );
  139. }
  140. async getGCEDefaultProject() {
  141. return this.getResource(`gceDefaultProject`);
  142. }
  143. getDefaultProject(): string {
  144. const { defaultProject, authenticationType, gceDefaultProject } = this.instanceSettings.jsonData;
  145. if (authenticationType === 'gce') {
  146. return gceDefaultProject || '';
  147. }
  148. return defaultProject || '';
  149. }
  150. async ensureGCEDefaultProject() {
  151. const { authenticationType, gceDefaultProject } = this.instanceSettings.jsonData;
  152. if (authenticationType === 'gce' && !gceDefaultProject) {
  153. this.instanceSettings.jsonData.gceDefaultProject = await this.getGCEDefaultProject();
  154. }
  155. }
  156. async getMetricTypes(projectName: string): Promise<MetricDescriptor[]> {
  157. if (!projectName) {
  158. return [];
  159. }
  160. return this.getResource(
  161. `metricDescriptors/v3/projects/${this.templateSrv.replace(projectName)}/metricDescriptors`
  162. ) as Promise<MetricDescriptor[]>;
  163. }
  164. async getSLOServices(projectName: string): Promise<Array<SelectableValue<string>>> {
  165. return this.getResource(`services/v3/projects/${this.templateSrv.replace(projectName)}/services?pageSize=1000`);
  166. }
  167. async getServiceLevelObjectives(projectName: string, serviceId: string): Promise<Array<SelectableValue<string>>> {
  168. if (!serviceId) {
  169. return Promise.resolve([]);
  170. }
  171. let { projectName: p, serviceId: s } = this.interpolateProps({ projectName, serviceId });
  172. return this.getResource(`slo-services/v3/projects/${p}/services/${s}/serviceLevelObjectives`);
  173. }
  174. getProjects(): Promise<Array<SelectableValue<string>>> {
  175. return this.getResource(`projects`);
  176. }
  177. migrateQuery(query: CloudMonitoringQuery): CloudMonitoringQuery {
  178. if (!query.hasOwnProperty('metricQuery')) {
  179. const { hide, refId, datasource, key, queryType, maxLines, metric, intervalMs, type, ...rest } = query as any;
  180. return {
  181. refId,
  182. intervalMs,
  183. type,
  184. hide,
  185. queryType: QueryType.METRICS,
  186. metricQuery: {
  187. ...rest,
  188. view: rest.view || 'FULL',
  189. },
  190. };
  191. }
  192. return query;
  193. }
  194. interpolateProps<T extends Record<string, any>>(object: T, scopedVars: ScopedVars = {}): T {
  195. return Object.entries(object).reduce((acc, [key, value]) => {
  196. return {
  197. ...acc,
  198. [key]: value && isString(value) ? this.templateSrv.replace(value, scopedVars) : value,
  199. };
  200. }, {} as T);
  201. }
  202. filterQuery(query: CloudMonitoringQuery): boolean {
  203. if (query.hide) {
  204. return false;
  205. }
  206. if (query.queryType && query.queryType === QueryType.SLO && query.sloQuery) {
  207. const { selectorName, serviceId, sloId, projectName } = query.sloQuery;
  208. return !!selectorName && !!serviceId && !!sloId && !!projectName;
  209. }
  210. if (query.queryType && query.queryType === QueryType.METRICS && query.metricQuery.editorMode === EditorMode.MQL) {
  211. return !!query.metricQuery.projectName && !!query.metricQuery.query;
  212. }
  213. const { metricType } = query.metricQuery;
  214. return !!metricType;
  215. }
  216. interpolateVariablesInQueries(queries: CloudMonitoringQuery[], scopedVars: ScopedVars): CloudMonitoringQuery[] {
  217. return queries.map(
  218. (query) => this.applyTemplateVariables(this.migrateQuery(query), scopedVars) as CloudMonitoringQuery
  219. );
  220. }
  221. interpolateFilters(filters: string[], scopedVars: ScopedVars) {
  222. const completeFilter: Filter[] = chunk(filters, 4)
  223. .map(([key, operator, value, condition]) => ({
  224. key,
  225. operator,
  226. value,
  227. ...(condition && { condition }),
  228. }))
  229. .filter((item) => item.value);
  230. const filterArray = flatten(
  231. completeFilter.map(({ key, operator, value, condition }: Filter) => [
  232. this.templateSrv.replace(key, scopedVars || {}),
  233. operator,
  234. this.templateSrv.replace(value, scopedVars || {}, (value: string | string[]) => {
  235. return isArray(value) && value.length ? `(${value.join('|')})` : value;
  236. }),
  237. ...(condition ? [condition] : []),
  238. ])
  239. );
  240. return filterArray || [];
  241. }
  242. interpolateGroupBys(groupBys: string[], scopedVars: {}): string[] {
  243. let interpolatedGroupBys: string[] = [];
  244. (groupBys || []).forEach((gb) => {
  245. const interpolated = this.templateSrv.replace(gb, scopedVars || {}, 'csv').split(',');
  246. if (Array.isArray(interpolated)) {
  247. interpolatedGroupBys = interpolatedGroupBys.concat(interpolated);
  248. } else {
  249. interpolatedGroupBys.push(interpolated);
  250. }
  251. });
  252. return interpolatedGroupBys;
  253. }
  254. }