diff --git a/public/app/features/explore/state/explorePane.ts b/public/app/features/explore/state/explorePane.ts index f79bae831f2..e8767684831 100644 --- a/public/app/features/explore/state/explorePane.ts +++ b/public/app/features/explore/state/explorePane.ts @@ -94,7 +94,7 @@ export function changeSize(exploreId: string, { width }: { width: number }): Pay return changeSizeAction({ exploreId, width }); } -interface InitializeExploreOptions { +export interface InitializeExploreOptions { exploreId: string; datasource: DataSourceRef | string | undefined; queries: DataQuery[]; diff --git a/public/app/features/explore/state/main.test.ts b/public/app/features/explore/state/main.test.ts index 240e9e7226f..d388986b0dd 100644 --- a/public/app/features/explore/state/main.test.ts +++ b/public/app/features/explore/state/main.test.ts @@ -7,9 +7,10 @@ import { PanelModel } from 'app/features/dashboard/state'; import { reducerTester } from '../../../../test/core/redux/reducerTester'; import { MockDataSourceApi } from '../../../../test/mocks/datasource_srv'; -import { ExploreItemState, ExploreState } from '../../../types'; +import { configureStore } from '../../../store/configureStore'; +import { ExploreItemState, ExploreState, StoreState, ThunkDispatch } from '../../../types'; -import { exploreReducer, navigateToExplore, splitClose } from './main'; +import { exploreReducer, navigateToExplore, splitClose, splitOpen } from './main'; const getNavigateToExploreContext = async (openInNewWindow?: (url: string) => void) => { const url = '/explore'; @@ -116,6 +117,35 @@ describe('navigateToExplore', () => { describe('Explore reducer', () => { describe('split view', () => { + describe('split open', () => { + it('it should create only ony new pane', async () => { + let dispatch: ThunkDispatch, getState: () => StoreState; + + const store: { dispatch: ThunkDispatch; getState: () => StoreState } = configureStore({ + explore: { + panes: { + one: { queries: [], range: {} }, + }, + }, + } as unknown as Partial); + + dispatch = store.dispatch; + getState = store.getState; + + await dispatch(splitOpen()); + let splitPanes = Object.keys(getState().explore.panes); + expect(splitPanes).toHaveLength(2); + let secondSplitPaneId = splitPanes[1]; + + await dispatch(splitOpen()); + splitPanes = Object.keys(getState().explore.panes); + // only 2 panes exist... + expect(splitPanes).toHaveLength(2); + // ...and the second pane is replaced + expect(splitPanes[0]).toBe('one'); + expect(splitPanes[1]).not.toBe(secondSplitPaneId); + }); + }); describe('split close', () => { it('should reset right pane when it is closed', () => { const leftItemMock = { diff --git a/public/app/features/explore/state/main.ts b/public/app/features/explore/state/main.ts index b0501535338..6a0312bd979 100644 --- a/public/app/features/explore/state/main.ts +++ b/public/app/features/explore/state/main.ts @@ -14,7 +14,7 @@ import { CorrelationData } from '../../correlations/useCorrelations'; import { TimeSrv } from '../../dashboard/services/TimeSrv'; import { withUniqueRefIds } from '../utils/queries'; -import { initializeExplore, paneReducer } from './explorePane'; +import { initializeExplore, InitializeExploreOptions, paneReducer } from './explorePane'; import { DEFAULT_RANGE, makeExplorePaneState } from './utils'; // @@ -63,10 +63,7 @@ export const setPaneState = createAction('explore/set export const clearPanes = createAction('explore/clearPanes'); /** - * Opens a new split pane. It either copies existing state of an already present pane - * or uses values from options arg. - * - * TODO: this can be improved by better inferring fallback values. + * Ensure Explore doesn't exceed supported number of panes and initializes the new pane. */ export const splitOpen = createAsyncThunk( 'explore/splitOpen', @@ -76,8 +73,15 @@ export const splitOpen = createAsyncThunk( const queries = options?.queries ?? (options?.query ? [options?.query] : originState?.queries || []); + Object.keys(getState().explore.panes).forEach((paneId, index) => { + // Only 2 panes are supported. Remove panes before create a new one. + if (index >= 1) { + dispatch(splitClose(paneId)); + } + }); + await dispatch( - initializeExplore({ + createNewSplitOpenPane({ exploreId: requestId, datasource: options?.datasourceUid || originState?.datasourceInstance?.getRef(), queries: withUniqueRefIds(queries), @@ -91,6 +95,19 @@ export const splitOpen = createAsyncThunk( } ); +/** + * Opens a new split pane. It either copies existing state of an already present pane + * or uses values from options arg. + * + * TODO: this can be improved by better inferring fallback values. + */ +const createNewSplitOpenPane = createAsyncThunk( + 'explore/createNewSplitOpen', + async (options: InitializeExploreOptions, { dispatch }) => { + await dispatch(initializeExplore(options)); + } +); + export interface NavigateToExploreDependencies { getDataSourceSrv: () => DataSourceSrv; getTimeSrv: () => TimeSrv; @@ -215,12 +232,12 @@ export const exploreReducer = (state = initialExploreState, action: AnyAction): }; } - if (splitOpen.pending.match(action)) { + if (createNewSplitOpenPane.pending.match(action)) { return { ...state, panes: { ...state.panes, - [action.meta.requestId]: initialExploreItemState, + [action.meta.arg.exploreId]: initialExploreItemState, }, }; }