metrics_panel_ctrl.ts 6.48 KB
Newer Older
1 2
import _ from 'lodash';
import { PanelCtrl } from 'app/features/panel/panel_ctrl';
3
import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
4
import { ContextSrv } from 'app/core/services/context_srv';
5 6
import {
  DataFrame,
7
  DataQueryResponse,
8
  DataSourceApi,
9 10
  LegacyResponseData,
  LoadingState,
11 12
  PanelData,
  PanelEvents,
13 14 15
  TimeRange,
  toDataFrameDTO,
  toLegacyResponseData,
16
} from '@grafana/data';
17
import { Unsubscribable } from 'rxjs';
18
import { PanelModel } from 'app/features/dashboard/state';
19
import { PanelQueryRunner } from '../query/state/PanelQueryRunner';
20

21
class MetricsPanelCtrl extends PanelCtrl {
22
  scope: any;
23
  datasource: DataSourceApi;
24
  $timeout: any;
25
  contextSrv: ContextSrv;
26
  datasourceSrv: any;
27
  timeSrv: any;
28
  templateSrv: any;
29
  range: TimeRange;
30
  interval: any;
31
  intervalMs: any;
32
  resolution: any;
33
  timeInfo?: string;
34
  skipDataOnInit: boolean;
35
  dataList: LegacyResponseData[];
36
  querySubscription?: Unsubscribable | null;
37
  useDataFrames = false;
38
  panelData?: PanelData;
39

40
  constructor($scope: any, $injector: any) {
41
    super($scope, $injector);
42

43
    this.contextSrv = $injector.get('contextSrv');
44 45 46
    this.datasourceSrv = $injector.get('datasourceSrv');
    this.timeSrv = $injector.get('timeSrv');
    this.templateSrv = $injector.get('templateSrv');
47
    this.scope = $scope;
48
    this.panel.datasource = this.panel.datasource || null;
49

50 51
    this.events.on(PanelEvents.refresh, this.onMetricsPanelRefresh.bind(this));
    this.events.on(PanelEvents.panelTeardown, this.onPanelTearDown.bind(this));
52 53 54 55
    this.events.on(PanelEvents.componentDidMount, this.onMetricsPanelMounted.bind(this));
  }

  private onMetricsPanelMounted() {
56 57 58 59
    const queryRunner = this.panel.getQueryRunner() as PanelQueryRunner;
    this.querySubscription = queryRunner
      .getData({ withTransforms: true, withFieldConfig: true })
      .subscribe(this.panelDataObserver);
60 61 62
  }

  private onPanelTearDown() {
63 64 65
    if (this.querySubscription) {
      this.querySubscription.unsubscribe();
      this.querySubscription = null;
66
    }
67 68
  }

69
  private onMetricsPanelRefresh() {
70
    // ignore fetching data if another panel is in fullscreen
71 72 73
    if (this.otherPanelInFullscreenMode()) {
      return;
    }
74

75
    // if we have snapshot data use that
76
    if (this.panel.snapshotData) {
77
      this.updateTimeRange();
78
      let data = this.panel.snapshotData;
79
      // backward compatibility
80
      if (!_.isArray(data)) {
81
        data = data.data;
82 83
      }

84 85 86 87 88 89
      this.panelData = {
        state: LoadingState.Done,
        series: data,
        timeRange: this.range,
      };

90 91 92
      // Defer panel rendering till the next digest cycle.
      // For some reason snapshot panels don't init at this time, so this helps to avoid rendering issues.
      return this.$timeout(() => {
93
        this.events.emit(PanelEvents.dataSnapshotLoad, data);
94
      });
95 96
    }

97
    // clear loading/error state
98 99 100
    delete this.error;
    this.loading = true;

101
    // load datasource service
102 103 104 105 106 107 108
    return this.datasourceSrv
      .get(this.panel.datasource, this.panel.scopedVars)
      .then(this.updateTimeRange.bind(this))
      .then(this.issueQueries.bind(this))
      .catch((err: any) => {
        this.processDataError(err);
      });
109 110 111 112 113 114 115 116 117 118
  }

  processDataError(err: any) {
    // if canceled  keep loading set to true
    if (err.cancelled) {
      console.log('Panel request cancelled', err);
      return;
    }

    this.error = err.message || 'Request Error';
119

120 121 122
    if (err.data) {
      if (err.data.message) {
        this.error = err.data.message;
123
      } else if (err.data.error) {
124 125 126
        this.error = err.data.error;
      }
    }
127 128 129 130 131

    this.angularDirtyCheck();
  }

  angularDirtyCheck() {
132
    if (!this.$scope.$root.$$phase) {
133 134
      this.$scope.$digest();
    }
135 136 137 138 139
  }

  // Updates the response with information from the stream
  panelDataObserver = {
    next: (data: PanelData) => {
140 141
      this.panelData = data;

142 143 144
      if (data.state === LoadingState.Error) {
        this.loading = false;
        this.processDataError(data.error);
145
      }
146

147 148 149
      // Ignore data in loading state
      if (data.state === LoadingState.Loading) {
        this.loading = true;
150
        this.angularDirtyCheck();
151 152
        return;
      }
153

154
      if (data.request) {
155
        const { timeInfo } = data.request;
156 157 158 159 160
        if (timeInfo) {
          this.timeInfo = timeInfo;
        }
      }

161 162 163 164
      if (data.timeRange) {
        this.range = data.timeRange;
      }

165
      if (this.useDataFrames) {
166
        this.handleDataFrames(data.series);
167 168 169 170
      } else {
        // Make the results look as if they came directly from a <6.2 datasource request
        const legacy = data.series.map(v => toLegacyResponseData(v));
        this.handleQueryResult({ data: legacy });
171
      }
172 173

      this.angularDirtyCheck();
174 175
    },
  };
176

177
  updateTimeRange(datasource?: DataSourceApi) {
178
    this.datasource = datasource || this.datasource;
179 180
    this.range = this.timeSrv.timeRange();

181 182 183 184
    const newTimeData = applyPanelTimeOverrides(this.panel, this.range);
    this.timeInfo = newTimeData.timeInfo;
    this.range = newTimeData.timeRange;

185
    return this.datasource;
186
  }
187

188
  issueQueries(datasource: DataSourceApi) {
189
    this.datasource = datasource;
190

191 192
    const panel = this.panel as PanelModel;
    const queryRunner = panel.getQueryRunner();
193

194 195 196 197
    return queryRunner.run({
      datasource: panel.datasource,
      queries: panel.targets,
      panelId: panel.id,
198
      dashboardId: this.dashboard.id,
199
      timezone: this.dashboard.getTimezone(),
200
      timeInfo: this.timeInfo,
201
      timeRange: this.range,
202
      maxDataPoints: panel.maxDataPoints || this.width,
203 204 205
      minInterval: panel.interval,
      scopedVars: panel.scopedVars,
      cacheTimeout: panel.cacheTimeout,
206
      transformations: panel.transformations,
207
    });
208
  }
209

210
  handleDataFrames(data: DataFrame[]) {
211 212
    this.loading = false;

213
    if (this.dashboard && this.dashboard.snapshot) {
214 215 216 217
      this.panel.snapshotData = data.map(frame => toDataFrameDTO(frame));
    }

    try {
218
      this.events.emit(PanelEvents.dataFramesReceived, data);
219 220
    } catch (err) {
      this.processDataError(err);
221 222 223
    }
  }

224
  handleQueryResult(result: DataQueryResponse) {
225
    this.loading = false;
226

227
    if (this.dashboard.snapshot) {
228
      this.panel.snapshotData = result.data;
229
    }
Torkel Ödegaard committed
230

231
    if (!result || !result.data) {
232
      console.log('Data source query result invalid, missing data field:', result);
233
      result = { data: [] };
234 235
    }

236
    try {
237
      this.events.emit(PanelEvents.dataReceived, result.data);
238 239 240
    } catch (err) {
      this.processDataError(err);
    }
241
  }
242 243
}

244
export { MetricsPanelCtrl };