Refactor: improvements to PanelQueryRunner (#16678)

merged DataQueryOptions + DataRequestInfo info: DataQueryRequest
This commit is contained in:
Ryan McKinley 2019-04-18 21:56:27 -07:00 committed by GitHub
parent 5f474c6328
commit 4e54509dde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 87 additions and 45 deletions

View File

@ -13,15 +13,23 @@ export enum FieldType {
other = 'other', // Object, Array, etc other = 'other', // Object, Array, etc
} }
export interface QueryResultMeta {
[key: string]: any;
// Match the result to the query
requestId?: string;
}
export interface QueryResultBase { export interface QueryResultBase {
/** /**
* Matches the query target refId * Matches the query target refId
*/ */
refId?: string; refId?: string;
/** /**
* Used by some backend datasources to communicate back info about the execution (generated sql, timing) * Used by some backend datasources to communicate back info about the execution (generated sql, timing)
*/ */
meta?: any; meta?: QueryResultMeta;
} }
export interface Field { export interface Field {

View File

@ -1,5 +1,5 @@
import { ComponentClass } from 'react'; import { ComponentClass } from 'react';
import { TimeRange, RawTimeRange } from './time'; import { TimeRange } from './time';
import { PluginMeta } from './plugin'; import { PluginMeta } from './plugin';
import { TableData, TimeSeries, SeriesData } from './data'; import { TableData, TimeSeries, SeriesData } from './data';
@ -94,7 +94,7 @@ export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
/** /**
* Main metrics / data query action * Main metrics / data query action
*/ */
query(options: DataQueryOptions<TQuery>): Promise<DataQueryResponse>; query(options: DataQueryRequest<TQuery>): Promise<DataQueryResponse>;
/** /**
* Test & verify datasource settings & connection details * Test & verify datasource settings & connection details
@ -220,10 +220,11 @@ export interface ScopedVars {
[key: string]: ScopedVar; [key: string]: ScopedVar;
} }
export interface DataQueryOptions<TQuery extends DataQuery = DataQuery> { export interface DataQueryRequest<TQuery extends DataQuery = DataQuery> {
requestId: string; // Used to identify results and optionally cancel the request in backendSrv
timezone: string; timezone: string;
range: TimeRange; range: TimeRange;
rangeRaw: RawTimeRange; // Duplicate of results in range. will be deprecated eventually timeInfo?: string; // The query time description (blue text in the upper right)
targets: TQuery[]; targets: TQuery[];
panelId: number; panelId: number;
dashboardId: number; dashboardId: number;
@ -232,13 +233,8 @@ export interface DataQueryOptions<TQuery extends DataQuery = DataQuery> {
intervalMs: number; intervalMs: number;
maxDataPoints: number; maxDataPoints: number;
scopedVars: ScopedVars; scopedVars: ScopedVars;
}
/** // Request Timing
* Timestamps when the query starts and stops
*/
export interface DataRequestInfo extends DataQueryOptions {
timeInfo?: string; // The query time description (blue text in the upper right)
startTime: number; startTime: number;
endTime?: number; endTime?: number;
} }

View File

@ -1,14 +1,14 @@
import { ComponentClass } from 'react'; import { ComponentClass } from 'react';
import { LoadingState, SeriesData } from './data'; import { LoadingState, SeriesData } from './data';
import { TimeRange } from './time'; import { TimeRange } from './time';
import { ScopedVars, DataRequestInfo, DataQueryError, LegacyResponseData } from './datasource'; import { ScopedVars, DataQueryRequest, DataQueryError, LegacyResponseData } from './datasource';
export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string; export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string;
export interface PanelData { export interface PanelData {
state: LoadingState; state: LoadingState;
series: SeriesData[]; series: SeriesData[];
request?: DataRequestInfo; request?: DataQueryRequest;
error?: DataQueryError; error?: DataQueryError;
// Data format expected by Angular panels // Data format expected by Angular panels

View File

@ -336,5 +336,9 @@ export class PanelModel {
destroy() { destroy() {
this.events.emit('panel-teardown'); this.events.emit('panel-teardown');
this.events.removeAllListeners(); this.events.removeAllListeners();
if (this.queryRunner) {
this.queryRunner.destroy();
}
} }
} }

View File

@ -1,5 +1,5 @@
import { getProcessedSeriesData, PanelQueryRunner } from './PanelQueryRunner'; import { getProcessedSeriesData, PanelQueryRunner } from './PanelQueryRunner';
import { PanelData, DataQueryOptions } from '@grafana/ui/src/types'; import { PanelData, DataQueryRequest } from '@grafana/ui/src/types';
import moment from 'moment'; import moment from 'moment';
describe('PanelQueryRunner', () => { describe('PanelQueryRunner', () => {
@ -46,7 +46,7 @@ interface ScenarioContext {
minInterval?: string; minInterval?: string;
events?: PanelData[]; events?: PanelData[];
res?: PanelData; res?: PanelData;
queryCalledWith?: DataQueryOptions; queryCalledWith?: DataQueryRequest;
} }
type ScenarioFn = (ctx: ScenarioContext) => void; type ScenarioFn = (ctx: ScenarioContext) => void;
@ -70,9 +70,9 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn
beforeEach(async () => { beforeEach(async () => {
setupFn(); setupFn();
const ds: any = { const datasource: any = {
interval: ctx.dsInterval, interval: ctx.dsInterval,
query: (options: DataQueryOptions) => { query: (options: DataQueryRequest) => {
ctx.queryCalledWith = options; ctx.queryCalledWith = options;
return Promise.resolve(response); return Promise.resolve(response);
}, },
@ -80,8 +80,7 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn
}; };
const args: any = { const args: any = {
ds: ds as any, datasource,
datasource: '',
minInterval: ctx.minInterval, minInterval: ctx.minInterval,
widthPixels: ctx.widthPixels, widthPixels: ctx.widthPixels,
maxDataPoints: ctx.maxDataPoints, maxDataPoints: ctx.maxDataPoints,

View File

@ -4,6 +4,7 @@ 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';
@ -16,7 +17,7 @@ import {
DataQuery, DataQuery,
TimeRange, TimeRange,
ScopedVars, ScopedVars,
DataRequestInfo, DataQueryRequest,
SeriesData, SeriesData,
DataQueryError, DataQueryError,
toLegacyResponseData, toLegacyResponseData,
@ -25,8 +26,7 @@ import {
} from '@grafana/ui'; } from '@grafana/ui';
export interface QueryRunnerOptions<TQuery extends DataQuery = DataQuery> { export interface QueryRunnerOptions<TQuery extends DataQuery = DataQuery> {
ds?: DataSourceApi<TQuery>; // if they already have the datasource, don't look it up datasource: string | DataSourceApi<TQuery>;
datasource: string | null;
queries: TQuery[]; queries: TQuery[];
panelId: number; panelId: number;
dashboardId?: number; dashboardId?: number;
@ -47,6 +47,11 @@ export enum PanelQueryRunnerFormat {
both = 'both', both = 'both',
} }
let counter = 100;
function getNextRequestId() {
return 'Q' + counter++;
}
export class PanelQueryRunner { export class PanelQueryRunner {
private subject?: Subject<PanelData>; private subject?: Subject<PanelData>;
@ -106,12 +111,12 @@ export class PanelQueryRunner {
delayStateNotification, delayStateNotification,
} = options; } = options;
const request: DataRequestInfo = { const request: DataQueryRequest = {
requestId: getNextRequestId(),
timezone, timezone,
panelId, panelId,
dashboardId, dashboardId,
range: timeRange, range: timeRange,
rangeRaw: timeRange.raw,
timeInfo, timeInfo,
interval: '', interval: '',
intervalMs: 0, intervalMs: 0,
@ -121,6 +126,8 @@ export class PanelQueryRunner {
cacheTimeout, cacheTimeout,
startTime: Date.now(), startTime: Date.now(),
}; };
// Deprecated
(request as any).rangeRaw = timeRange.raw;
if (!queries) { if (!queries) {
return this.publishUpdate({ return this.publishUpdate({
@ -134,7 +141,10 @@ export class PanelQueryRunner {
let loadingStateTimeoutId = 0; let loadingStateTimeoutId = 0;
try { try {
const ds = options.ds ? options.ds : await getDatasourceSrv().get(datasource, request.scopedVars); const ds =
datasource && (datasource as any).query
? (datasource as DataSourceApi)
: await getDatasourceSrv().get(datasource as string, request.scopedVars);
const lowerIntervalLimit = minInterval ? templateSrv.replace(minInterval, request.scopedVars) : ds.interval; const lowerIntervalLimit = minInterval ? templateSrv.replace(minInterval, request.scopedVars) : ds.interval;
const norm = kbn.calculateInterval(timeRange, widthPixels, lowerIntervalLimit); const norm = kbn.calculateInterval(timeRange, widthPixels, lowerIntervalLimit);
@ -157,6 +167,11 @@ export class PanelQueryRunner {
const resp = await ds.query(request); const resp = await ds.query(request);
request.endTime = Date.now(); 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 // Make sure the response is in a supported format
const series = this.sendSeries ? getProcessedSeriesData(resp.data) : []; const series = this.sendSeries ? getProcessedSeriesData(resp.data) : [];
const legacy = this.sendLegacy const legacy = this.sendLegacy
@ -214,6 +229,22 @@ export class PanelQueryRunner {
return this.data; return this.data;
} }
/**
* Called when the panel is closed
*/
destroy() {
// Tell anyone listening that we are done
if (this.subject) {
this.subject.complete();
}
// If there are open HTTP requests, close them
const { request } = this.data;
if (request && request.requestId) {
getBackendSrv().resolveCancelerIfExists(request.requestId);
}
}
} }
/** /**

View File

@ -3,7 +3,7 @@ import _ from 'lodash';
import * as dateMath from 'app/core/utils/datemath'; import * as dateMath from 'app/core/utils/datemath';
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import { CloudWatchQuery } from './types'; import { CloudWatchQuery } from './types';
import { DataSourceApi } from '@grafana/ui/src/types'; import { DataSourceApi, DataQueryRequest } from '@grafana/ui/src/types';
// import * as moment from 'moment'; // import * as moment from 'moment';
export default class CloudWatchDatasource implements DataSourceApi<CloudWatchQuery> { export default class CloudWatchDatasource implements DataSourceApi<CloudWatchQuery> {
@ -23,7 +23,7 @@ export default class CloudWatchDatasource implements DataSourceApi<CloudWatchQue
this.standardStatistics = ['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount']; this.standardStatistics = ['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount'];
} }
query(options) { query(options: DataQueryRequest<CloudWatchQuery>) {
options = angular.copy(options); options = angular.copy(options);
options.targets = this.expandTemplateVariable(options.targets, options.scopedVars, this.templateSrv); options.targets = this.expandTemplateVariable(options.targets, options.scopedVars, this.templateSrv);

View File

@ -1,6 +1,6 @@
// Types // Types
import { import {
DataQueryOptions, DataQueryRequest,
SeriesData, SeriesData,
DataQueryResponse, DataQueryResponse,
DataSourceApi, DataSourceApi,
@ -67,7 +67,7 @@ export class InputDatasource implements DataSourceApi<InputQuery> {
}); });
} }
query(options: DataQueryOptions<InputQuery>): Promise<DataQueryResponse> { query(options: DataQueryRequest<InputQuery>): Promise<DataQueryResponse> {
const results: SeriesData[] = []; const results: SeriesData[] = [];
for (const query of options.targets) { for (const query of options.targets) {
if (query.hide) { if (query.hide) {

View File

@ -11,7 +11,7 @@ import { makeSeriesForLogs } from 'app/core/logs_model';
// Types // Types
import { LogsStream, LogsModel } from 'app/core/logs_model'; import { LogsStream, LogsModel } from 'app/core/logs_model';
import { PluginMeta, DataQueryOptions } from '@grafana/ui/src/types'; import { PluginMeta, DataQueryRequest } from '@grafana/ui/src/types';
import { LokiQuery } from './types'; import { LokiQuery } from './types';
export const DEFAULT_MAX_LINES = 1000; export const DEFAULT_MAX_LINES = 1000;
@ -73,7 +73,7 @@ export class LokiDatasource {
}; };
} }
async query(options: DataQueryOptions<LokiQuery>) { async query(options: DataQueryRequest<LokiQuery>) {
const queryTargets = options.targets const queryTargets = options.targets
.filter(target => target.expr && !target.hide) .filter(target => target.expr && !target.hide)
.map(target => this.prepareQueryTarget(target, options)); .map(target => this.prepareQueryTarget(target, options));

View File

@ -1,13 +1,13 @@
import _ from 'lodash'; import _ from 'lodash';
import { DataSourceApi, DataQuery, DataQueryOptions } from '@grafana/ui'; import { DataSourceApi, DataQuery, DataQueryRequest } from '@grafana/ui';
import DatasourceSrv from 'app/features/plugins/datasource_srv'; import DatasourceSrv from 'app/features/plugins/datasource_srv';
class MixedDatasource implements DataSourceApi<DataQuery> { class MixedDatasource implements DataSourceApi<DataQuery> {
/** @ngInject */ /** @ngInject */
constructor(private datasourceSrv: DatasourceSrv) {} constructor(private datasourceSrv: DatasourceSrv) {}
query(options: DataQueryOptions<DataQuery>) { query(options: DataQueryRequest<DataQuery>) {
const sets = _.groupBy(options.targets, 'datasource'); const sets = _.groupBy(options.targets, 'datasource');
const promises: any = _.map(sets, (targets: DataQuery[]) => { const promises: any = _.map(sets, (targets: DataQuery[]) => {
const dsName = targets[0].datasource; const dsName = targets[0].datasource;

View File

@ -15,7 +15,7 @@ import { expandRecordingRules } from './language_utils';
// Types // Types
import { PromQuery } from './types'; import { PromQuery } from './types';
import { DataQueryOptions, DataSourceApi, AnnotationEvent } from '@grafana/ui/src/types'; import { DataQueryRequest, DataSourceApi, AnnotationEvent } from '@grafana/ui/src/types';
import { ExploreUrlState } from 'app/types/explore'; import { ExploreUrlState } from 'app/types/explore';
export class PrometheusDatasource implements DataSourceApi<PromQuery> { export class PrometheusDatasource implements DataSourceApi<PromQuery> {
@ -120,7 +120,7 @@ export class PrometheusDatasource implements DataSourceApi<PromQuery> {
return this.templateSrv.variableExists(target.expr); return this.templateSrv.variableExists(target.expr);
} }
query(options: DataQueryOptions<PromQuery>) { query(options: DataQueryRequest<PromQuery>) {
const start = this.getPrometheusTime(options.range.from, false); const start = this.getPrometheusTime(options.range.from, false);
const end = this.getPrometheusTime(options.range.to, true); const end = this.getPrometheusTime(options.range.to, true);

View File

@ -3,7 +3,7 @@ import appEvents from 'app/core/app_events';
import _ from 'lodash'; import _ from 'lodash';
import StackdriverMetricFindQuery from './StackdriverMetricFindQuery'; import StackdriverMetricFindQuery from './StackdriverMetricFindQuery';
import { StackdriverQuery, MetricDescriptor } from './types'; import { StackdriverQuery, MetricDescriptor } from './types';
import { DataSourceApi, DataQueryOptions } from '@grafana/ui/src/types'; import { DataSourceApi, DataQueryRequest } from '@grafana/ui/src/types';
export default class StackdriverDatasource implements DataSourceApi<StackdriverQuery> { export default class StackdriverDatasource implements DataSourceApi<StackdriverQuery> {
id: number; id: number;
@ -108,7 +108,7 @@ export default class StackdriverDatasource implements DataSourceApi<StackdriverQ
return unit; return unit;
} }
async query(options: DataQueryOptions<StackdriverQuery>) { async query(options: DataQueryRequest<StackdriverQuery>) {
const result = []; const result = [];
const data = await this.getTimeSeries(options); const data = await this.getTimeSeries(options);
if (data.results) { if (data.results) {

View File

@ -1,5 +1,5 @@
import _ from 'lodash'; import _ from 'lodash';
import { DataSourceApi, DataQueryOptions, TableData, TimeSeries } from '@grafana/ui'; import { DataSourceApi, DataQueryRequest, TableData, TimeSeries } from '@grafana/ui';
import { TestDataQuery, Scenario } from './types'; import { TestDataQuery, Scenario } from './types';
type TestData = TimeSeries | TableData; type TestData = TimeSeries | TableData;
@ -16,7 +16,7 @@ export class TestDataDatasource implements DataSourceApi<TestDataQuery> {
this.id = instanceSettings.id; this.id = instanceSettings.id;
} }
query(options: DataQueryOptions<TestDataQuery>) { query(options: DataQueryRequest<TestDataQuery>) {
const queries = _.filter(options.targets, item => { const queries = _.filter(options.targets, item => {
return item.hide !== true; return item.hide !== true;
}).map(item => { }).map(item => {
@ -45,8 +45,11 @@ export class TestDataDatasource implements DataSourceApi<TestDataQuery> {
to: options.range.to.valueOf().toString(), to: options.range.to.valueOf().toString(),
queries: queries, queries: queries,
}, },
// This sets up a cancel token
requestId: options.requestId,
}) })
.then(res => { .then((res: any) => {
const data: TestData[] = []; const data: TestData[] = [];
// Returns data in the order it was asked for. // Returns data in the order it was asked for.

View File

@ -1,15 +1,15 @@
import { DataQueryOptions, DataQuery } from '@grafana/ui'; import { DataQueryRequest, DataQuery } from '@grafana/ui';
import moment from 'moment'; import moment from 'moment';
export function getQueryOptions<TQuery extends DataQuery>( export function getQueryOptions<TQuery extends DataQuery>(
options: Partial<DataQueryOptions<TQuery>> options: Partial<DataQueryRequest<TQuery>>
): DataQueryOptions<TQuery> { ): DataQueryRequest<TQuery> {
const raw = { from: 'now', to: 'now-1h' }; const raw = { from: 'now', to: 'now-1h' };
const range = { from: moment(), to: moment(), raw: raw }; const range = { from: moment(), to: moment(), raw: raw };
const defaults: DataQueryOptions<TQuery> = { const defaults: DataQueryRequest<TQuery> = {
requestId: 'TEST',
range: range, range: range,
rangeRaw: raw,
targets: [], targets: [],
scopedVars: {}, scopedVars: {},
timezone: 'browser', timezone: 'browser',
@ -18,6 +18,7 @@ export function getQueryOptions<TQuery extends DataQuery>(
interval: '60s', interval: '60s',
intervalMs: 60000, intervalMs: 60000,
maxDataPoints: 500, maxDataPoints: 500,
startTime: 0,
}; };
Object.assign(defaults, options); Object.assign(defaults, options);