Ver Fonte

Refactor: improvements to PanelQueryRunner (#16678)

merged DataQueryOptions + DataRequestInfo info: DataQueryRequest
Ryan McKinley há 6 anos atrás
pai
commit
4e54509dde

+ 9 - 1
packages/grafana-ui/src/types/data.ts

@@ -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 {

+ 6 - 10
packages/grafana-ui/src/types/datasource.ts

@@ -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;
-}
 
 
-/**
- * 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;
   startTime: number;
   endTime?: number;
   endTime?: number;
 }
 }

+ 2 - 2
packages/grafana-ui/src/types/panel.ts

@@ -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

+ 4 - 0
public/app/features/dashboard/state/PanelModel.ts

@@ -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();
+    }
   }
   }
 }
 }

+ 5 - 6
public/app/features/dashboard/state/PanelQueryRunner.test.ts

@@ -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,

+ 37 - 6
public/app/features/dashboard/state/PanelQueryRunner.ts

@@ -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 | null;
+  datasource: string | DataSourceApi<TQuery>;
   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);
+    }
+  }
 }
 }
 
 
 /**
 /**

+ 2 - 2
public/app/plugins/datasource/cloudwatch/datasource.ts

@@ -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);
 
 

+ 2 - 2
public/app/plugins/datasource/input/datasource.ts

@@ -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) {

+ 2 - 2
public/app/plugins/datasource/loki/datasource.ts

@@ -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));

+ 2 - 2
public/app/plugins/datasource/mixed/datasource.ts

@@ -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;

+ 2 - 2
public/app/plugins/datasource/prometheus/datasource.ts

@@ -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);
 
 

+ 2 - 2
public/app/plugins/datasource/stackdriver/datasource.ts

@@ -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) {

+ 6 - 3
public/app/plugins/datasource/testdata/datasource.ts

@@ -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.

+ 6 - 5
public/test/helpers/getQueryOptions.ts

@@ -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>>
-): DataQueryOptions<TQuery> {
+  options: Partial<DataQueryRequest<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);