grafana/public/app/features/explore/state/main.test.ts

460 lines
16 KiB
TypeScript
Raw Normal View History

import { serializeStateToUrlParam } from '@grafana/data/src/utils/url';
import { exploreReducer, initialExploreState, navigateToExplore, splitCloseAction, splitOpenAction } from './main';
import { thunkTester } from 'test/core/thunk/thunkTester';
import { PanelModel } from 'app/features/dashboard/state';
import { updateLocation } from '../../../core/actions';
import { MockDataSourceApi } from '../../../../test/mocks/datasource_srv';
import { ExploreId, ExploreItemState, ExploreState } from '../../../types';
import { makeExplorePaneState, makeInitialUpdateState } from './utils';
import { reducerTester } from '../../../../test/core/redux/reducerTester';
import { ExploreUrlState, UrlQueryMap } from '@grafana/data';
const getNavigateToExploreContext = async (openInNewWindow?: (url: string) => void) => {
const url = 'http://www.someurl.com';
const panel: Partial<PanelModel> = {
datasource: 'mocked datasource',
targets: [{ refId: 'A' }],
};
const datasource = new MockDataSourceApi(panel.datasource!);
const get = jest.fn().mockResolvedValue(datasource);
const getDataSourceSrv = jest.fn().mockReturnValue({ get });
const getTimeSrv = jest.fn();
const getExploreUrl = jest.fn().mockResolvedValue(url);
const dispatchedActions = await thunkTester({})
.givenThunk(navigateToExplore)
.whenThunkIsDispatched(panel, { getDataSourceSrv, getTimeSrv, getExploreUrl, openInNewWindow });
return {
url,
panel,
datasource,
get,
getDataSourceSrv,
getTimeSrv,
getExploreUrl,
dispatchedActions,
};
};
describe('navigateToExplore', () => {
describe('when navigateToExplore thunk is dispatched', () => {
describe('and openInNewWindow is undefined', () => {
const openInNewWindow: (url: string) => void = (undefined as unknown) as (url: string) => void;
it('then it should dispatch correct actions', async () => {
const { dispatchedActions, url } = await getNavigateToExploreContext(openInNewWindow);
expect(dispatchedActions).toEqual([updateLocation({ path: url, query: {} })]);
});
QueryProcessing: Observable query interface and RxJS for query & stream processing (#18899) * I needed to learn some rxjs and understand this more, so just playing around * Updated * Removed all the complete calls * Refactoring * StreamHandler -> observable start * progress * simple singal works * Handle update time range * added error handling * wrap old function * minor changes * handle data format in the subscribe function * Use replay subject to return last value to subscribers * Set loading state after no response in 50ms * added missing file * updated comment * Added cancelation of network requests * runRequest: Added unit test scenario framework * Progress on tests * minor refactor of unit tests * updated test * removed some old code * Shared queries work again, and also became so much simplier * unified query and observe methods * implict any fix * Fixed closed subject issue * removed comment * Use last returned data for loading state * WIP: Explore to runRequest makover step1 * Minor progress * Minor progress on explore and runRequest * minor progress * Things are starting to work in explore * Updated prometheus to use new observable query response, greatly simplified code * Revert refId change * Found better solution for key/refId/requestId problem * use observable with loki * tests compile * fix loki query prep * Explore: correct first response handling * Refactorings * Refactoring * Explore: Fixes LoadingState and GraphResults between runs (#18986) * Refactor: Adds state to DataQueryResponse * Fix: Fixes so we do not empty results before new data arrives Fixes: #17409 * Transformations work * observable test data * remove single() from loki promise * Fixed comment * Explore: Fixes failing Loki and Prometheus unit tests (#18995) * Tests: Makes datasource tests work again * Fix: Fixes loki datasource so highligthing works * Chore: Runs Prettier * Fixed query runner tests * Delay loading state indication to 200ms * Fixed test * fixed unit tests * Clear cached calcs * Fixed bug getProcesedDataFrames * Fix the correct test is a better idea * Fix: Fixes so queries in Explore are only run if Graph/Table is shown (#19000) * Fix: Fixes so queries in Explore are only run if Graph/Table is shown Fixes: #18618 * Refactor: Removes unnecessary condition * PanelData: provide legacy data only when needed (#19018) * no legacy * invert logic... now compiles * merge getQueryResponseData and getDataRaw * update comment about query editor * use single getData() function * only send legacy when it is used in explore * pre process rather than post process * pre process rather than post process * Minor refactoring * Add missing tags to test datasource response * MixedDatasource: Adds query observable pattern to MixedDatasource (#19037) * start mixed datasource * Refactor: Refactors into observable parttern * Tests: Fixes tests * Tests: Removes console.log * Refactor: Adds unique requestId
2019-09-12 10:28:46 -05:00
it('then getDataSourceSrv should have been once', async () => {
const { getDataSourceSrv } = await getNavigateToExploreContext(openInNewWindow);
expect(getDataSourceSrv).toHaveBeenCalledTimes(1);
});
it('then getDataSourceSrv.get should have been called with correct arguments', async () => {
const { get, panel } = await getNavigateToExploreContext(openInNewWindow);
expect(get).toHaveBeenCalledTimes(1);
expect(get).toHaveBeenCalledWith(panel.datasource);
});
it('then getTimeSrv should have been called once', async () => {
const { getTimeSrv } = await getNavigateToExploreContext(openInNewWindow);
expect(getTimeSrv).toHaveBeenCalledTimes(1);
});
it('then getExploreUrl should have been called with correct arguments', async () => {
const { getExploreUrl, panel, datasource, getDataSourceSrv, getTimeSrv } = await getNavigateToExploreContext(
openInNewWindow
);
expect(getExploreUrl).toHaveBeenCalledTimes(1);
expect(getExploreUrl).toHaveBeenCalledWith({
panel,
panelTargets: panel.targets,
panelDatasource: datasource,
datasourceSrv: getDataSourceSrv(),
timeSrv: getTimeSrv(),
});
});
});
describe('and openInNewWindow is defined', () => {
const openInNewWindow: (url: string) => void = jest.fn();
it('then it should dispatch no actions', async () => {
const { dispatchedActions } = await getNavigateToExploreContext(openInNewWindow);
expect(dispatchedActions).toEqual([]);
});
it('then getDataSourceSrv should have been once', async () => {
const { getDataSourceSrv } = await getNavigateToExploreContext(openInNewWindow);
expect(getDataSourceSrv).toHaveBeenCalledTimes(1);
});
it('then getDataSourceSrv.get should have been called with correct arguments', async () => {
const { get, panel } = await getNavigateToExploreContext(openInNewWindow);
expect(get).toHaveBeenCalledTimes(1);
expect(get).toHaveBeenCalledWith(panel.datasource);
});
it('then getTimeSrv should have been called once', async () => {
const { getTimeSrv } = await getNavigateToExploreContext(openInNewWindow);
expect(getTimeSrv).toHaveBeenCalledTimes(1);
});
it('then getExploreUrl should have been called with correct arguments', async () => {
const { getExploreUrl, panel, datasource, getDataSourceSrv, getTimeSrv } = await getNavigateToExploreContext(
openInNewWindow
);
expect(getExploreUrl).toHaveBeenCalledTimes(1);
expect(getExploreUrl).toHaveBeenCalledWith({
panel,
panelTargets: panel.targets,
panelDatasource: datasource,
datasourceSrv: getDataSourceSrv(),
timeSrv: getTimeSrv(),
});
});
it('then openInNewWindow should have been called with correct arguments', async () => {
const openInNewWindowFunc = jest.fn();
const { url } = await getNavigateToExploreContext(openInNewWindowFunc);
expect(openInNewWindowFunc).toHaveBeenCalledTimes(1);
expect(openInNewWindowFunc).toHaveBeenCalledWith(url);
});
});
});
});
describe('Explore reducer', () => {
describe('split view', () => {
it("should make right pane a duplicate of the given item's state on split open", () => {
const leftItemMock = ({
containerWidth: 100,
} as unknown) as ExploreItemState;
const initialState = ({
split: null,
left: leftItemMock as ExploreItemState,
right: makeExplorePaneState(),
} as unknown) as ExploreState;
reducerTester<ExploreState>()
.givenReducer(exploreReducer, initialState)
.whenActionIsDispatched(splitOpenAction({ itemState: leftItemMock }))
.thenStateShouldEqual(({
split: true,
left: leftItemMock,
right: leftItemMock,
} as unknown) as ExploreState);
});
describe('split close', () => {
it('should keep right pane as left when left is closed', () => {
const leftItemMock = ({
containerWidth: 100,
} as unknown) as ExploreItemState;
const rightItemMock = ({
containerWidth: 200,
} as unknown) as ExploreItemState;
const initialState = ({
split: null,
left: leftItemMock,
right: rightItemMock,
} as unknown) as ExploreState;
// closing left item
reducerTester<ExploreState>()
.givenReducer(exploreReducer, initialState)
.whenActionIsDispatched(splitCloseAction({ itemId: ExploreId.left }))
.thenStateShouldEqual(({
split: false,
left: rightItemMock,
right: initialExploreState.right,
} as unknown) as ExploreState);
});
it('should reset right pane when it is closed ', () => {
const leftItemMock = ({
containerWidth: 100,
} as unknown) as ExploreItemState;
const rightItemMock = ({
containerWidth: 200,
} as unknown) as ExploreItemState;
const initialState = ({
split: null,
left: leftItemMock,
right: rightItemMock,
} as unknown) as ExploreState;
// closing left item
reducerTester<ExploreState>()
.givenReducer(exploreReducer, initialState)
.whenActionIsDispatched(splitCloseAction({ itemId: ExploreId.right }))
.thenStateShouldEqual(({
split: false,
left: leftItemMock,
right: initialExploreState.right,
} as unknown) as ExploreState);
});
});
});
describe('when updateLocation is dispatched', () => {
describe('and payload does not contain a query', () => {
it('then it should just return state', () => {
reducerTester<ExploreState>()
.givenReducer(exploreReducer, ({} as unknown) as ExploreState)
.whenActionIsDispatched(updateLocation({ query: (null as unknown) as UrlQueryMap }))
.thenStateShouldEqual(({} as unknown) as ExploreState);
});
});
describe('and payload contains a query', () => {
describe("but does not contain 'left'", () => {
it('then it should just return state', () => {
reducerTester<ExploreState>()
.givenReducer(exploreReducer, ({} as unknown) as ExploreState)
.whenActionIsDispatched(updateLocation({ query: {} }))
.thenStateShouldEqual(({} as unknown) as ExploreState);
});
});
describe("and query contains a 'right'", () => {
it('then it should add split in state', () => {
const { initialState, serializedUrlState } = setup();
const expectedState = { ...initialState, split: true };
reducerTester<ExploreState>()
.givenReducer(exploreReducer, initialState)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
right: serializedUrlState,
},
})
)
.thenStateShouldEqual(expectedState);
});
});
describe("and query contains a 'left'", () => {
describe('but urlState is not set in state', () => {
it('then it should just add urlState and update in state', () => {
const { initialState, serializedUrlState } = setup();
const urlState: ExploreUrlState = (null as unknown) as ExploreUrlState;
const stateWithoutUrlState = ({ ...initialState, left: { urlState } } as unknown) as ExploreState;
const expectedState = { ...initialState };
reducerTester<ExploreState>()
.givenReducer(exploreReducer, stateWithoutUrlState)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
},
path: '/explore',
})
)
.thenStateShouldEqual(expectedState);
});
});
describe("but '/explore' is missing in path", () => {
it('then it should just add urlState and update in state', () => {
const { initialState, serializedUrlState } = setup();
const expectedState = { ...initialState };
reducerTester<ExploreState>()
.givenReducer(exploreReducer, initialState)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
},
path: '/dashboard',
})
)
.thenStateShouldEqual(expectedState);
});
});
describe("and '/explore' is in path", () => {
describe('and datasource differs', () => {
it('then it should return update datasource', () => {
const { initialState, serializedUrlState } = setup();
const expectedState = {
...initialState,
left: {
...initialState.left,
update: {
...initialState.left.update,
datasource: true,
},
},
};
const stateWithDifferentDataSource: any = {
...initialState,
left: {
...initialState.left,
urlState: {
...initialState.left.urlState,
datasource: 'different datasource',
},
},
};
reducerTester<ExploreState>()
.givenReducer(exploreReducer, stateWithDifferentDataSource)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
},
path: '/explore',
})
)
.thenStateShouldEqual(expectedState);
});
});
describe('and range differs', () => {
it('then it should return update range', () => {
const { initialState, serializedUrlState } = setup();
const expectedState = {
...initialState,
left: {
...initialState.left,
update: {
...initialState.left.update,
range: true,
},
},
};
const stateWithDifferentDataSource: any = {
...initialState,
left: {
...initialState.left,
urlState: {
...initialState.left.urlState,
range: {
from: 'now',
to: 'now-6h',
},
},
},
};
reducerTester<ExploreState>()
.givenReducer(exploreReducer, stateWithDifferentDataSource)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
},
path: '/explore',
})
)
.thenStateShouldEqual(expectedState);
});
});
describe('and queries differs', () => {
it('then it should return update queries', () => {
const { initialState, serializedUrlState } = setup();
const expectedState = {
...initialState,
left: {
...initialState.left,
update: {
...initialState.left.update,
queries: true,
},
},
};
const stateWithDifferentDataSource: any = {
...initialState,
left: {
...initialState.left,
urlState: {
...initialState.left.urlState,
queries: [{ expr: '{__filename__="some.log"}' }],
},
},
};
reducerTester<ExploreState>()
.givenReducer(exploreReducer, stateWithDifferentDataSource)
.whenActionIsDispatched(
Explore & Dashboard: New Refresh picker (#16505) * Added RefreshButton * Added RefreshSelect * Added RefreshSelectButton * Added RefreshPicker * Removed the magic string Paused * Minor style changes and using Off instead of Pause * Added HeadlessSelect * Added HeadlessSelect story * Added SelectButton * Removed RefreshSelectButton * Added TimePicker and moved ClickOutsideWrapper to ui/components * Added TimePickerPopOver * Added react-calendar * Missed yarn lock file * Added inputs to popover * Added TimePicker and RefreshPicker to DashNav * Moved TimePicker and RefreshPicker to app/core * Added react-calendar to app and removed from ui/components * Fixed PopOver onClick * Moved everything back to ui components because of typings problems * Exporing RefreshPicker and TimePicker * Added Apply and inputs * Added typings * Added TimePickerInput and logic * Fixed parsing of string to Moments * Fixed range string * Styling and connecting the calendars and inputs * Changed Calendar styling * Added backward forward and zoom * Fixed responsive styles * Moved TimePicker and RefreshPicker into app core * Renamed menuIsOpen to isOpen * Changed from className={} to className="" * Moved Popover to TimePickerOptionGroup * Renamed all PopOver to Popover * Renamed popOver to popover and some minor refactorings * Renamed files with git mv * Added ButtonSelect and refactored RefreshPicker * Refactored TimePicker to use new ButtonSelect * Removed HeadlessSelect as suggested * fix: Fix typings and misc errors after rebase * wip: Enable time picker on dashboard and add tooltip * Merge branch 'master' into hugoh/new-timepicker-and-unified-component # Conflicts: # packages/grafana-ui/package.json # packages/grafana-ui/src/components/Input/Input.test.tsx # packages/grafana-ui/src/components/Input/Input.tsx # packages/grafana-ui/src/utils/validate.ts # public/app/features/dashboard/panel_editor/QueryOptions.tsx # yarn.lock * fix: Snapshot update * Move TimePicker default options into the TimePicker as statics, pass the tooltipContent down the line when wanted and wrap the button in a tooltip element * fix: Override internal state prop if we provide one in a prop * Updated snapshots * Let dashnav control refreshPicker state * feat: Add a stringToMs function * wip: RefreshPicker * wip: Move RefreshPicker to @grafana/ui * wip: Move TimePicker to @grafana/ui * wip: Remove comments * wip: Add refreshPicker to explore * wip: Use default intervals if the prop is missing * wip: Nicer way of setting defaults * fix: Control the select component * wip: Add onMoveForward/onMoveBack * Remove code related to the new time picker and refresh picker from dashnav * Fix: Typings after merge * chore: Minor fix after merge * chore: Remove _.map usage * chore: Moved refresh-picker logic out of the refresh picker since it will work a little differently in explore and dashboards until we have replaced the TimeSrv * feat: Add an Interval component to @grafana/ui * chore: Remove intervalId from redux state and move setInterval logic from ExploreToolbar to its own Interval component * feat: Add refreshInterval to Explore's URL state * feat: Pick up refreshInterval from url on page load * fix: Set default refreshInterval when no value can be retained from URL * fix: Update test initial state with refreshInterval * fix: Handle URLs before RefreshPicker * fix: Move RefreshInterval to url position 3 since the segments can take multiple positions * fix: A better way of detecting urls without RefreshInterval in Explore * chore: Some Explore typings * fix: Attach refresh picker to interval picker * chore: Sass fix for refresh button border radius * fix: Remove refreshInterval from URL * fix: Intervals now start when previous interval is finished * fix: Use clearTimeout instead of clearInterval * fix: Make sure there's a delay set before adding a timeout when we have slow explore queries * wip: Add refresh picker to dashboard * feat: Add util for removing keys with empty values * feat: RefreshPicker in dashboards and tmp rem out old RefreshPicker * fix: Remove the jumpy:ness in the refreshpicker * Changed placement and made it hide when your in dashboard settings * chore: Move logic related to refresh picker out of DashNav to its own component * feat: Add tooltip to refreshpicker * fix: Fix bug with refreshpicker not updating when setting to 'off' * fix: Make it possible to override refresh intervals using the dashboard intervals * chore: Change name of Interval to SetInterval to align with ecmascripts naming since its basically the same but declarative and async * fix: Use default intervals when auto refresh is empty in dashboard settings * fix: Hide time/interval picker when hidden is true on the model, such as on the home dashboard * fix: Interval picker will have to handle location changes since timeSrv wont * RefreshPicker: Refactoring refresh picker * RefreshPicker: minor refactoring
2019-04-16 02:15:23 -05:00
updateLocation({
query: {
left: serializedUrlState,
},
path: '/explore',
})
)
.thenStateShouldEqual(expectedState);
});
});
describe('and nothing differs', () => {
it('then it should return update ui', () => {
const { initialState, serializedUrlState } = setup();
const expectedState = { ...initialState };
reducerTester<ExploreState>()
.givenReducer(exploreReducer, initialState)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
},
path: '/explore',
})
)
.thenStateShouldEqual(expectedState);
});
});
});
});
});
});
});
export const setup = (urlStateOverrides?: any) => {
const update = makeInitialUpdateState();
const urlStateDefaults: ExploreUrlState = {
datasource: 'some-datasource',
queries: [],
range: {
from: '',
to: '',
},
};
const urlState: ExploreUrlState = { ...urlStateDefaults, ...urlStateOverrides };
const serializedUrlState = serializeStateToUrlParam(urlState);
const initialState = ({
split: false,
left: { urlState, update },
right: { urlState, update },
} as unknown) as ExploreState;
return {
initialState,
serializedUrlState,
};
};