metrics_panel_ctrl.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import { isArray } from 'lodash';
  2. import { Unsubscribable } from 'rxjs';
  3. import {
  4. DataFrame,
  5. DataQueryResponse,
  6. DataSourceApi,
  7. LegacyResponseData,
  8. LoadingState,
  9. PanelData,
  10. PanelEvents,
  11. TimeRange,
  12. toDataFrameDTO,
  13. toLegacyResponseData,
  14. } from '@grafana/data';
  15. import { PanelCtrl } from 'app/angular/panel/panel_ctrl';
  16. import { ContextSrv } from 'app/core/services/context_srv';
  17. import { PanelModel } from 'app/features/dashboard/state';
  18. import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
  19. import { PanelQueryRunner } from '../../features/query/state/PanelQueryRunner';
  20. class MetricsPanelCtrl extends PanelCtrl {
  21. declare datasource: DataSourceApi;
  22. declare range: TimeRange;
  23. contextSrv: ContextSrv;
  24. datasourceSrv: any;
  25. timeSrv: any;
  26. templateSrv: any;
  27. interval: any;
  28. intervalMs: any;
  29. resolution: any;
  30. timeInfo?: string;
  31. skipDataOnInit = false;
  32. dataList: LegacyResponseData[] = [];
  33. querySubscription?: Unsubscribable | null;
  34. useDataFrames = false;
  35. panelData?: PanelData;
  36. constructor($scope: any, $injector: any) {
  37. super($scope, $injector);
  38. this.contextSrv = $injector.get('contextSrv');
  39. this.datasourceSrv = $injector.get('datasourceSrv');
  40. this.timeSrv = $injector.get('timeSrv');
  41. this.templateSrv = $injector.get('templateSrv');
  42. this.panel.datasource = this.panel.datasource || null;
  43. this.events.on(PanelEvents.refresh, this.onMetricsPanelRefresh.bind(this));
  44. this.events.on(PanelEvents.panelTeardown, this.onPanelTearDown.bind(this));
  45. this.events.on(PanelEvents.componentDidMount, this.onMetricsPanelMounted.bind(this));
  46. }
  47. private onMetricsPanelMounted() {
  48. const queryRunner = this.panel.getQueryRunner() as PanelQueryRunner;
  49. this.querySubscription = queryRunner
  50. .getData({ withTransforms: true, withFieldConfig: true })
  51. .subscribe(this.panelDataObserver);
  52. }
  53. private onPanelTearDown() {
  54. if (this.querySubscription) {
  55. this.querySubscription.unsubscribe();
  56. this.querySubscription = null;
  57. }
  58. }
  59. private onMetricsPanelRefresh() {
  60. // ignore fetching data if another panel is in fullscreen
  61. if (this.otherPanelInFullscreenMode()) {
  62. return;
  63. }
  64. // if we have snapshot data use that
  65. if (this.panel.snapshotData) {
  66. this.updateTimeRange();
  67. let data = this.panel.snapshotData;
  68. // backward compatibility
  69. if (!isArray(data)) {
  70. data = data.data;
  71. }
  72. this.panelData = {
  73. state: LoadingState.Done,
  74. series: data,
  75. timeRange: this.range,
  76. };
  77. // Defer panel rendering till the next digest cycle.
  78. // For some reason snapshot panels don't init at this time, so this helps to avoid rendering issues.
  79. return this.$timeout(() => {
  80. this.events.emit(PanelEvents.dataSnapshotLoad, data);
  81. });
  82. }
  83. // clear loading/error state
  84. delete this.error;
  85. this.loading = true;
  86. // load datasource service
  87. return this.datasourceSrv
  88. .get(this.panel.datasource, this.panel.scopedVars)
  89. .then(this.issueQueries.bind(this))
  90. .catch((err: any) => {
  91. this.processDataError(err);
  92. });
  93. }
  94. processDataError(err: any) {
  95. // if canceled keep loading set to true
  96. if (err.cancelled) {
  97. console.log('Panel request cancelled', err);
  98. return;
  99. }
  100. this.error = err.message || 'Request Error';
  101. if (err.data) {
  102. if (err.data.message) {
  103. this.error = err.data.message;
  104. } else if (err.data.error) {
  105. this.error = err.data.error;
  106. }
  107. }
  108. this.angularDirtyCheck();
  109. }
  110. angularDirtyCheck() {
  111. if (!this.$scope.$root.$$phase) {
  112. this.$scope.$digest();
  113. }
  114. }
  115. // Updates the response with information from the stream
  116. panelDataObserver = {
  117. next: (data: PanelData) => {
  118. this.panelData = data;
  119. if (data.state === LoadingState.Error) {
  120. this.loading = false;
  121. this.processDataError(data.error);
  122. }
  123. // Ignore data in loading state
  124. if (data.state === LoadingState.Loading) {
  125. this.loading = true;
  126. this.angularDirtyCheck();
  127. return;
  128. }
  129. if (data.request) {
  130. const { timeInfo } = data.request;
  131. if (timeInfo) {
  132. this.timeInfo = timeInfo;
  133. }
  134. }
  135. if (data.timeRange) {
  136. this.range = data.timeRange;
  137. }
  138. if (this.useDataFrames) {
  139. this.handleDataFrames(data.series);
  140. } else {
  141. // Make the results look as if they came directly from a <6.2 datasource request
  142. const legacy = data.series.map((v) => toLegacyResponseData(v));
  143. this.handleQueryResult({ data: legacy });
  144. }
  145. this.angularDirtyCheck();
  146. },
  147. };
  148. updateTimeRange(datasource?: DataSourceApi) {
  149. this.datasource = datasource || this.datasource;
  150. this.range = this.timeSrv.timeRange();
  151. const newTimeData = applyPanelTimeOverrides(this.panel, this.range);
  152. this.timeInfo = newTimeData.timeInfo;
  153. this.range = newTimeData.timeRange;
  154. }
  155. issueQueries(datasource: DataSourceApi) {
  156. this.updateTimeRange(datasource);
  157. this.datasource = datasource;
  158. const panel = this.panel as PanelModel;
  159. const queryRunner = panel.getQueryRunner();
  160. return queryRunner.run({
  161. datasource: panel.datasource,
  162. queries: panel.targets,
  163. panelId: panel.id,
  164. dashboardId: this.dashboard.id,
  165. timezone: this.dashboard.getTimezone(),
  166. timeInfo: this.timeInfo,
  167. timeRange: this.range,
  168. maxDataPoints: panel.maxDataPoints || this.width,
  169. minInterval: panel.interval,
  170. scopedVars: panel.scopedVars,
  171. cacheTimeout: panel.cacheTimeout,
  172. transformations: panel.transformations,
  173. });
  174. }
  175. handleDataFrames(data: DataFrame[]) {
  176. this.loading = false;
  177. if (this.dashboard && this.dashboard.snapshot) {
  178. this.panel.snapshotData = data.map((frame) => toDataFrameDTO(frame));
  179. }
  180. try {
  181. this.events.emit(PanelEvents.dataFramesReceived, data);
  182. } catch (err) {
  183. this.processDataError(err);
  184. }
  185. }
  186. handleQueryResult(result: DataQueryResponse) {
  187. this.loading = false;
  188. if (this.dashboard.snapshot) {
  189. this.panel.snapshotData = result.data;
  190. }
  191. if (!result || !result.data) {
  192. console.log('Data source query result invalid, missing data field:', result);
  193. result = { data: [] };
  194. }
  195. try {
  196. this.events.emit(PanelEvents.dataReceived, result.data);
  197. } catch (err) {
  198. this.processDataError(err);
  199. }
  200. }
  201. }
  202. export { MetricsPanelCtrl };