mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Fix time interpolation (#46737)
* Ensure TemplateService is updated with new time range on each time range change. * Fix linting errors * Fix explorePane.test.ts * Reuse createDefaultInitialState * Remove unused imports * Add a test for left/right split * Silence console.error in tests * Silence console.error in tests
This commit is contained in:
parent
562397ff8b
commit
bf977ac245
@ -329,7 +329,7 @@ exports[`no enzyme tests`] = {
|
||||
"public/app/plugins/datasource/loki/configuration/ConfigEditor.test.tsx:1661240493": [
|
||||
[1, 17, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"public/app/plugins/datasource/loki/configuration/DebugSection.test.tsx:3844880066": [
|
||||
"public/app/plugins/datasource/loki/configuration/DebugSection.test.tsx:2909181412": [
|
||||
[1, 17, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"public/app/plugins/datasource/loki/configuration/DerivedField.test.tsx:1527527456": [
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { VariableModel, ScopedVars } from '@grafana/data';
|
||||
import { VariableModel, ScopedVars, TimeRange } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* Via the TemplateSrv consumers get access to all the available template variables
|
||||
@ -22,6 +22,11 @@ export interface TemplateSrv {
|
||||
* Checks if a target contains template variables.
|
||||
*/
|
||||
containsTemplate(target?: string): boolean;
|
||||
|
||||
/**
|
||||
* Update the current time range to be used when interpolating __from / __to variables.
|
||||
*/
|
||||
updateTimeRange(timeRange: TimeRange): void;
|
||||
}
|
||||
|
||||
let singletonInstance: TemplateSrv;
|
||||
|
@ -144,7 +144,7 @@ function makeDatasourceSetup({ name = 'loki', id = 1 }: { name?: string; id?: nu
|
||||
}
|
||||
|
||||
export const waitForExplore = async () => {
|
||||
await screen.findByText(/Editor/i);
|
||||
await screen.findAllByText(/Editor/i);
|
||||
};
|
||||
|
||||
export const tearDown = () => {
|
||||
|
46
public/app/features/explore/spec/interpolation.test.tsx
Normal file
46
public/app/features/explore/spec/interpolation.test.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import { setupExplore, waitForExplore } from './helper/setup';
|
||||
import { makeLogsQueryResponse } from './helper/query';
|
||||
import { DataQueryRequest, serializeStateToUrlParam } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { LokiQuery } from '../../../plugins/datasource/loki/types';
|
||||
|
||||
jest.mock('react-virtualized-auto-sizer', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default(props: any) {
|
||||
return <div>{props.children({ width: 1000 })}</div>;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('Explore: interpolation', () => {
|
||||
// support-escalations/issues/1459
|
||||
it('Time is interpolated when explore is opened with a URL', async () => {
|
||||
const urlParams = {
|
||||
left: serializeStateToUrlParam({
|
||||
datasource: 'loki',
|
||||
queries: [{ refId: 'A', expr: '{ job="a", from="${__from}", to="${__to}" }' }],
|
||||
range: { from: '1600000000000', to: '1700000000000' },
|
||||
}),
|
||||
right: serializeStateToUrlParam({
|
||||
datasource: 'loki',
|
||||
queries: [{ refId: 'b', expr: '{ job="b", from="${__from}", to="${__to}" }' }],
|
||||
range: { from: '1800000000000', to: '1900000000000' },
|
||||
}),
|
||||
};
|
||||
const { datasources } = setupExplore({ urlParams });
|
||||
const fakeFetch = jest.fn();
|
||||
|
||||
(datasources.loki.query as jest.Mock).mockImplementation((request: DataQueryRequest<LokiQuery>) => {
|
||||
fakeFetch(getTemplateSrv().replace(request.targets[0]!.expr));
|
||||
return makeLogsQueryResponse();
|
||||
});
|
||||
|
||||
await waitForExplore();
|
||||
|
||||
expect(fakeFetch).toBeCalledTimes(2);
|
||||
expect(fakeFetch).toHaveBeenCalledWith('{ job="a", from="1600000000000", to="1700000000000" }');
|
||||
expect(fakeFetch).toHaveBeenCalledWith('{ job="b", from="1800000000000", to="1900000000000" }');
|
||||
});
|
||||
});
|
@ -1,47 +1,26 @@
|
||||
import { DataQuery, DefaultTimeZone, EventBusExtended, serializeStateToUrlParam, toUtc } from '@grafana/data';
|
||||
import { serializeStateToUrlParam } from '@grafana/data';
|
||||
import { ExploreId, StoreState, ThunkDispatch } from 'app/types';
|
||||
import { refreshExplore } from './explorePane';
|
||||
import { setDataSourceSrv } from '@grafana/runtime';
|
||||
import { configureStore } from '../../../store/configureStore';
|
||||
import { of } from 'rxjs';
|
||||
import { createDefaultInitialState } from './helpers';
|
||||
|
||||
jest.mock('../../dashboard/services/TimeSrv', () => ({
|
||||
getTimeSrv: jest.fn().mockReturnValue({
|
||||
init: jest.fn(),
|
||||
timeRange: jest.fn().mockReturnValue({}),
|
||||
}),
|
||||
}));
|
||||
|
||||
const t = toUtc();
|
||||
const testRange = {
|
||||
from: t,
|
||||
to: t,
|
||||
raw: {
|
||||
from: t,
|
||||
to: t,
|
||||
},
|
||||
};
|
||||
const { testRange, defaultInitialState } = createDefaultInitialState();
|
||||
|
||||
const defaultInitialState = {
|
||||
user: {
|
||||
orgId: '1',
|
||||
timeZone: DefaultTimeZone,
|
||||
},
|
||||
explore: {
|
||||
[ExploreId.left]: {
|
||||
initialized: true,
|
||||
containerWidth: 1920,
|
||||
eventBridge: {} as EventBusExtended,
|
||||
queries: [] as DataQuery[],
|
||||
range: testRange,
|
||||
history: [],
|
||||
refreshInterval: {
|
||||
label: 'Off',
|
||||
value: 0,
|
||||
},
|
||||
cache: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...(jest.requireActual('@grafana/runtime') as unknown as object),
|
||||
getTemplateSrv: () => ({
|
||||
updateTimeRange: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
function setupStore(state?: any) {
|
||||
return configureStore({
|
||||
|
46
public/app/features/explore/state/helpers.ts
Normal file
46
public/app/features/explore/state/helpers.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { DefaultTimeZone, toUtc } from '@grafana/data';
|
||||
import { ExploreId } from '../../../types';
|
||||
|
||||
export const createDefaultInitialState = () => {
|
||||
const t = toUtc();
|
||||
const testRange = {
|
||||
from: t,
|
||||
to: t,
|
||||
raw: {
|
||||
from: t,
|
||||
to: t,
|
||||
},
|
||||
};
|
||||
|
||||
const defaultInitialState = {
|
||||
user: {
|
||||
orgId: '1',
|
||||
timeZone: DefaultTimeZone,
|
||||
},
|
||||
explore: {
|
||||
[ExploreId.left]: {
|
||||
datasourceInstance: {
|
||||
query: jest.fn(),
|
||||
getRef: jest.fn(),
|
||||
getLogsVolumeDataProvider: jest.fn(),
|
||||
meta: {
|
||||
id: 'something',
|
||||
},
|
||||
},
|
||||
initialized: true,
|
||||
containerWidth: 1920,
|
||||
eventBridge: { emit: () => {} } as any,
|
||||
queries: [{ expr: 'test' }] as any[],
|
||||
range: testRange,
|
||||
history: [],
|
||||
refreshInterval: {
|
||||
label: 'Off',
|
||||
value: 0,
|
||||
},
|
||||
cache: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return { testRange, defaultInitialState };
|
||||
};
|
@ -22,12 +22,10 @@ import {
|
||||
DataSourceApi,
|
||||
DataSourceJsonData,
|
||||
DataSourceWithLogsVolumeSupport,
|
||||
DefaultTimeZone,
|
||||
LoadingState,
|
||||
MutableDataFrame,
|
||||
PanelData,
|
||||
RawTimeRange,
|
||||
toUtc,
|
||||
} from '@grafana/data';
|
||||
import { thunkTester } from 'test/core/thunk/thunkTester';
|
||||
import { makeExplorePaneState } from './utils';
|
||||
@ -35,45 +33,24 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester';
|
||||
import { configureStore } from '../../../store/configureStore';
|
||||
import { setTimeSrv } from '../../dashboard/services/TimeSrv';
|
||||
import Mock = jest.Mock;
|
||||
import { createDefaultInitialState } from './helpers';
|
||||
|
||||
const t = toUtc();
|
||||
const testRange = {
|
||||
from: t,
|
||||
to: t,
|
||||
raw: {
|
||||
from: t,
|
||||
to: t,
|
||||
},
|
||||
};
|
||||
const defaultInitialState = {
|
||||
user: {
|
||||
orgId: '1',
|
||||
timeZone: DefaultTimeZone,
|
||||
},
|
||||
explore: {
|
||||
[ExploreId.left]: {
|
||||
datasourceInstance: {
|
||||
query: jest.fn(),
|
||||
getRef: jest.fn(),
|
||||
getLogsVolumeDataProvider: jest.fn(),
|
||||
meta: {
|
||||
id: 'something',
|
||||
},
|
||||
},
|
||||
initialized: true,
|
||||
containerWidth: 1920,
|
||||
eventBridge: { emit: () => {} } as any,
|
||||
queries: [{ expr: 'test' }] as any[],
|
||||
range: testRange,
|
||||
history: [],
|
||||
refreshInterval: {
|
||||
label: 'Off',
|
||||
value: 0,
|
||||
},
|
||||
cache: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
const { testRange, defaultInitialState } = createDefaultInitialState();
|
||||
|
||||
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
|
||||
...jest.requireActual('app/features/dashboard/services/TimeSrv'),
|
||||
getTimeSrv: () => ({
|
||||
init: jest.fn(),
|
||||
timeRange: jest.fn().mockReturnValue({}),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...(jest.requireActual('@grafana/runtime') as unknown as object),
|
||||
getTemplateSrv: () => ({
|
||||
updateTimeRange: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
function setupQueryResponse(state: StoreState) {
|
||||
(state.explore[ExploreId.left].datasourceInstance?.query as Mock).mockReturnValueOnce(
|
||||
|
@ -3,9 +3,44 @@ import { dateTime, LoadingState } from '@grafana/data';
|
||||
import { makeExplorePaneState } from './utils';
|
||||
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
||||
import { reducerTester } from 'test/core/redux/reducerTester';
|
||||
import { changeRangeAction, changeRefreshIntervalAction, timeReducer } from './time';
|
||||
import { changeRangeAction, changeRefreshIntervalAction, timeReducer, updateTime } from './time';
|
||||
import { createDefaultInitialState } from './helpers';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput';
|
||||
|
||||
const MOCK_TIME_RANGE = {};
|
||||
|
||||
const mockTimeSrv = {
|
||||
init: jest.fn(),
|
||||
timeRange: jest.fn().mockReturnValue(MOCK_TIME_RANGE),
|
||||
};
|
||||
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
|
||||
...jest.requireActual('app/features/dashboard/services/TimeSrv'),
|
||||
getTimeSrv: () => mockTimeSrv,
|
||||
}));
|
||||
|
||||
const mockTemplateSrv = {
|
||||
updateTimeRange: jest.fn(),
|
||||
};
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...(jest.requireActual('@grafana/runtime') as unknown as object),
|
||||
getTemplateSrv: () => mockTemplateSrv,
|
||||
}));
|
||||
|
||||
describe('Explore item reducer', () => {
|
||||
silenceConsoleOutput();
|
||||
|
||||
describe('When time is updated', () => {
|
||||
it('Time service is re-initialized and template service is updated with the new time range', async () => {
|
||||
const { dispatch } = configureStore({
|
||||
...(createDefaultInitialState() as any),
|
||||
});
|
||||
await dispatch(updateTime({ exploreId: ExploreId.left }));
|
||||
expect(mockTimeSrv.init).toBeCalled();
|
||||
expect(mockTemplateSrv.updateTimeRange).toBeCalledWith(MOCK_TIME_RANGE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('changing refresh intervals', () => {
|
||||
it("should result in 'streaming' state, when live-tailing is active", () => {
|
||||
const initialState = makeExplorePaneState();
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { RefreshPicker } from '@grafana/ui';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import { getTimeRange, refreshIntervalToSortOrder, stopQueryState } from 'app/core/utils/explore';
|
||||
import { ExploreItemState, ThunkResult } from 'app/types';
|
||||
@ -105,7 +106,11 @@ export const updateTime = (config: {
|
||||
},
|
||||
};
|
||||
|
||||
// We need to re-initialize TimeSrv because it might have been triggered by the other Explore pane (when split)
|
||||
getTimeSrv().init(timeModel);
|
||||
// After re-initializing TimeSrv we need to update the time range in Template service for interpolation
|
||||
// of __from and __to variables
|
||||
getTemplateSrv().updateTimeRange(getTimeSrv().timeRange());
|
||||
|
||||
dispatch(changeRangeAction({ exploreId, range, absoluteRange }));
|
||||
};
|
||||
|
@ -25,6 +25,7 @@ describe('getFieldLinksForExplore', () => {
|
||||
containsTemplate() {
|
||||
return false;
|
||||
},
|
||||
updateTimeRange(timeRange: TimeRange) {},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ScopedVars, VariableModel } from '@grafana/data';
|
||||
import { ScopedVars, TimeRange, VariableModel } from '@grafana/data';
|
||||
import { variableRegex } from '../variables/utils';
|
||||
import { TemplateSrv } from '@grafana/runtime';
|
||||
|
||||
@ -55,4 +55,6 @@ export class TemplateSrvMock implements TemplateSrv {
|
||||
const match = this.regex.exec(target);
|
||||
return match !== null;
|
||||
}
|
||||
|
||||
updateTimeRange(timeRange: TimeRange) {}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { dateTime } from '@grafana/data';
|
||||
import { dateTime, TimeRange } from '@grafana/data';
|
||||
import { setTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import { DebugSection } from './DebugSection';
|
||||
@ -38,6 +38,7 @@ describe('DebugSection', () => {
|
||||
containsTemplate() {
|
||||
return false;
|
||||
},
|
||||
updateTimeRange(timeRange: TimeRange) {},
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user