123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- const applyFieldOverridesMock = jest.fn(); // needs to be first in this file
- import { Subject } from 'rxjs';
- // Importing this way to be able to spy on grafana/data
- import * as grafanaData from '@grafana/data';
- import { setDataSourceSrv, setEchoSrv } from '@grafana/runtime';
- import { Echo } from '../../../core/services/echo/Echo';
- import { DashboardModel } from '../../dashboard/state/index';
- import {
- createDashboardQueryRunner,
- setDashboardQueryRunnerFactory,
- } from './DashboardQueryRunner/DashboardQueryRunner';
- import { emptyResult } from './DashboardQueryRunner/utils';
- import { PanelQueryRunner } from './PanelQueryRunner';
- jest.mock('@grafana/data', () => ({
- __esModule: true,
- ...(jest.requireActual('@grafana/data') as any),
- applyFieldOverrides: applyFieldOverridesMock,
- }));
- jest.mock('app/core/services/backend_srv');
- jest.mock('app/core/config', () => ({
- config: { featureToggles: { transformations: true } },
- getConfig: () => ({
- featureToggles: {},
- }),
- }));
- const dashboardModel = new DashboardModel({
- panels: [{ id: 1, type: 'graph' }],
- });
- jest.mock('app/features/dashboard/services/DashboardSrv', () => ({
- getDashboardSrv: () => {
- return {
- getCurrent: () => dashboardModel,
- };
- },
- }));
- interface ScenarioContext {
- setup: (fn: () => void) => void;
- // Options used in setup
- maxDataPoints?: number | null;
- dsInterval?: string;
- minInterval?: string;
- scopedVars: grafanaData.ScopedVars;
- // Filled in by the Scenario runner
- events?: grafanaData.PanelData[];
- res?: grafanaData.PanelData;
- queryCalledWith?: grafanaData.DataQueryRequest;
- runner: PanelQueryRunner;
- }
- type ScenarioFn = (ctx: ScenarioContext) => void;
- const defaultPanelConfig: grafanaData.DataConfigSource = {
- getFieldOverrideOptions: () => undefined,
- getTransformations: () => undefined,
- getDataSupport: () => ({ annotations: false, alertStates: false }),
- };
- function describeQueryRunnerScenario(
- description: string,
- scenarioFn: ScenarioFn,
- panelConfig?: grafanaData.DataConfigSource
- ) {
- describe(description, () => {
- let setupFn = () => {};
- const ctx: ScenarioContext = {
- maxDataPoints: 200,
- scopedVars: {
- server: { text: 'Server1', value: 'server-1' },
- },
- runner: new PanelQueryRunner(panelConfig || defaultPanelConfig),
- setup: (fn: () => void) => {
- setupFn = fn;
- },
- };
- const response: any = {
- data: [
- {
- target: 'hello',
- datapoints: [
- [1, 1000],
- [2, 2000],
- ],
- },
- ],
- };
- setDataSourceSrv({} as any);
- setDashboardQueryRunnerFactory(() => ({
- getResult: emptyResult,
- run: () => undefined,
- cancel: () => undefined,
- cancellations: () => new Subject<any>(),
- destroy: () => undefined,
- }));
- createDashboardQueryRunner({} as any);
- beforeEach(async () => {
- setEchoSrv(new Echo());
- setupFn();
- const datasource: any = {
- name: 'TestDB',
- uid: 'TestDB-uid',
- interval: ctx.dsInterval,
- query: (options: grafanaData.DataQueryRequest) => {
- ctx.queryCalledWith = options;
- return Promise.resolve(response);
- },
- getRef: () => ({ type: 'test', uid: 'TestDB-uid' }),
- testDatasource: jest.fn(),
- };
- const args: any = {
- datasource,
- scopedVars: ctx.scopedVars,
- minInterval: ctx.minInterval,
- maxDataPoints: ctx.maxDataPoints,
- timeRange: {
- from: grafanaData.dateTime().subtract(1, 'days'),
- to: grafanaData.dateTime(),
- raw: { from: '1d', to: 'now' },
- },
- panelId: 1,
- queries: [{ refId: 'A', test: 1 }],
- };
- ctx.runner = new PanelQueryRunner(panelConfig || defaultPanelConfig);
- ctx.runner.getData({ withTransforms: true, withFieldConfig: true }).subscribe({
- next: (data: grafanaData.PanelData) => {
- ctx.res = data;
- ctx.events?.push(data);
- },
- });
- ctx.events = [];
- ctx.runner.run(args);
- });
- scenarioFn(ctx);
- });
- }
- describe('PanelQueryRunner', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
- describeQueryRunnerScenario('simple scenario', (ctx) => {
- it('should set requestId on request', async () => {
- expect(ctx.queryCalledWith?.requestId).toBe('Q100');
- });
- it('should set datasource uid on request', async () => {
- expect(ctx.queryCalledWith?.targets[0].datasource?.uid).toBe('TestDB-uid');
- });
- it('should pass scopedVars to datasource with interval props', async () => {
- expect(ctx.queryCalledWith?.scopedVars.server.text).toBe('Server1');
- expect(ctx.queryCalledWith?.scopedVars.__interval.text).toBe('5m');
- expect(ctx.queryCalledWith?.scopedVars.__interval_ms.text).toBe('300000');
- });
- });
- describeQueryRunnerScenario('with maxDataPoints', (ctx) => {
- ctx.setup(() => {
- ctx.maxDataPoints = 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.maxDataPoints = 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.maxDataPoints = 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);
- });
- it('should use instead of width to calculate interval', async () => {
- expect(ctx.queryCalledWith?.interval).toBe('2h');
- });
- });
- describeQueryRunnerScenario(
- 'field overrides',
- (ctx) => {
- it('should apply when field override options are set', async () => {
- ctx.runner.getData({ withTransforms: true, withFieldConfig: true }).subscribe({
- next: (data: grafanaData.PanelData) => {
- return data;
- },
- });
- expect(applyFieldOverridesMock).toBeCalled();
- });
- },
- {
- getFieldOverrideOptions: () => ({
- fieldConfig: {
- defaults: {
- unit: 'm/s',
- },
- // @ts-ignore
- overrides: [],
- },
- replaceVariables: (v) => v,
- theme: grafanaData.createTheme(),
- }),
- getTransformations: () => undefined,
- getDataSupport: () => ({ annotations: false, alertStates: false }),
- }
- );
- describeQueryRunnerScenario(
- 'transformations',
- (ctx) => {
- it('should apply when transformations are set', async () => {
- const spy = jest.spyOn(grafanaData, 'transformDataFrame');
- spy.mockClear();
- ctx.runner.getData({ withTransforms: true, withFieldConfig: true }).subscribe({
- next: (data: grafanaData.PanelData) => {
- return data;
- },
- });
- expect(spy).toBeCalled();
- });
- },
- {
- getFieldOverrideOptions: () => undefined,
- // @ts-ignore
- getTransformations: () => [{} as unknown as grafanaData.DataTransformerConfig],
- getDataSupport: () => ({ annotations: false, alertStates: false }),
- }
- );
- describeQueryRunnerScenario(
- 'getData',
- (ctx) => {
- it('should not apply transformations when transform option is false', async () => {
- const spy = jest.spyOn(grafanaData, 'transformDataFrame');
- spy.mockClear();
- ctx.runner.getData({ withTransforms: false, withFieldConfig: true }).subscribe({
- next: (data: grafanaData.PanelData) => {
- return data;
- },
- });
- expect(spy).not.toBeCalled();
- });
- it('should not apply field config when applyFieldConfig option is false', async () => {
- ctx.runner.getData({ withFieldConfig: false, withTransforms: true }).subscribe({
- next: (data: grafanaData.PanelData) => {
- return data;
- },
- });
- expect(applyFieldOverridesMock).not.toBeCalled();
- });
- },
- {
- getFieldOverrideOptions: () => ({
- fieldConfig: {
- defaults: {
- unit: 'm/s',
- },
- // @ts-ignore
- overrides: [],
- },
- replaceVariables: (v) => v,
- theme: grafanaData.createTheme(),
- }),
- // @ts-ignore
- getTransformations: () => [{} as unknown as grafanaData.DataTransformerConfig],
- getDataSupport: () => ({ annotations: false, alertStates: false }),
- }
- );
- describeQueryRunnerScenario(
- 'getData',
- (ctx) => {
- it('should not apply transformations when transform option is false', async () => {
- const spy = jest.spyOn(grafanaData, 'transformDataFrame');
- spy.mockClear();
- ctx.runner.getData({ withTransforms: false, withFieldConfig: true }).subscribe({
- next: (data: grafanaData.PanelData) => {
- return data;
- },
- });
- expect(spy).not.toBeCalled();
- });
- it('should not apply field config when applyFieldConfig option is false', async () => {
- ctx.runner.getData({ withFieldConfig: false, withTransforms: true }).subscribe({
- next: (data: grafanaData.PanelData) => {
- return data;
- },
- });
- expect(applyFieldOverridesMock).not.toBeCalled();
- });
- },
- {
- getFieldOverrideOptions: () => ({
- fieldConfig: {
- defaults: {
- unit: 'm/s',
- },
- // @ts-ignore
- overrides: [],
- },
- replaceVariables: (v) => v,
- theme: grafanaData.createTheme(),
- }),
- // @ts-ignore
- getTransformations: () => [{}],
- getDataSupport: () => ({ annotations: false, alertStates: false }),
- }
- );
- const snapshotData: grafanaData.DataFrameDTO[] = [
- {
- fields: [
- { name: 'time', type: grafanaData.FieldType.time, values: [1000] },
- { name: 'value', type: grafanaData.FieldType.number, values: [1] },
- ],
- },
- ];
- describeQueryRunnerScenario(
- 'getData with snapshot data',
- (ctx) => {
- it('should return snapshotted data', async () => {
- ctx.runner.getData({ withTransforms: false, withFieldConfig: true }).subscribe({
- next: (data: grafanaData.PanelData) => {
- expect(data.state).toBe(grafanaData.LoadingState.Done);
- expect(data.series).toEqual(snapshotData);
- expect(data.timeRange).toEqual(grafanaData.getDefaultTimeRange());
- return data;
- },
- });
- });
- },
- {
- ...defaultPanelConfig,
- snapshotData,
- }
- );
- });
|