|
@@ -4,7 +4,6 @@ import { Subject, Unsubscribable, PartialObserver } from 'rxjs';
|
|
|
|
|
|
|
|
// Services & Utils
|
|
// Services & Utils
|
|
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
|
|
-import { getBackendSrv } from 'app/core/services/backend_srv';
|
|
|
|
|
import kbn from 'app/core/utils/kbn';
|
|
import kbn from 'app/core/utils/kbn';
|
|
|
import templateSrv from 'app/features/templating/template_srv';
|
|
import templateSrv from 'app/features/templating/template_srv';
|
|
|
|
|
|
|
@@ -19,11 +18,9 @@ import {
|
|
|
ScopedVars,
|
|
ScopedVars,
|
|
|
DataQueryRequest,
|
|
DataQueryRequest,
|
|
|
SeriesData,
|
|
SeriesData,
|
|
|
- DataQueryError,
|
|
|
|
|
- toLegacyResponseData,
|
|
|
|
|
- isSeriesData,
|
|
|
|
|
DataSourceApi,
|
|
DataSourceApi,
|
|
|
} from '@grafana/ui';
|
|
} from '@grafana/ui';
|
|
|
|
|
+import { PanelQueryState } from './PanelQueryState';
|
|
|
|
|
|
|
|
export interface QueryRunnerOptions<TQuery extends DataQuery = DataQuery> {
|
|
export interface QueryRunnerOptions<TQuery extends DataQuery = DataQuery> {
|
|
|
datasource: string | DataSourceApi<TQuery>;
|
|
datasource: string | DataSourceApi<TQuery>;
|
|
@@ -55,13 +52,7 @@ function getNextRequestId() {
|
|
|
export class PanelQueryRunner {
|
|
export class PanelQueryRunner {
|
|
|
private subject?: Subject<PanelData>;
|
|
private subject?: Subject<PanelData>;
|
|
|
|
|
|
|
|
- private sendSeries = false;
|
|
|
|
|
- private sendLegacy = false;
|
|
|
|
|
-
|
|
|
|
|
- private data = {
|
|
|
|
|
- state: LoadingState.NotStarted,
|
|
|
|
|
- series: [],
|
|
|
|
|
- } as PanelData;
|
|
|
|
|
|
|
+ private state = new PanelQueryState();
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Listen for updates to the PanelData. If a query has already run for this panel,
|
|
* Listen for updates to the PanelData. If a query has already run for this panel,
|
|
@@ -73,18 +64,17 @@ export class PanelQueryRunner {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (format === PanelQueryRunnerFormat.legacy) {
|
|
if (format === PanelQueryRunnerFormat.legacy) {
|
|
|
- this.sendLegacy = true;
|
|
|
|
|
|
|
+ this.state.sendLegacy = true;
|
|
|
} else if (format === PanelQueryRunnerFormat.both) {
|
|
} else if (format === PanelQueryRunnerFormat.both) {
|
|
|
- this.sendSeries = true;
|
|
|
|
|
- this.sendLegacy = true;
|
|
|
|
|
|
|
+ this.state.sendSeries = true;
|
|
|
|
|
+ this.state.sendLegacy = true;
|
|
|
} else {
|
|
} else {
|
|
|
- this.sendSeries = true;
|
|
|
|
|
|
|
+ this.state.sendSeries = true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Send the last result
|
|
// Send the last result
|
|
|
- if (this.data.state !== LoadingState.NotStarted) {
|
|
|
|
|
- // TODO: make sure it has legacy if necessary
|
|
|
|
|
- observer.next(this.data);
|
|
|
|
|
|
|
+ if (this.state.data.state !== LoadingState.NotStarted) {
|
|
|
|
|
+ observer.next(this.state.getDataAfterCheckingFormats());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return this.subject.subscribe(observer);
|
|
return this.subject.subscribe(observer);
|
|
@@ -95,6 +85,8 @@ export class PanelQueryRunner {
|
|
|
this.subject = new Subject();
|
|
this.subject = new Subject();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ const { state } = this;
|
|
|
|
|
+
|
|
|
const {
|
|
const {
|
|
|
queries,
|
|
queries,
|
|
|
timezone,
|
|
timezone,
|
|
@@ -120,7 +112,11 @@ export class PanelQueryRunner {
|
|
|
timeInfo,
|
|
timeInfo,
|
|
|
interval: '',
|
|
interval: '',
|
|
|
intervalMs: 0,
|
|
intervalMs: 0,
|
|
|
- targets: cloneDeep(queries),
|
|
|
|
|
|
|
+ targets: cloneDeep(
|
|
|
|
|
+ queries.filter(q => {
|
|
|
|
|
+ return !q.hide; // Skip any hidden queries
|
|
|
|
|
+ })
|
|
|
|
|
+ ),
|
|
|
maxDataPoints: maxDataPoints || widthPixels,
|
|
maxDataPoints: maxDataPoints || widthPixels,
|
|
|
scopedVars: scopedVars || {},
|
|
scopedVars: scopedVars || {},
|
|
|
cacheTimeout,
|
|
cacheTimeout,
|
|
@@ -129,15 +125,6 @@ export class PanelQueryRunner {
|
|
|
// Deprecated
|
|
// Deprecated
|
|
|
(request as any).rangeRaw = timeRange.raw;
|
|
(request as any).rangeRaw = timeRange.raw;
|
|
|
|
|
|
|
|
- if (!queries) {
|
|
|
|
|
- return this.publishUpdate({
|
|
|
|
|
- state: LoadingState.Done,
|
|
|
|
|
- series: [], // Clear the data
|
|
|
|
|
- legacy: [],
|
|
|
|
|
- request,
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
let loadingStateTimeoutId = 0;
|
|
let loadingStateTimeoutId = 0;
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
@@ -159,77 +146,40 @@ export class PanelQueryRunner {
|
|
|
request.interval = norm.interval;
|
|
request.interval = norm.interval;
|
|
|
request.intervalMs = norm.intervalMs;
|
|
request.intervalMs = norm.intervalMs;
|
|
|
|
|
|
|
|
|
|
+ // Check if we can reuse the already issued query
|
|
|
|
|
+ if (state.isRunning()) {
|
|
|
|
|
+ if (state.isSameQuery(ds, request)) {
|
|
|
|
|
+ // TODO? maybe cancel if it has run too long?
|
|
|
|
|
+ return state.getCurrentExecutor();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ state.cancel('Query Changed while running');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Send a loading status event on slower queries
|
|
// Send a loading status event on slower queries
|
|
|
loadingStateTimeoutId = window.setTimeout(() => {
|
|
loadingStateTimeoutId = window.setTimeout(() => {
|
|
|
- this.publishUpdate({ state: LoadingState.Loading });
|
|
|
|
|
|
|
+ if (this.state.isRunning()) {
|
|
|
|
|
+ this.subject.next(this.state.data);
|
|
|
|
|
+ }
|
|
|
}, delayStateNotification || 500);
|
|
}, delayStateNotification || 500);
|
|
|
|
|
|
|
|
- const resp = await ds.query(request);
|
|
|
|
|
- request.endTime = Date.now();
|
|
|
|
|
-
|
|
|
|
|
- // Make sure we send something back -- called run() w/o subscribe!
|
|
|
|
|
- if (!(this.sendSeries || this.sendLegacy)) {
|
|
|
|
|
- this.sendSeries = true;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Make sure the response is in a supported format
|
|
|
|
|
- const series = this.sendSeries ? getProcessedSeriesData(resp.data) : [];
|
|
|
|
|
- const legacy = this.sendLegacy
|
|
|
|
|
- ? resp.data.map(v => {
|
|
|
|
|
- if (isSeriesData(v)) {
|
|
|
|
|
- return toLegacyResponseData(v);
|
|
|
|
|
- }
|
|
|
|
|
- return v;
|
|
|
|
|
- })
|
|
|
|
|
- : undefined;
|
|
|
|
|
|
|
+ const data = await state.execute(ds, request);
|
|
|
|
|
|
|
|
- // Make sure the delayed loading state timeout is cleared
|
|
|
|
|
|
|
+ // Clear the delayed loading state timeout
|
|
|
clearTimeout(loadingStateTimeoutId);
|
|
clearTimeout(loadingStateTimeoutId);
|
|
|
|
|
|
|
|
- // Publish the result
|
|
|
|
|
- return this.publishUpdate({
|
|
|
|
|
- state: LoadingState.Done,
|
|
|
|
|
- series,
|
|
|
|
|
- legacy,
|
|
|
|
|
- request,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ // Broadcast results
|
|
|
|
|
+ this.subject.next(data);
|
|
|
|
|
+ return data;
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- const error = err as DataQueryError;
|
|
|
|
|
- if (!error.message) {
|
|
|
|
|
- let message = 'Query error';
|
|
|
|
|
- if (error.message) {
|
|
|
|
|
- message = error.message;
|
|
|
|
|
- } else if (error.data && error.data.message) {
|
|
|
|
|
- message = error.data.message;
|
|
|
|
|
- } else if (error.data && error.data.error) {
|
|
|
|
|
- message = error.data.error;
|
|
|
|
|
- } else if (error.status) {
|
|
|
|
|
- message = `Query error: ${error.status} ${error.statusText}`;
|
|
|
|
|
- }
|
|
|
|
|
- error.message = message;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Make sure the delayed loading state timeout is cleared
|
|
|
|
|
clearTimeout(loadingStateTimeoutId);
|
|
clearTimeout(loadingStateTimeoutId);
|
|
|
|
|
|
|
|
- return this.publishUpdate({
|
|
|
|
|
- state: LoadingState.Error,
|
|
|
|
|
- error: error,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const data = state.setError(err);
|
|
|
|
|
+ this.subject.next(data);
|
|
|
|
|
+ return data;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- publishUpdate(update: Partial<PanelData>): PanelData {
|
|
|
|
|
- this.data = {
|
|
|
|
|
- ...this.data,
|
|
|
|
|
- ...update,
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- this.subject.next(this.data);
|
|
|
|
|
-
|
|
|
|
|
- return this.data;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* Called when the panel is closed
|
|
* Called when the panel is closed
|
|
|
*/
|
|
*/
|
|
@@ -239,11 +189,8 @@ export class PanelQueryRunner {
|
|
|
this.subject.complete();
|
|
this.subject.complete();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // If there are open HTTP requests, close them
|
|
|
|
|
- const { request } = this.data;
|
|
|
|
|
- if (request && request.requestId) {
|
|
|
|
|
- getBackendSrv().resolveCancelerIfExists(request.requestId);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // Will cancel and disconnect any open requets
|
|
|
|
|
+ this.state.cancel('destroy');
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|