Commit 4e54509d by Ryan McKinley Committed by GitHub

Refactor: improvements to PanelQueryRunner (#16678)

merged DataQueryOptions + DataRequestInfo info: DataQueryRequest
parent 5f474c63
......@@ -13,15 +13,23 @@ export enum FieldType {
other = 'other', // Object, Array, etc
}
export interface QueryResultMeta {
[key: string]: any;
// Match the result to the query
requestId?: string;
}
export interface QueryResultBase {
/**
* Matches the query target refId
*/
refId?: string;
/**
* Used by some backend datasources to communicate back info about the execution (generated sql, timing)
*/
meta?: any;
meta?: QueryResultMeta;
}
export interface Field {
......
import { ComponentClass } from 'react';
import { TimeRange, RawTimeRange } from './time';
import { TimeRange } from './time';
import { PluginMeta } from './plugin';
import { TableData, TimeSeries, SeriesData } from './data';
......@@ -94,7 +94,7 @@ export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
/**
* Main metrics / data query action
*/
query(options: DataQueryOptions<TQuery>): Promise<DataQueryResponse>;
query(options: DataQueryRequest<TQuery>): Promise<DataQueryResponse>;
/**
* Test & verify datasource settings & connection details
......@@ -220,10 +220,11 @@ export interface ScopedVars {
[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;
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[];
panelId: number;
dashboardId: number;
......@@ -232,13 +233,8 @@ export interface DataQueryOptions<TQuery extends DataQuery = DataQuery> {
intervalMs: number;
maxDataPoints: number;
scopedVars: ScopedVars;
}
/**
* Timestamps when the query starts and stops
*/
export interface DataRequestInfo extends DataQueryOptions {
timeInfo?: string; // The query time description (blue text in the upper right)
// Request Timing
startTime: number;
endTime?: number;
}
......
import { ComponentClass } from 'react';
import { LoadingState, SeriesData } from './data';
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 interface PanelData {
state: LoadingState;
series: SeriesData[];
request?: DataRequestInfo;
request?: DataQueryRequest;
error?: DataQueryError;
// Data format expected by Angular panels
......
......@@ -336,5 +336,9 @@ export class PanelModel {
destroy() {
this.events.emit('panel-teardown');
this.events.removeAllListeners();
if (this.queryRunner) {
this.queryRunner.destroy();
}
}
}
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';
describe('PanelQueryRunner', () => {
......@@ -46,7 +46,7 @@ interface ScenarioContext {
minInterval?: string;
events?: PanelData[];
res?: PanelData;
queryCalledWith?: DataQueryOptions;
queryCalledWith?: DataQueryRequest;
}
type ScenarioFn = (ctx: ScenarioContext) => void;
......@@ -70,9 +70,9 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn
beforeEach(async () => {
setupFn();
const ds: any = {
const datasource: any = {
interval: ctx.dsInterval,
query: (options: DataQueryOptions) => {
query: (options: DataQueryRequest) => {
ctx.queryCalledWith = options;
return Promise.resolve(response);
},
......@@ -80,8 +80,7 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn
};
const args: any = {
ds: ds as any,
datasource: '',
datasource,
minInterval: ctx.minInterval,
widthPixels: ctx.widthPixels,
maxDataPoints: ctx.maxDataPoints,
......
......@@ -4,6 +4,7 @@ import { Subject, Unsubscribable, PartialObserver } from 'rxjs';
// Services & Utils
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { getBackendSrv } from 'app/core/services/backend_srv';
import kbn from 'app/core/utils/kbn';
import templateSrv from 'app/features/templating/template_srv';
......@@ -16,7 +17,7 @@ import {
DataQuery,
TimeRange,
ScopedVars,
DataRequestInfo,
DataQueryRequest,
SeriesData,
DataQueryError,
toLegacyResponseData,
......@@ -25,8 +26,7 @@ import {
} from '@grafana/ui';
export interface QueryRunnerOptions<TQuery extends DataQuery = DataQuery> {
ds?: DataSourceApi<TQuery>; // if they already have the datasource, don't look it up
datasource: string | null;
datasource: string | DataSourceApi<TQuery>;
queries: TQuery[];
panelId: number;
dashboardId?: number;
......@@ -47,6 +47,11 @@ export enum PanelQueryRunnerFormat {
both = 'both',
}
let counter = 100;
function getNextRequestId() {
return 'Q' + counter++;
}
export class PanelQueryRunner {
private subject?: Subject<PanelData>;
......@@ -106,12 +111,12 @@ export class PanelQueryRunner {
delayStateNotification,
} = options;
const request: DataRequestInfo = {
const request: DataQueryRequest = {
requestId: getNextRequestId(),
timezone,
panelId,
dashboardId,
range: timeRange,
rangeRaw: timeRange.raw,
timeInfo,
interval: '',
intervalMs: 0,
......@@ -121,6 +126,8 @@ export class PanelQueryRunner {
cacheTimeout,
startTime: Date.now(),
};
// Deprecated
(request as any).rangeRaw = timeRange.raw;
if (!queries) {
return this.publishUpdate({
......@@ -134,7 +141,10 @@ export class PanelQueryRunner {
let loadingStateTimeoutId = 0;
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 norm = kbn.calculateInterval(timeRange, widthPixels, lowerIntervalLimit);
......@@ -157,6 +167,11 @@ export class PanelQueryRunner {
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
......@@ -214,6 +229,22 @@ export class PanelQueryRunner {
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);
}
}
}
/**
......
......@@ -3,7 +3,7 @@ import _ from 'lodash';
import * as dateMath from 'app/core/utils/datemath';
import kbn from 'app/core/utils/kbn';
import { CloudWatchQuery } from './types';
import { DataSourceApi } from '@grafana/ui/src/types';
import { DataSourceApi, DataQueryRequest } from '@grafana/ui/src/types';
// import * as moment from 'moment';
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'];
}
query(options) {
query(options: DataQueryRequest<CloudWatchQuery>) {
options = angular.copy(options);
options.targets = this.expandTemplateVariable(options.targets, options.scopedVars, this.templateSrv);
......
// Types
import {
DataQueryOptions,
DataQueryRequest,
SeriesData,
DataQueryResponse,
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[] = [];
for (const query of options.targets) {
if (query.hide) {
......
......@@ -11,7 +11,7 @@ import { makeSeriesForLogs } from 'app/core/logs_model';
// Types
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';
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
.filter(target => target.expr && !target.hide)
.map(target => this.prepareQueryTarget(target, options));
......
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';
class MixedDatasource implements DataSourceApi<DataQuery> {
/** @ngInject */
constructor(private datasourceSrv: DatasourceSrv) {}
query(options: DataQueryOptions<DataQuery>) {
query(options: DataQueryRequest<DataQuery>) {
const sets = _.groupBy(options.targets, 'datasource');
const promises: any = _.map(sets, (targets: DataQuery[]) => {
const dsName = targets[0].datasource;
......
......@@ -15,7 +15,7 @@ import { expandRecordingRules } from './language_utils';
// 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';
export class PrometheusDatasource implements DataSourceApi<PromQuery> {
......@@ -120,7 +120,7 @@ export class PrometheusDatasource implements DataSourceApi<PromQuery> {
return this.templateSrv.variableExists(target.expr);
}
query(options: DataQueryOptions<PromQuery>) {
query(options: DataQueryRequest<PromQuery>) {
const start = this.getPrometheusTime(options.range.from, false);
const end = this.getPrometheusTime(options.range.to, true);
......
......@@ -3,7 +3,7 @@ import appEvents from 'app/core/app_events';
import _ from 'lodash';
import StackdriverMetricFindQuery from './StackdriverMetricFindQuery';
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> {
id: number;
......@@ -108,7 +108,7 @@ export default class StackdriverDatasource implements DataSourceApi<StackdriverQ
return unit;
}
async query(options: DataQueryOptions<StackdriverQuery>) {
async query(options: DataQueryRequest<StackdriverQuery>) {
const result = [];
const data = await this.getTimeSeries(options);
if (data.results) {
......
import _ from 'lodash';
import { DataSourceApi, DataQueryOptions, TableData, TimeSeries } from '@grafana/ui';
import { DataSourceApi, DataQueryRequest, TableData, TimeSeries } from '@grafana/ui';
import { TestDataQuery, Scenario } from './types';
type TestData = TimeSeries | TableData;
......@@ -16,7 +16,7 @@ export class TestDataDatasource implements DataSourceApi<TestDataQuery> {
this.id = instanceSettings.id;
}
query(options: DataQueryOptions<TestDataQuery>) {
query(options: DataQueryRequest<TestDataQuery>) {
const queries = _.filter(options.targets, item => {
return item.hide !== true;
}).map(item => {
......@@ -45,8 +45,11 @@ export class TestDataDatasource implements DataSourceApi<TestDataQuery> {
to: options.range.to.valueOf().toString(),
queries: queries,
},
// This sets up a cancel token
requestId: options.requestId,
})
.then(res => {
.then((res: any) => {
const data: TestData[] = [];
// Returns data in the order it was asked for.
......
import { DataQueryOptions, DataQuery } from '@grafana/ui';
import { DataQueryRequest, DataQuery } from '@grafana/ui';
import moment from 'moment';
export function getQueryOptions<TQuery extends DataQuery>(
options: Partial<DataQueryOptions<TQuery>>
): DataQueryOptions<TQuery> {
options: Partial<DataQueryRequest<TQuery>>
): DataQueryRequest<TQuery> {
const raw = { from: 'now', to: 'now-1h' };
const range = { from: moment(), to: moment(), raw: raw };
const defaults: DataQueryOptions<TQuery> = {
const defaults: DataQueryRequest<TQuery> = {
requestId: 'TEST',
range: range,
rangeRaw: raw,
targets: [],
scopedVars: {},
timezone: 'browser',
......@@ -18,6 +18,7 @@ export function getQueryOptions<TQuery extends DataQuery>(
interval: '60s',
intervalMs: 60000,
maxDataPoints: 500,
startTime: 0,
};
Object.assign(defaults, options);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment