mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Refactor: move getQueryRunner() to PanelModel (#16679)
* move queryRunner to panelModel * Added interval back as prop, not used yet * PanelQueryRunner: Refactoring, added getQueryRunner to PanelModel * PanelQueryRunner: interpolatel min interval
This commit is contained in:
@@ -22,7 +22,7 @@ import { ScopedVars } from '@grafana/ui';
|
||||
|
||||
import templateSrv from 'app/features/templating/template_srv';
|
||||
|
||||
import { PanelQueryRunner, getProcessedSeriesData } from '../state/PanelQueryRunner';
|
||||
import { getProcessedSeriesData } from '../state/PanelQueryRunner';
|
||||
import { Unsubscribable } from 'rxjs';
|
||||
|
||||
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
|
||||
@@ -134,16 +134,17 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
// Issue Query
|
||||
if (this.wantsQueryExecution) {
|
||||
if (width < 0) {
|
||||
console.log('No width yet... wait till we know');
|
||||
console.log('Refresh skippted, no width yet... wait till we know');
|
||||
return;
|
||||
}
|
||||
if (!panel.queryRunner) {
|
||||
panel.queryRunner = new PanelQueryRunner();
|
||||
}
|
||||
|
||||
const queryRunner = panel.getQueryRunner();
|
||||
|
||||
if (!this.querySubscription) {
|
||||
this.querySubscription = panel.queryRunner.subscribe(this.panelDataObserver);
|
||||
this.querySubscription = queryRunner.subscribe(this.panelDataObserver);
|
||||
}
|
||||
panel.queryRunner.run({
|
||||
|
||||
queryRunner.run({
|
||||
datasource: panel.datasource,
|
||||
queries: panel.targets,
|
||||
panelId: panel.id,
|
||||
@@ -152,8 +153,8 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
timeRange: timeData.timeRange,
|
||||
timeInfo: timeData.timeInfo,
|
||||
widthPixels: width,
|
||||
minInterval: undefined, // Currently not passed in DataPanel?
|
||||
maxDataPoints: panel.maxDataPoints,
|
||||
minInterval: panel.interval,
|
||||
scopedVars: panel.scopedVars,
|
||||
cacheTimeout: panel.cacheTimeout,
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ import { PanelModel } from '../state/PanelModel';
|
||||
import { DashboardModel } from '../state/DashboardModel';
|
||||
import { DataQuery, DataSourceSelectItem, PanelData, LoadingState } from '@grafana/ui/src/types';
|
||||
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
|
||||
import { PanelQueryRunner, PanelQueryRunnerFormat } from '../state/PanelQueryRunner';
|
||||
import { PanelQueryRunnerFormat } from '../state/PanelQueryRunner';
|
||||
import { Unsubscribable } from 'rxjs';
|
||||
|
||||
interface Props {
|
||||
@@ -58,12 +58,9 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
|
||||
componentDidMount() {
|
||||
const { panel } = this.props;
|
||||
const queryRunner = panel.getQueryRunner();
|
||||
|
||||
if (!panel.queryRunner) {
|
||||
panel.queryRunner = new PanelQueryRunner();
|
||||
}
|
||||
|
||||
this.querySubscription = panel.queryRunner.subscribe(this.panelDataObserver, PanelQueryRunnerFormat.both);
|
||||
this.querySubscription = queryRunner.subscribe(this.panelDataObserver, PanelQueryRunnerFormat.both);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
||||
@@ -118,7 +118,7 @@ export class PanelModel {
|
||||
cachedPluginOptions?: any;
|
||||
legend?: { show: boolean };
|
||||
plugin?: PanelPlugin;
|
||||
queryRunner?: PanelQueryRunner;
|
||||
private queryRunner?: PanelQueryRunner;
|
||||
|
||||
constructor(model: any) {
|
||||
this.events = new Emitter();
|
||||
@@ -326,6 +326,13 @@ export class PanelModel {
|
||||
});
|
||||
}
|
||||
|
||||
getQueryRunner(): PanelQueryRunner {
|
||||
if (!this.queryRunner) {
|
||||
this.queryRunner = new PanelQueryRunner();
|
||||
}
|
||||
return this.queryRunner;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.events.emit('panel-teardown');
|
||||
this.events.removeAllListeners();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { getProcessedSeriesData } from './PanelQueryRunner';
|
||||
import { getProcessedSeriesData, PanelQueryRunner } from './PanelQueryRunner';
|
||||
import { PanelData, DataQueryOptions } from '@grafana/ui/src/types';
|
||||
import moment from 'moment';
|
||||
|
||||
describe('QueryRunner', () => {
|
||||
describe('PanelQueryRunner', () => {
|
||||
it('converts timeseries to table skipping nulls', () => {
|
||||
const input1 = {
|
||||
target: 'Field Name',
|
||||
@@ -35,3 +37,133 @@ describe('QueryRunner', () => {
|
||||
expect(getProcessedSeriesData([])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
interface ScenarioContext {
|
||||
setup: (fn: () => void) => void;
|
||||
maxDataPoints?: number | null;
|
||||
widthPixels: number;
|
||||
dsInterval?: string;
|
||||
minInterval?: string;
|
||||
events?: PanelData[];
|
||||
res?: PanelData;
|
||||
queryCalledWith?: DataQueryOptions;
|
||||
}
|
||||
|
||||
type ScenarioFn = (ctx: ScenarioContext) => void;
|
||||
|
||||
function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn) {
|
||||
describe(description, () => {
|
||||
let setupFn = () => {};
|
||||
|
||||
const ctx: ScenarioContext = {
|
||||
widthPixels: 200,
|
||||
setup: (fn: () => void) => {
|
||||
setupFn = fn;
|
||||
},
|
||||
};
|
||||
|
||||
let runner: PanelQueryRunner;
|
||||
const response: any = {
|
||||
data: [{ target: 'hello', datapoints: [] }],
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
setupFn();
|
||||
|
||||
const ds: any = {
|
||||
interval: ctx.dsInterval,
|
||||
query: (options: DataQueryOptions) => {
|
||||
ctx.queryCalledWith = options;
|
||||
return Promise.resolve(response);
|
||||
},
|
||||
testDatasource: jest.fn(),
|
||||
};
|
||||
|
||||
const args: any = {
|
||||
ds: ds as any,
|
||||
datasource: '',
|
||||
minInterval: ctx.minInterval,
|
||||
widthPixels: ctx.widthPixels,
|
||||
maxDataPoints: ctx.maxDataPoints,
|
||||
timeRange: {
|
||||
from: moment().subtract(1, 'days'),
|
||||
to: moment(),
|
||||
raw: { from: '1h', to: 'now' },
|
||||
},
|
||||
panelId: 0,
|
||||
queries: [{ refId: 'A', test: 1 }],
|
||||
};
|
||||
|
||||
runner = new PanelQueryRunner();
|
||||
runner.subscribe({
|
||||
next: (data: PanelData) => {
|
||||
ctx.events.push(data);
|
||||
},
|
||||
});
|
||||
|
||||
ctx.events = [];
|
||||
ctx.res = await runner.run(args);
|
||||
});
|
||||
|
||||
scenarioFn(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
describe('PanelQueryRunner', () => {
|
||||
describeQueryRunnerScenario('with no maxDataPoints or minInterval', ctx => {
|
||||
ctx.setup(() => {
|
||||
ctx.maxDataPoints = null;
|
||||
ctx.widthPixels = 200;
|
||||
});
|
||||
|
||||
it('should return data', async () => {
|
||||
expect(ctx.res.error).toBeUndefined();
|
||||
expect(ctx.res.series.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should use widthPixels as maxDataPoints', async () => {
|
||||
expect(ctx.queryCalledWith.maxDataPoints).toBe(200);
|
||||
});
|
||||
|
||||
it('should calculate interval based on width', async () => {
|
||||
expect(ctx.queryCalledWith.interval).toBe('5m');
|
||||
});
|
||||
|
||||
it('fast query should only publish 1 data events', async () => {
|
||||
expect(ctx.events.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describeQueryRunnerScenario('with no panel min interval but datasource min interval', ctx => {
|
||||
ctx.setup(() => {
|
||||
ctx.widthPixels = 20000;
|
||||
ctx.dsInterval = '15s';
|
||||
});
|
||||
|
||||
it('should limit interval to data source min interval', async () => {
|
||||
expect(ctx.queryCalledWith.interval).toBe('15s');
|
||||
});
|
||||
});
|
||||
|
||||
describeQueryRunnerScenario('with panel min interval and data source min interval', ctx => {
|
||||
ctx.setup(() => {
|
||||
ctx.widthPixels = 20000;
|
||||
ctx.dsInterval = '15s';
|
||||
ctx.minInterval = '30s';
|
||||
});
|
||||
|
||||
it('should limit interval to panel min interval', async () => {
|
||||
expect(ctx.queryCalledWith.interval).toBe('30s');
|
||||
});
|
||||
});
|
||||
|
||||
describeQueryRunnerScenario('with maxDataPoints', ctx => {
|
||||
ctx.setup(() => {
|
||||
ctx.maxDataPoints = 10;
|
||||
});
|
||||
|
||||
it('should pass maxDataPoints if specified', async () => {
|
||||
expect(ctx.queryCalledWith.maxDataPoints).toBe(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
// Libraries
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { Subject, Unsubscribable, PartialObserver } from 'rxjs';
|
||||
|
||||
// Services & Utils
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import templateSrv from 'app/features/templating/template_srv';
|
||||
|
||||
// Components & Types
|
||||
import {
|
||||
guessFieldTypes,
|
||||
toSeriesData,
|
||||
@@ -16,10 +24,6 @@ import {
|
||||
DataSourceApi,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
|
||||
export interface QueryRunnerOptions<TQuery extends DataQuery = DataQuery> {
|
||||
ds?: DataSourceApi<TQuery>; // if they already have the datasource, don't look it up
|
||||
datasource: string | null;
|
||||
@@ -27,11 +31,11 @@ export interface QueryRunnerOptions<TQuery extends DataQuery = DataQuery> {
|
||||
panelId: number;
|
||||
dashboardId?: number;
|
||||
timezone?: string;
|
||||
timeRange?: TimeRange;
|
||||
timeRange: TimeRange;
|
||||
timeInfo?: string; // String description of time range for display
|
||||
widthPixels: number;
|
||||
minInterval?: string;
|
||||
maxDataPoints?: number;
|
||||
maxDataPoints: number | undefined | null;
|
||||
minInterval: string | undefined | null;
|
||||
scopedVars?: ScopedVars;
|
||||
cacheTimeout?: string;
|
||||
delayStateNotification?: number; // default 100ms.
|
||||
@@ -98,6 +102,7 @@ export class PanelQueryRunner {
|
||||
widthPixels,
|
||||
maxDataPoints,
|
||||
scopedVars,
|
||||
minInterval,
|
||||
delayStateNotification,
|
||||
} = options;
|
||||
|
||||
@@ -118,14 +123,12 @@ export class PanelQueryRunner {
|
||||
};
|
||||
|
||||
if (!queries) {
|
||||
this.data = {
|
||||
return this.publishUpdate({
|
||||
state: LoadingState.Done,
|
||||
series: [], // Clear the data
|
||||
legacy: [],
|
||||
request,
|
||||
};
|
||||
this.subject.next(this.data);
|
||||
return this.data;
|
||||
});
|
||||
}
|
||||
|
||||
let loadingStateTimeoutId = 0;
|
||||
@@ -133,8 +136,8 @@ export class PanelQueryRunner {
|
||||
try {
|
||||
const ds = options.ds ? options.ds : await getDatasourceSrv().get(datasource, request.scopedVars);
|
||||
|
||||
const minInterval = options.minInterval || ds.interval;
|
||||
const norm = kbn.calculateInterval(timeRange, widthPixels, minInterval);
|
||||
const lowerIntervalLimit = minInterval ? templateSrv.replace(minInterval, request.scopedVars) : ds.interval;
|
||||
const norm = kbn.calculateInterval(timeRange, widthPixels, lowerIntervalLimit);
|
||||
|
||||
// make shallow copy of scoped vars,
|
||||
// and add built in variables interval and interval_ms
|
||||
@@ -142,6 +145,7 @@ export class PanelQueryRunner {
|
||||
__interval: { text: norm.interval, value: norm.interval },
|
||||
__interval_ms: { text: norm.intervalMs, value: norm.intervalMs },
|
||||
});
|
||||
|
||||
request.interval = norm.interval;
|
||||
request.intervalMs = norm.intervalMs;
|
||||
|
||||
@@ -151,7 +155,6 @@ export class PanelQueryRunner {
|
||||
}, delayStateNotification || 500);
|
||||
|
||||
const resp = await ds.query(request);
|
||||
|
||||
request.endTime = Date.now();
|
||||
|
||||
// Make sure the response is in a supported format
|
||||
@@ -229,5 +232,6 @@ export function getProcessedSeriesData(results?: any[]): SeriesData[] {
|
||||
series.push(guessFieldTypes(toSeriesData(r)));
|
||||
}
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('MetricsPanelCtrl', () => {
|
||||
describe('and has datasource set that supports explore and user does not have access to explore', () => {
|
||||
it('should not return any items', () => {
|
||||
const ctrl = setupController({ hasAccessToExplore: false });
|
||||
ctrl.datasource = { meta: { explore: true } };
|
||||
ctrl.datasource = { meta: { explore: true } } as any;
|
||||
|
||||
expect(ctrl.getAdditionalMenuItems().length).toBe(0);
|
||||
});
|
||||
@@ -39,7 +39,7 @@ describe('MetricsPanelCtrl', () => {
|
||||
describe('and has datasource set that supports explore and user has access to explore', () => {
|
||||
it('should return one item', () => {
|
||||
const ctrl = setupController({ hasAccessToExplore: true });
|
||||
ctrl.datasource = { meta: { explore: true } };
|
||||
ctrl.datasource = { meta: { explore: true } } as any;
|
||||
|
||||
expect(ctrl.getAdditionalMenuItems().length).toBe(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user