|
|
@@ -6,12 +6,23 @@ import { PanelCtrl } from 'app/features/panel/panel_ctrl';
|
|
|
import { getExploreUrl } from 'app/core/utils/explore';
|
|
|
import { applyPanelTimeOverrides, getResolution } from 'app/features/dashboard/utils/panel';
|
|
|
import { ContextSrv } from 'app/core/services/context_srv';
|
|
|
-import { toLegacyResponseData, isSeriesData, LegacyResponseData, TimeRange } from '@grafana/ui';
|
|
|
+import {
|
|
|
+ toLegacyResponseData,
|
|
|
+ isSeriesData,
|
|
|
+ LegacyResponseData,
|
|
|
+ TimeRange,
|
|
|
+ DataSourceApi,
|
|
|
+ PanelData,
|
|
|
+ LoadingState,
|
|
|
+ DataQueryResponse,
|
|
|
+} from '@grafana/ui';
|
|
|
import { Unsubscribable } from 'rxjs';
|
|
|
+import { PanelModel } from 'app/features/dashboard/state';
|
|
|
+import { PanelQueryRunnerFormat } from '../dashboard/state/PanelQueryRunner';
|
|
|
|
|
|
class MetricsPanelCtrl extends PanelCtrl {
|
|
|
scope: any;
|
|
|
- datasource: any;
|
|
|
+ datasource: DataSourceApi;
|
|
|
$q: any;
|
|
|
$timeout: any;
|
|
|
contextSrv: ContextSrv;
|
|
|
@@ -24,11 +35,10 @@ class MetricsPanelCtrl extends PanelCtrl {
|
|
|
resolution: any;
|
|
|
timeInfo?: string;
|
|
|
skipDataOnInit: boolean;
|
|
|
- dataStream: any;
|
|
|
- dataSubscription?: Unsubscribable;
|
|
|
dataList: LegacyResponseData[];
|
|
|
+ querySubscription?: Unsubscribable;
|
|
|
|
|
|
- constructor($scope, $injector) {
|
|
|
+ constructor($scope: any, $injector: any) {
|
|
|
super($scope, $injector);
|
|
|
|
|
|
this.$q = $injector.get('$q');
|
|
|
@@ -44,9 +54,9 @@ class MetricsPanelCtrl extends PanelCtrl {
|
|
|
}
|
|
|
|
|
|
private onPanelTearDown() {
|
|
|
- if (this.dataSubscription) {
|
|
|
- this.dataSubscription.unsubscribe();
|
|
|
- this.dataSubscription = null;
|
|
|
+ if (this.querySubscription) {
|
|
|
+ this.querySubscription.unsubscribe();
|
|
|
+ this.querySubscription = null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -72,47 +82,76 @@ class MetricsPanelCtrl extends PanelCtrl {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- // // ignore if we have data stream
|
|
|
- if (this.dataStream) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
// clear loading/error state
|
|
|
delete this.error;
|
|
|
this.loading = true;
|
|
|
|
|
|
// load datasource service
|
|
|
- this.datasourceSrv
|
|
|
- .get(this.panel.datasource, this.panel.scopedVars)
|
|
|
- .then(this.updateTimeRange.bind(this))
|
|
|
- .then(this.issueQueries.bind(this))
|
|
|
- .then(this.handleQueryResult.bind(this))
|
|
|
- .catch(err => {
|
|
|
- // if canceled keep loading set to true
|
|
|
- if (err.cancelled) {
|
|
|
- console.log('Panel request cancelled', err);
|
|
|
- return;
|
|
|
- }
|
|
|
+ return (
|
|
|
+ this.datasourceSrv
|
|
|
+ .get(this.panel.datasource, this.panel.scopedVars)
|
|
|
+ .then(this.updateTimeRange.bind(this))
|
|
|
+ .then(this.issueQueries.bind(this))
|
|
|
+ // NOTE handleQueryResult is called by panelDataObserver
|
|
|
+ .catch((err: any) => {
|
|
|
+ this.processDataError(err);
|
|
|
+ })
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ processDataError(err: any) {
|
|
|
+ // if canceled keep loading set to true
|
|
|
+ if (err.cancelled) {
|
|
|
+ console.log('Panel request cancelled', err);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loading = false;
|
|
|
+ this.error = err.message || 'Request Error';
|
|
|
+ this.inspector = { error: err };
|
|
|
|
|
|
+ if (err.data) {
|
|
|
+ if (err.data.message) {
|
|
|
+ this.error = err.data.message;
|
|
|
+ }
|
|
|
+ if (err.data.error) {
|
|
|
+ this.error = err.data.error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.events.emit('data-error', err);
|
|
|
+ console.log('Panel data error:', err);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Updates the response with information from the stream
|
|
|
+ panelDataObserver = {
|
|
|
+ next: (data: PanelData) => {
|
|
|
+ if (data.state === LoadingState.Error) {
|
|
|
+ this.loading = false;
|
|
|
+ this.processDataError(data.error);
|
|
|
+ } else if (data.state === LoadingState.Done) {
|
|
|
this.loading = false;
|
|
|
- this.error = err.message || 'Request Error';
|
|
|
- this.inspector = { error: err };
|
|
|
-
|
|
|
- if (err.data) {
|
|
|
- if (err.data.message) {
|
|
|
- this.error = err.data.message;
|
|
|
- }
|
|
|
- if (err.data.error) {
|
|
|
- this.error = err.data.error;
|
|
|
- }
|
|
|
+
|
|
|
+ // The result should already be processed, but just in case
|
|
|
+ if (!data.legacy) {
|
|
|
+ data.legacy = data.series.map(v => {
|
|
|
+ if (isSeriesData(v)) {
|
|
|
+ return toLegacyResponseData(v);
|
|
|
+ }
|
|
|
+ return v;
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
- this.events.emit('data-error', err);
|
|
|
- console.log('Panel data error:', err);
|
|
|
- });
|
|
|
- }
|
|
|
+ // Make the results look like they came directly from a <6.2 datasource request
|
|
|
+ // NOTE: any object other than 'data' is no longer supported supported
|
|
|
+ this.handleQueryResult({
|
|
|
+ data: data.legacy,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ };
|
|
|
|
|
|
- updateTimeRange(datasource?) {
|
|
|
+ updateTimeRange(datasource?: DataSourceApi) {
|
|
|
this.datasource = datasource || this.datasource;
|
|
|
this.range = this.timeSrv.timeRange();
|
|
|
this.resolution = getResolution(this.panel);
|
|
|
@@ -141,46 +180,34 @@ class MetricsPanelCtrl extends PanelCtrl {
|
|
|
this.intervalMs = res.intervalMs;
|
|
|
}
|
|
|
|
|
|
- issueQueries(datasource) {
|
|
|
+ issueQueries(datasource: DataSourceApi) {
|
|
|
this.datasource = datasource;
|
|
|
|
|
|
- if (!this.panel.targets || this.panel.targets.length === 0) {
|
|
|
- return this.$q.when([]);
|
|
|
- }
|
|
|
+ const panel = this.panel as PanelModel;
|
|
|
+ const queryRunner = panel.getQueryRunner();
|
|
|
|
|
|
- // make shallow copy of scoped vars,
|
|
|
- // and add built in variables interval and interval_ms
|
|
|
- const scopedVars = Object.assign({}, this.panel.scopedVars, {
|
|
|
- __interval: { text: this.interval, value: this.interval },
|
|
|
- __interval_ms: { text: this.intervalMs, value: this.intervalMs },
|
|
|
- });
|
|
|
+ if (!this.querySubscription) {
|
|
|
+ this.querySubscription = queryRunner.subscribe(this.panelDataObserver, PanelQueryRunnerFormat.legacy);
|
|
|
+ }
|
|
|
|
|
|
- const metricsQuery = {
|
|
|
- timezone: this.dashboard.getTimezone(),
|
|
|
- panelId: this.panel.id,
|
|
|
+ return queryRunner.run({
|
|
|
+ datasource: panel.datasource,
|
|
|
+ queries: panel.targets,
|
|
|
+ panelId: panel.id,
|
|
|
dashboardId: this.dashboard.id,
|
|
|
- range: this.range,
|
|
|
- rangeRaw: this.range.raw,
|
|
|
- interval: this.interval,
|
|
|
- intervalMs: this.intervalMs,
|
|
|
- targets: this.panel.targets,
|
|
|
- maxDataPoints: this.resolution,
|
|
|
- scopedVars: scopedVars,
|
|
|
- cacheTimeout: this.panel.cacheTimeout,
|
|
|
- };
|
|
|
-
|
|
|
- return datasource.query(metricsQuery);
|
|
|
+ timezone: this.dashboard.timezone,
|
|
|
+ timeRange: this.range,
|
|
|
+ widthPixels: this.resolution, // The pixel width
|
|
|
+ maxDataPoints: panel.maxDataPoints,
|
|
|
+ minInterval: panel.interval,
|
|
|
+ scopedVars: panel.scopedVars,
|
|
|
+ cacheTimeout: panel.cacheTimeout,
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
- handleQueryResult(result) {
|
|
|
+ handleQueryResult(result: DataQueryResponse) {
|
|
|
this.loading = false;
|
|
|
|
|
|
- // check for if data source returns subject
|
|
|
- if (result && result.subscribe) {
|
|
|
- this.handleDataStream(result);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
if (this.dashboard.snapshot) {
|
|
|
this.panel.snapshotData = result.data;
|
|
|
}
|
|
|
@@ -190,41 +217,7 @@ class MetricsPanelCtrl extends PanelCtrl {
|
|
|
result = { data: [] };
|
|
|
}
|
|
|
|
|
|
- // Make sure the data is TableData | TimeSeries
|
|
|
- const data = result.data.map(v => {
|
|
|
- if (isSeriesData(v)) {
|
|
|
- return toLegacyResponseData(v);
|
|
|
- }
|
|
|
- return v;
|
|
|
- });
|
|
|
- this.events.emit('data-received', data);
|
|
|
- }
|
|
|
-
|
|
|
- handleDataStream(stream) {
|
|
|
- // if we already have a connection
|
|
|
- if (this.dataStream) {
|
|
|
- console.log('two stream observables!');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- this.dataStream = stream;
|
|
|
- this.dataSubscription = stream.subscribe({
|
|
|
- next: data => {
|
|
|
- console.log('dataSubject next!');
|
|
|
- if (data.range) {
|
|
|
- this.range = data.range;
|
|
|
- }
|
|
|
- this.events.emit('data-received', data.data);
|
|
|
- },
|
|
|
- error: error => {
|
|
|
- this.events.emit('data-error', error);
|
|
|
- console.log('panel: observer got error');
|
|
|
- },
|
|
|
- complete: () => {
|
|
|
- console.log('panel: observer got complete');
|
|
|
- this.dataStream = null;
|
|
|
- },
|
|
|
- });
|
|
|
+ this.events.emit('data-received', result.data);
|
|
|
}
|
|
|
|
|
|
getAdditionalMenuItems() {
|