query_ctrl.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import _ from 'lodash';
  2. import { QueryCtrl } from 'app/plugins/sdk';
  3. import appEvents from 'app/core/app_events';
  4. export interface QueryMeta {
  5. rawQuery: string;
  6. rawQueryString: string;
  7. metricLabels: any;
  8. }
  9. export class StackdriverQueryCtrl extends QueryCtrl {
  10. static templateUrl = 'partials/query.editor.html';
  11. target: {
  12. project: {
  13. id: string;
  14. name: string;
  15. };
  16. metricType: string;
  17. refId: string;
  18. aggregation: {
  19. crossSeriesReducer: string;
  20. alignmentPeriod: string;
  21. perSeriesAligner: string;
  22. groupBys: string[];
  23. };
  24. };
  25. defaultDropdownValue = 'Select metric';
  26. defaults = {
  27. project: {
  28. id: 'default',
  29. name: 'loading project...',
  30. },
  31. metricType: this.defaultDropdownValue,
  32. aggregation: {
  33. crossSeriesReducer: 'REDUCE_MEAN',
  34. alignmentPeriod: '',
  35. perSeriesAligner: '',
  36. groupBys: [],
  37. },
  38. };
  39. groupBySegments: any[];
  40. aggOptions = [
  41. { text: 'none', value: 'REDUCE_NONE' },
  42. { text: 'mean', value: 'REDUCE_MEAN' },
  43. { text: 'min', value: 'REDUCE_MIN' },
  44. { text: 'max', value: 'REDUCE_MAX' },
  45. { text: 'sum', value: 'REDUCE_SUM' },
  46. { text: 'std. dev.', value: 'REDUCE_STDDEV' },
  47. { text: 'count', value: 'REDUCE_COUNT' },
  48. { text: '99th percentile', value: 'REDUCE_PERCENTILE_99' },
  49. { text: '95th percentile', value: 'REDUCE_PERCENTILE_95' },
  50. { text: '50th percentile', value: 'REDUCE_PERCENTILE_50' },
  51. { text: '5th percentile', value: 'REDUCE_PERCENTILE_05' },
  52. ];
  53. showHelp: boolean;
  54. showLastQuery: boolean;
  55. lastQueryMeta: QueryMeta;
  56. lastQueryError?: string;
  57. /** @ngInject */
  58. constructor($scope, $injector, private uiSegmentSrv) {
  59. super($scope, $injector);
  60. _.defaultsDeep(this.target, this.defaults);
  61. this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope);
  62. this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope);
  63. this.getCurrentProject().then(this.getMetricTypes.bind(this));
  64. this.groupBySegments = _.map(this.target.aggregation.groupBys, groupBy => {
  65. return uiSegmentSrv.getSegmentForValue(groupBy);
  66. });
  67. this.ensurePlusButton(this.groupBySegments);
  68. }
  69. async getCurrentProject() {
  70. try {
  71. const projects = await this.datasource.getProjects();
  72. if (projects && projects.length > 0) {
  73. this.target.project = projects[0];
  74. } else {
  75. throw new Error('No projects found');
  76. }
  77. } catch (error) {
  78. let message = 'Projects cannot be fetched: ';
  79. message += error.statusText ? error.statusText + ': ' : '';
  80. if (error && error.data && error.data.error && error.data.error.message) {
  81. if (error.data.error.code === 403) {
  82. message += `
  83. A list of projects could not be fetched from the Google Cloud Resource Manager API.
  84. You might need to enable it first:
  85. https://console.developers.google.com/apis/library/cloudresourcemanager.googleapis.com`;
  86. } else {
  87. message += error.data.error.code + '. ' + error.data.error.message;
  88. }
  89. } else {
  90. message += 'Cannot connect to Stackdriver API';
  91. }
  92. appEvents.emit('ds-request-error', message);
  93. }
  94. }
  95. async getMetricTypes() {
  96. //projects/raintank-production/metricDescriptors/agent.googleapis.com/agent/api_request_count
  97. if (this.target.project.id !== 'default') {
  98. const metricTypes = await this.datasource.getMetricTypes(this.target.project.id);
  99. if (this.target.metricType === this.defaultDropdownValue && metricTypes.length > 0) {
  100. this.$scope.$apply(() => (this.target.metricType = metricTypes[0].name));
  101. }
  102. return metricTypes.map(mt => ({ value: mt.id, text: mt.id }));
  103. } else {
  104. return [];
  105. }
  106. }
  107. getGroupBys() {
  108. const segments = _.map(Object.keys(this.lastQueryMeta.metricLabels), (label: string) => {
  109. return this.uiSegmentSrv.newSegment({ value: label, expandable: false });
  110. });
  111. return Promise.resolve(segments);
  112. }
  113. groupByChanged(segment, index) {
  114. this.target.aggregation.groupBys = _.reduce(
  115. this.groupBySegments,
  116. function(memo, seg) {
  117. if (!seg.fake) {
  118. memo.push(seg.value);
  119. }
  120. return memo;
  121. },
  122. []
  123. );
  124. this.ensurePlusButton(this.groupBySegments);
  125. }
  126. ensurePlusButton(segments) {
  127. const count = segments.length;
  128. const lastSegment = segments[Math.max(count - 1, 0)];
  129. if (!lastSegment || lastSegment.type !== 'plus-button') {
  130. segments.push(this.uiSegmentSrv.newPlusButton());
  131. }
  132. }
  133. onDataReceived(dataList) {
  134. this.lastQueryError = null;
  135. this.lastQueryMeta = null;
  136. const anySeriesFromQuery: any = _.find(dataList, { refId: this.target.refId });
  137. if (anySeriesFromQuery) {
  138. this.lastQueryMeta = anySeriesFromQuery.meta;
  139. this.lastQueryMeta.rawQueryString = decodeURIComponent(this.lastQueryMeta.rawQuery);
  140. }
  141. }
  142. onDataError(err) {
  143. if (err.data && err.data.results) {
  144. const queryRes = err.data.results[this.target.refId];
  145. if (queryRes) {
  146. this.lastQueryMeta = queryRes.meta;
  147. this.lastQueryMeta.rawQueryString = decodeURIComponent(this.lastQueryMeta.rawQuery);
  148. let jsonBody;
  149. try {
  150. jsonBody = JSON.parse(queryRes.error);
  151. } catch {
  152. this.lastQueryError = queryRes.error;
  153. }
  154. this.lastQueryError = jsonBody.error.message;
  155. }
  156. }
  157. }
  158. }