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": [
|
"public/app/plugins/datasource/loki/configuration/ConfigEditor.test.tsx:1661240493": [
|
||||||
[1, 17, 13, "RegExp match", "2409514259"]
|
[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"]
|
[1, 17, 13, "RegExp match", "2409514259"]
|
||||||
],
|
],
|
||||||
"public/app/plugins/datasource/loki/configuration/DerivedField.test.tsx:1527527456": [
|
"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
|
* 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.
|
* Checks if a target contains template variables.
|
||||||
*/
|
*/
|
||||||
containsTemplate(target?: string): boolean;
|
containsTemplate(target?: string): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current time range to be used when interpolating __from / __to variables.
|
||||||
|
*/
|
||||||
|
updateTimeRange(timeRange: TimeRange): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let singletonInstance: TemplateSrv;
|
let singletonInstance: TemplateSrv;
|
||||||
|
@ -144,7 +144,7 @@ function makeDatasourceSetup({ name = 'loki', id = 1 }: { name?: string; id?: nu
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const waitForExplore = async () => {
|
export const waitForExplore = async () => {
|
||||||
await screen.findByText(/Editor/i);
|
await screen.findAllByText(/Editor/i);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tearDown = () => {
|
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 { ExploreId, StoreState, ThunkDispatch } from 'app/types';
|
||||||
import { refreshExplore } from './explorePane';
|
import { refreshExplore } from './explorePane';
|
||||||
import { setDataSourceSrv } from '@grafana/runtime';
|
import { setDataSourceSrv } from '@grafana/runtime';
|
||||||
import { configureStore } from '../../../store/configureStore';
|
import { configureStore } from '../../../store/configureStore';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
import { createDefaultInitialState } from './helpers';
|
||||||
|
|
||||||
jest.mock('../../dashboard/services/TimeSrv', () => ({
|
jest.mock('../../dashboard/services/TimeSrv', () => ({
|
||||||
getTimeSrv: jest.fn().mockReturnValue({
|
getTimeSrv: jest.fn().mockReturnValue({
|
||||||
init: jest.fn(),
|
init: jest.fn(),
|
||||||
|
timeRange: jest.fn().mockReturnValue({}),
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const t = toUtc();
|
const { testRange, defaultInitialState } = createDefaultInitialState();
|
||||||
const testRange = {
|
|
||||||
from: t,
|
|
||||||
to: t,
|
|
||||||
raw: {
|
|
||||||
from: t,
|
|
||||||
to: t,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultInitialState = {
|
jest.mock('@grafana/runtime', () => ({
|
||||||
user: {
|
...(jest.requireActual('@grafana/runtime') as unknown as object),
|
||||||
orgId: '1',
|
getTemplateSrv: () => ({
|
||||||
timeZone: DefaultTimeZone,
|
updateTimeRange: jest.fn(),
|
||||||
},
|
}),
|
||||||
explore: {
|
}));
|
||||||
[ExploreId.left]: {
|
|
||||||
initialized: true,
|
|
||||||
containerWidth: 1920,
|
|
||||||
eventBridge: {} as EventBusExtended,
|
|
||||||
queries: [] as DataQuery[],
|
|
||||||
range: testRange,
|
|
||||||
history: [],
|
|
||||||
refreshInterval: {
|
|
||||||
label: 'Off',
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
cache: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function setupStore(state?: any) {
|
function setupStore(state?: any) {
|
||||||
return configureStore({
|
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,
|
DataSourceApi,
|
||||||
DataSourceJsonData,
|
DataSourceJsonData,
|
||||||
DataSourceWithLogsVolumeSupport,
|
DataSourceWithLogsVolumeSupport,
|
||||||
DefaultTimeZone,
|
|
||||||
LoadingState,
|
LoadingState,
|
||||||
MutableDataFrame,
|
MutableDataFrame,
|
||||||
PanelData,
|
PanelData,
|
||||||
RawTimeRange,
|
RawTimeRange,
|
||||||
toUtc,
|
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { thunkTester } from 'test/core/thunk/thunkTester';
|
import { thunkTester } from 'test/core/thunk/thunkTester';
|
||||||
import { makeExplorePaneState } from './utils';
|
import { makeExplorePaneState } from './utils';
|
||||||
@ -35,45 +33,24 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester';
|
|||||||
import { configureStore } from '../../../store/configureStore';
|
import { configureStore } from '../../../store/configureStore';
|
||||||
import { setTimeSrv } from '../../dashboard/services/TimeSrv';
|
import { setTimeSrv } from '../../dashboard/services/TimeSrv';
|
||||||
import Mock = jest.Mock;
|
import Mock = jest.Mock;
|
||||||
|
import { createDefaultInitialState } from './helpers';
|
||||||
|
|
||||||
const t = toUtc();
|
const { testRange, defaultInitialState } = createDefaultInitialState();
|
||||||
const testRange = {
|
|
||||||
from: t,
|
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
|
||||||
to: t,
|
...jest.requireActual('app/features/dashboard/services/TimeSrv'),
|
||||||
raw: {
|
getTimeSrv: () => ({
|
||||||
from: t,
|
init: jest.fn(),
|
||||||
to: t,
|
timeRange: jest.fn().mockReturnValue({}),
|
||||||
},
|
}),
|
||||||
};
|
}));
|
||||||
const defaultInitialState = {
|
|
||||||
user: {
|
jest.mock('@grafana/runtime', () => ({
|
||||||
orgId: '1',
|
...(jest.requireActual('@grafana/runtime') as unknown as object),
|
||||||
timeZone: DefaultTimeZone,
|
getTemplateSrv: () => ({
|
||||||
},
|
updateTimeRange: jest.fn(),
|
||||||
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: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function setupQueryResponse(state: StoreState) {
|
function setupQueryResponse(state: StoreState) {
|
||||||
(state.explore[ExploreId.left].datasourceInstance?.query as Mock).mockReturnValueOnce(
|
(state.explore[ExploreId.left].datasourceInstance?.query as Mock).mockReturnValueOnce(
|
||||||
|
@ -3,9 +3,44 @@ import { dateTime, LoadingState } from '@grafana/data';
|
|||||||
import { makeExplorePaneState } from './utils';
|
import { makeExplorePaneState } from './utils';
|
||||||
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
||||||
import { reducerTester } from 'test/core/redux/reducerTester';
|
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', () => {
|
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', () => {
|
describe('changing refresh intervals', () => {
|
||||||
it("should result in 'streaming' state, when live-tailing is active", () => {
|
it("should result in 'streaming' state, when live-tailing is active", () => {
|
||||||
const initialState = makeExplorePaneState();
|
const initialState = makeExplorePaneState();
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
TimeRange,
|
TimeRange,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { RefreshPicker } from '@grafana/ui';
|
import { RefreshPicker } from '@grafana/ui';
|
||||||
|
import { getTemplateSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
import { getTimeRange, refreshIntervalToSortOrder, stopQueryState } from 'app/core/utils/explore';
|
import { getTimeRange, refreshIntervalToSortOrder, stopQueryState } from 'app/core/utils/explore';
|
||||||
import { ExploreItemState, ThunkResult } from 'app/types';
|
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);
|
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 }));
|
dispatch(changeRangeAction({ exploreId, range, absoluteRange }));
|
||||||
};
|
};
|
||||||
|
@ -25,6 +25,7 @@ describe('getFieldLinksForExplore', () => {
|
|||||||
containsTemplate() {
|
containsTemplate() {
|
||||||
return false;
|
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 { variableRegex } from '../variables/utils';
|
||||||
import { TemplateSrv } from '@grafana/runtime';
|
import { TemplateSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
@ -55,4 +55,6 @@ export class TemplateSrvMock implements TemplateSrv {
|
|||||||
const match = this.regex.exec(target);
|
const match = this.regex.exec(target);
|
||||||
return match !== null;
|
return match !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateTimeRange(timeRange: TimeRange) {}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import { dateTime } from '@grafana/data';
|
import { dateTime, TimeRange } from '@grafana/data';
|
||||||
import { setTemplateSrv } from '@grafana/runtime';
|
import { setTemplateSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
import { DebugSection } from './DebugSection';
|
import { DebugSection } from './DebugSection';
|
||||||
@ -38,6 +38,7 @@ describe('DebugSection', () => {
|
|||||||
containsTemplate() {
|
containsTemplate() {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
updateTimeRange(timeRange: TimeRange) {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user