mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Discover] Support state updates across tabs (#215620)
## Summary This PR adjusts the approach introduced in #214861 to ensure state updates work consistently across tabs, even after switching tabs during async operations. The `currentTabId` prop has been removed from the central state since it can't be relied on in actions, and instead tab IDs are injected using a `CurrentTabProvider`. This allows selectors to work the same as they did before, and tab specific actions have been updated to use a standard `TabAction` interface that accepts a tab ID and prevents leaking state changes. This approach is safer but adds some complexity, so for actions dispatched from React components, a `useCurrentTabAction` hook has been added to handle injecting the current tab ID. We also still need to access tab state within `DiscoverStateContainer` for now, so two utility methods (`injectCurrentTab` and `getCurrentTab`) have been added to make this easier. Since `DiscoverStateContainer` is scoped to a single tab, this should be safe, and ideally temporary until we get rid of it completely. Resolves #215398. ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
63575a8320
commit
bf7de0e6b9
39 changed files with 813 additions and 593 deletions
|
@ -240,7 +240,7 @@ export class DiscoverCustomizationExamplesPlugin implements Plugin {
|
|||
ControlGroupRendererApi | undefined
|
||||
>();
|
||||
const stateStorage = stateContainer.stateStorage;
|
||||
const currentTabId = stateContainer.internalState.getState().tabs.currentId;
|
||||
const currentTabId = stateContainer.getCurrentTab().id;
|
||||
const dataView = useObservable(
|
||||
stateContainer.runtimeStateManager.tabs.byId[currentTabId].currentDataView$,
|
||||
stateContainer.runtimeStateManager.tabs.byId[currentTabId].currentDataView$.getValue()
|
||||
|
|
|
@ -55,16 +55,18 @@ export function getDiscoverStateMock({
|
|||
...(toasts && withNotifyOnErrors(toasts)),
|
||||
});
|
||||
runtimeStateManager = runtimeStateManager ?? createRuntimeStateManager();
|
||||
const internalState = createInternalStateStore({
|
||||
services,
|
||||
customizationContext,
|
||||
runtimeStateManager,
|
||||
urlStateStorage: stateStorageContainer,
|
||||
});
|
||||
const container = getDiscoverStateContainer({
|
||||
tabId: internalState.getState().tabs.allIds[0],
|
||||
services,
|
||||
customizationContext,
|
||||
stateStorageContainer,
|
||||
internalState: createInternalStateStore({
|
||||
services,
|
||||
customizationContext,
|
||||
runtimeStateManager,
|
||||
urlStateStorage: stateStorageContainer,
|
||||
}),
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
});
|
||||
if (savedSearch !== false) {
|
||||
|
|
|
@ -30,7 +30,7 @@ import { createCustomizationService } from '../../../../customizations/customiza
|
|||
import { DiscoverGrid } from '../../../../components/discover_grid';
|
||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||
import type { ProfilesManager } from '../../../../context_awareness';
|
||||
import { internalStateActions } from '../../state_management/redux';
|
||||
import { CurrentTabProvider, internalStateActions } from '../../state_management/redux';
|
||||
|
||||
const customisationService = createCustomizationService();
|
||||
|
||||
|
@ -54,14 +54,16 @@ async function mountComponent(
|
|||
dataSource: createDataViewDataSource({ dataViewId: dataViewMock.id! }),
|
||||
});
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setDataRequestParams({
|
||||
timeRangeRelative: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
timeRangeAbsolute: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
stateContainer.injectCurrentTab(internalStateActions.setDataRequestParams)({
|
||||
dataRequestParams: {
|
||||
timeRangeRelative: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
timeRangeAbsolute: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -81,11 +83,13 @@ async function mountComponent(
|
|||
services={{ ...services, profilesManager: profilesManager ?? services.profilesManager }}
|
||||
>
|
||||
<DiscoverCustomizationProvider value={customisationService}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<EuiProvider highContrastMode={false}>
|
||||
<DiscoverDocuments {...props} />
|
||||
</EuiProvider>
|
||||
</DiscoverMainProvider>
|
||||
<CurrentTabProvider currentTabId={stateContainer.getCurrentTab().id}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<EuiProvider highContrastMode={false}>
|
||||
<DiscoverDocuments {...props} />
|
||||
</EuiProvider>
|
||||
</DiscoverMainProvider>
|
||||
</CurrentTabProvider>
|
||||
</DiscoverCustomizationProvider>
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
|
|
|
@ -76,10 +76,10 @@ import {
|
|||
} from '../../../../context_awareness';
|
||||
import {
|
||||
internalStateActions,
|
||||
useCurrentTabSelector,
|
||||
useInternalStateDispatch,
|
||||
useInternalStateSelector,
|
||||
} from '../../state_management/redux';
|
||||
import { useCurrentTabSelector } from '../../state_management/redux/hooks';
|
||||
|
||||
const DiscoverGridMemoized = React.memo(DiscoverGrid);
|
||||
|
||||
|
|
|
@ -37,7 +37,11 @@ import { DiscoverMainProvider } from '../../state_management/discover_state_prov
|
|||
import { act } from 'react-dom/test-utils';
|
||||
import { PanelsToggle } from '../../../../components/panels_toggle';
|
||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||
import { RuntimeStateProvider, internalStateActions } from '../../state_management/redux';
|
||||
import {
|
||||
CurrentTabProvider,
|
||||
RuntimeStateProvider,
|
||||
internalStateActions,
|
||||
} from '../../state_management/redux';
|
||||
|
||||
function getStateContainer(savedSearch?: SavedSearch) {
|
||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true, savedSearch });
|
||||
|
@ -50,16 +54,20 @@ function getStateContainer(savedSearch?: SavedSearch) {
|
|||
|
||||
stateContainer.appState.update(appState);
|
||||
|
||||
stateContainer.internalState.dispatch(internalStateActions.setDataView(dataView));
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setDataRequestParams({
|
||||
timeRangeAbsolute: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
timeRangeRelative: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
stateContainer.injectCurrentTab(internalStateActions.setDataView)({ dataView })
|
||||
);
|
||||
stateContainer.internalState.dispatch(
|
||||
stateContainer.injectCurrentTab(internalStateActions.setDataRequestParams)({
|
||||
dataRequestParams: {
|
||||
timeRangeAbsolute: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
timeRangeRelative: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -147,11 +155,13 @@ const mountComponent = async ({
|
|||
const component = mountWithIntl(
|
||||
<KibanaRenderContextProvider {...services.core}>
|
||||
<KibanaContextProvider services={services}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<RuntimeStateProvider currentDataView={dataView} adHocDataViews={[]}>
|
||||
<DiscoverHistogramLayout {...props} />
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
<CurrentTabProvider currentTabId={stateContainer.getCurrentTab().id}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<RuntimeStateProvider currentDataView={dataView} adHocDataViews={[]}>
|
||||
<DiscoverHistogramLayout {...props} />
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
</CurrentTabProvider>
|
||||
</KibanaContextProvider>
|
||||
</KibanaRenderContextProvider>
|
||||
);
|
||||
|
|
|
@ -40,7 +40,11 @@ import { act } from 'react-dom/test-utils';
|
|||
import { ErrorCallout } from '../../../../components/common/error_callout';
|
||||
import { PanelsToggle } from '../../../../components/panels_toggle';
|
||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||
import { RuntimeStateProvider, internalStateActions } from '../../state_management/redux';
|
||||
import {
|
||||
CurrentTabProvider,
|
||||
RuntimeStateProvider,
|
||||
internalStateActions,
|
||||
} from '../../state_management/redux';
|
||||
|
||||
jest.mock('@elastic/eui', () => ({
|
||||
...jest.requireActual('@elastic/eui'),
|
||||
|
@ -105,9 +109,13 @@ async function mountComponent(
|
|||
interval: 'auto',
|
||||
query,
|
||||
});
|
||||
stateContainer.internalState.dispatch(internalStateActions.setDataView(dataView));
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setDataRequestParams({ timeRangeAbsolute: time, timeRangeRelative: time })
|
||||
stateContainer.injectCurrentTab(internalStateActions.setDataView)({ dataView })
|
||||
);
|
||||
stateContainer.internalState.dispatch(
|
||||
stateContainer.injectCurrentTab(internalStateActions.setDataRequestParams)({
|
||||
dataRequestParams: { timeRangeAbsolute: time, timeRangeRelative: time },
|
||||
})
|
||||
);
|
||||
|
||||
const props = {
|
||||
|
@ -127,13 +135,15 @@ async function mountComponent(
|
|||
|
||||
const component = mountWithIntl(
|
||||
<KibanaContextProvider services={services}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<RuntimeStateProvider currentDataView={dataView} adHocDataViews={[]}>
|
||||
<EuiProvider highContrastMode={false}>
|
||||
<DiscoverLayout {...props} />
|
||||
</EuiProvider>
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
<CurrentTabProvider currentTabId={stateContainer.getCurrentTab().id}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<RuntimeStateProvider currentDataView={dataView} adHocDataViews={[]}>
|
||||
<EuiProvider highContrastMode={false}>
|
||||
<DiscoverLayout {...props} />
|
||||
</EuiProvider>
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
</CurrentTabProvider>
|
||||
</KibanaContextProvider>,
|
||||
mountOptions
|
||||
);
|
||||
|
|
|
@ -57,8 +57,7 @@ import type { PanelsToggleProps } from '../../../../components/panels_toggle';
|
|||
import { PanelsToggle } from '../../../../components/panels_toggle';
|
||||
import { sendErrorMsg } from '../../hooks/use_saved_search_messages';
|
||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||
import { useCurrentDataView } from '../../state_management/redux';
|
||||
import { useCurrentTabSelector } from '../../state_management/redux/hooks';
|
||||
import { useCurrentDataView, useCurrentTabSelector } from '../../state_management/redux';
|
||||
|
||||
const SidebarMemoized = React.memo(DiscoverSidebarResponsive);
|
||||
const TopNavMemoized = React.memo(DiscoverTopNav);
|
||||
|
|
|
@ -39,6 +39,7 @@ import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock'
|
|||
import { PanelsToggle } from '../../../../components/panels_toggle';
|
||||
import type { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||
import { CurrentTabProvider } from '../../state_management/redux';
|
||||
|
||||
const mountComponent = async ({
|
||||
hideChart = false,
|
||||
|
@ -130,9 +131,11 @@ const mountComponent = async ({
|
|||
const component = mountWithIntl(
|
||||
<KibanaRenderContextProvider {...services.core}>
|
||||
<KibanaContextProvider services={services}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<DiscoverMainContent {...props} />
|
||||
</DiscoverMainProvider>
|
||||
<CurrentTabProvider currentTabId={stateContainer.getCurrentTab().id}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<DiscoverMainContent {...props} />
|
||||
</DiscoverMainProvider>
|
||||
</CurrentTabProvider>
|
||||
</KibanaContextProvider>
|
||||
</KibanaRenderContextProvider>
|
||||
);
|
||||
|
|
|
@ -29,7 +29,11 @@ import type { InspectorAdapters } from '../../hooks/use_inspector';
|
|||
import type { UnifiedHistogramCustomization } from '../../../../customizations/customization_types/histogram_customization';
|
||||
import { useDiscoverCustomization } from '../../../../customizations';
|
||||
import type { DiscoverCustomizationId } from '../../../../customizations/customization_service';
|
||||
import { RuntimeStateProvider, internalStateActions } from '../../state_management/redux';
|
||||
import {
|
||||
CurrentTabProvider,
|
||||
RuntimeStateProvider,
|
||||
internalStateActions,
|
||||
} from '../../state_management/redux';
|
||||
import { dataViewMockWithTimeField } from '@kbn/discover-utils/src/__mocks__';
|
||||
|
||||
const mockData = dataPluginMock.createStartContract();
|
||||
|
@ -123,11 +127,13 @@ describe('useDiscoverHistogram', () => {
|
|||
};
|
||||
|
||||
const Wrapper = ({ children }: React.PropsWithChildren<unknown>) => (
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<RuntimeStateProvider currentDataView={dataViewMockWithTimeField} adHocDataViews={[]}>
|
||||
{children as ReactElement}
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
<CurrentTabProvider currentTabId={stateContainer.getCurrentTab().id}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<RuntimeStateProvider currentDataView={dataViewMockWithTimeField} adHocDataViews={[]}>
|
||||
{children as ReactElement}
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
</CurrentTabProvider>
|
||||
);
|
||||
|
||||
const hook = renderHook(
|
||||
|
@ -391,9 +397,11 @@ describe('useDiscoverHistogram', () => {
|
|||
const timeRangeAbs = { from: '2021-05-01T20:00:00Z', to: '2021-05-02T20:00:00Z' };
|
||||
const timeRangeRel = { from: 'now-15m', to: 'now' };
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setDataRequestParams({
|
||||
timeRangeAbsolute: timeRangeAbs,
|
||||
timeRangeRelative: timeRangeRel,
|
||||
stateContainer.injectCurrentTab(internalStateActions.setDataRequestParams)({
|
||||
dataRequestParams: {
|
||||
timeRangeAbsolute: timeRangeAbs,
|
||||
timeRangeRelative: timeRangeRel,
|
||||
},
|
||||
})
|
||||
);
|
||||
const { hook } = await renderUseDiscoverHistogram({ stateContainer });
|
||||
|
|
|
@ -57,9 +57,10 @@ import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
|||
import {
|
||||
internalStateActions,
|
||||
useCurrentDataView,
|
||||
useCurrentTabAction,
|
||||
useCurrentTabSelector,
|
||||
useInternalStateDispatch,
|
||||
} from '../../state_management/redux';
|
||||
import { useCurrentTabSelector } from '../../state_management/redux/hooks';
|
||||
|
||||
const EMPTY_ESQL_COLUMNS: DatatableColumn[] = [];
|
||||
const EMPTY_FILTERS: Filter[] = [];
|
||||
|
@ -321,6 +322,9 @@ export const useDiscoverHistogram = ({
|
|||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const timeRangeMemoized = useMemo(() => timeRange, [timeRange?.from, timeRange?.to]);
|
||||
const setOverriddenVisContextAfterInvalidation = useCurrentTabAction(
|
||||
internalStateActions.setOverriddenVisContextAfterInvalidation
|
||||
);
|
||||
const dispatch = useInternalStateDispatch();
|
||||
|
||||
const onVisContextChanged = useCallback(
|
||||
|
@ -335,25 +339,41 @@ export const useDiscoverHistogram = ({
|
|||
stateContainer.savedSearchState.updateVisContext({
|
||||
nextVisContext,
|
||||
});
|
||||
dispatch(internalStateActions.setOverriddenVisContextAfterInvalidation(undefined));
|
||||
dispatch(
|
||||
setOverriddenVisContextAfterInvalidation({
|
||||
overriddenVisContextAfterInvalidation: undefined,
|
||||
})
|
||||
);
|
||||
break;
|
||||
case UnifiedHistogramExternalVisContextStatus.automaticallyOverridden:
|
||||
// if the visualization was invalidated as incompatible and rebuilt
|
||||
// (it will be used later for saving the visualization via Save button)
|
||||
dispatch(internalStateActions.setOverriddenVisContextAfterInvalidation(nextVisContext));
|
||||
dispatch(
|
||||
setOverriddenVisContextAfterInvalidation({
|
||||
overriddenVisContextAfterInvalidation: nextVisContext,
|
||||
})
|
||||
);
|
||||
break;
|
||||
case UnifiedHistogramExternalVisContextStatus.automaticallyCreated:
|
||||
case UnifiedHistogramExternalVisContextStatus.applied:
|
||||
// clearing the value in the internal state so we don't use it during saved search saving
|
||||
dispatch(internalStateActions.setOverriddenVisContextAfterInvalidation(undefined));
|
||||
dispatch(
|
||||
setOverriddenVisContextAfterInvalidation({
|
||||
overriddenVisContextAfterInvalidation: undefined,
|
||||
})
|
||||
);
|
||||
break;
|
||||
case UnifiedHistogramExternalVisContextStatus.unknown:
|
||||
// using `{}` to overwrite the value inside the saved search SO during saving
|
||||
dispatch(internalStateActions.setOverriddenVisContextAfterInvalidation({}));
|
||||
dispatch(
|
||||
setOverriddenVisContextAfterInvalidation({
|
||||
overriddenVisContextAfterInvalidation: {},
|
||||
})
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
[dispatch, stateContainer.savedSearchState]
|
||||
[dispatch, setOverriddenVisContextAfterInvalidation, stateContainer.savedSearchState]
|
||||
);
|
||||
|
||||
const breakdownField = useAppStateSelector((state) => state.breakdownField);
|
||||
|
|
|
@ -20,7 +20,11 @@ import { Router } from '@kbn/shared-ux-router';
|
|||
import { createMemoryHistory } from 'history';
|
||||
import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock';
|
||||
import { DiscoverMainProvider } from '../../state_management/discover_state_provider';
|
||||
import { RuntimeStateProvider, internalStateActions } from '../../state_management/redux';
|
||||
import {
|
||||
CurrentTabProvider,
|
||||
RuntimeStateProvider,
|
||||
internalStateActions,
|
||||
} from '../../state_management/redux';
|
||||
|
||||
discoverServiceMock.data.query.timefilter.timefilter.getTime = () => {
|
||||
return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' };
|
||||
|
@ -46,11 +50,13 @@ describe('DiscoverMainApp', () => {
|
|||
const component = mountWithIntl(
|
||||
<Router history={history}>
|
||||
<KibanaContextProvider services={discoverServiceMock}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||
<DiscoverMainApp {...props} />
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
<CurrentTabProvider currentTabId={stateContainer.getCurrentTab().id}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||
<DiscoverMainApp {...props} />
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
</CurrentTabProvider>
|
||||
</KibanaContextProvider>
|
||||
</Router>
|
||||
);
|
||||
|
|
|
@ -29,6 +29,8 @@ import {
|
|||
useInternalStateSelector,
|
||||
useRuntimeState,
|
||||
useCurrentTabRuntimeState,
|
||||
useCurrentTabSelector,
|
||||
useCurrentTabAction,
|
||||
} from '../../state_management/redux';
|
||||
import type {
|
||||
CustomizationCallback,
|
||||
|
@ -89,11 +91,14 @@ export const DiscoverSessionView = forwardRef<DiscoverSessionViewRef, DiscoverSe
|
|||
const services = useDiscoverServices();
|
||||
const { core, history, getScopedHistory } = services;
|
||||
const { id: discoverSessionId } = useParams<{ id?: string }>();
|
||||
const currentTabId = useCurrentTabSelector((tab) => tab.id);
|
||||
const initializeSessionAction = useCurrentTabAction(internalStateActions.initializeSession);
|
||||
const [initializeSessionState, initializeSession] = useAsyncFunction<InitializeSession>(
|
||||
async ({ dataViewSpec, defaultUrlState } = {}) => {
|
||||
initializeSessionState.value?.stateContainer?.actions.stopSyncing();
|
||||
|
||||
const stateContainer = getDiscoverStateContainer({
|
||||
tabId: currentTabId,
|
||||
services,
|
||||
customizationContext,
|
||||
stateStorageContainer: urlStateStorage,
|
||||
|
@ -101,11 +106,13 @@ export const DiscoverSessionView = forwardRef<DiscoverSessionViewRef, DiscoverSe
|
|||
runtimeStateManager,
|
||||
});
|
||||
const { showNoDataPage } = await dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer,
|
||||
discoverSessionId,
|
||||
dataViewSpec,
|
||||
defaultUrlState,
|
||||
initializeSessionAction({
|
||||
initializeSessionParams: {
|
||||
stateContainer,
|
||||
discoverSessionId,
|
||||
dataViewSpec,
|
||||
defaultUrlState,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -13,20 +13,26 @@ import { pick } from 'lodash';
|
|||
import type { DiscoverSessionViewRef } from '../session_view';
|
||||
import { DiscoverSessionView, type DiscoverSessionViewProps } from '../session_view';
|
||||
import {
|
||||
CurrentTabProvider,
|
||||
createTabItem,
|
||||
internalStateActions,
|
||||
selectAllTabs,
|
||||
selectCurrentTab,
|
||||
useInternalStateDispatch,
|
||||
useInternalStateSelector,
|
||||
} from '../../state_management/redux';
|
||||
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||
|
||||
export const TabsView = ({ sessionViewProps }: { sessionViewProps: DiscoverSessionViewProps }) => {
|
||||
export const TabsView = ({
|
||||
initialTabId,
|
||||
sessionViewProps,
|
||||
}: {
|
||||
initialTabId: string;
|
||||
sessionViewProps: DiscoverSessionViewProps;
|
||||
}) => {
|
||||
const services = useDiscoverServices();
|
||||
const dispatch = useInternalStateDispatch();
|
||||
const currentTab = useInternalStateSelector(selectCurrentTab);
|
||||
const allTabs = useInternalStateSelector(selectAllTabs);
|
||||
const [currentTabId, setCurrentTabId] = useState(initialTabId);
|
||||
const [initialItems] = useState<TabItem[]>(() => allTabs.map((tab) => pick(tab, 'id', 'label')));
|
||||
const sessionViewRef = useRef<DiscoverSessionViewRef>(null);
|
||||
|
||||
|
@ -34,21 +40,25 @@ export const TabsView = ({ sessionViewProps }: { sessionViewProps: DiscoverSessi
|
|||
<UnifiedTabs
|
||||
services={services}
|
||||
initialItems={initialItems}
|
||||
onChanged={(updateState) =>
|
||||
dispatch(
|
||||
onChanged={async (updateState) => {
|
||||
await dispatch(
|
||||
internalStateActions.updateTabs({
|
||||
currentTabId,
|
||||
updateState,
|
||||
stopSyncing: sessionViewRef.current?.stopSyncing,
|
||||
})
|
||||
)
|
||||
}
|
||||
);
|
||||
setCurrentTabId(updateState.selectedItem?.id ?? currentTabId);
|
||||
}}
|
||||
createItem={() => createTabItem(allTabs)}
|
||||
getPreviewData={() => ({
|
||||
query: { language: 'kuery', query: 'sample query' },
|
||||
status: TabStatus.SUCCESS,
|
||||
})}
|
||||
renderContent={() => (
|
||||
<DiscoverSessionView key={currentTab.id} ref={sessionViewRef} {...sessionViewProps} />
|
||||
<CurrentTabProvider currentTabId={currentTabId}>
|
||||
<DiscoverSessionView key={currentTabId} ref={sessionViewRef} {...sessionViewProps} />
|
||||
</CurrentTabProvider>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -73,7 +73,9 @@ function getProps(
|
|||
mockDiscoverService.capabilities = capabilities as typeof mockDiscoverService.capabilities;
|
||||
}
|
||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
||||
stateContainer.internalState.dispatch(internalStateActions.setDataView(dataViewMock));
|
||||
stateContainer.internalState.dispatch(
|
||||
stateContainer.injectCurrentTab(internalStateActions.setDataView)({ dataView: dataViewMock })
|
||||
);
|
||||
|
||||
return {
|
||||
stateContainer,
|
||||
|
|
|
@ -17,7 +17,7 @@ import type { SavedSearch, SaveSavedSearchOptions } from '@kbn/saved-search-plug
|
|||
import type { DiscoverServices } from '../../../../build_services';
|
||||
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||
import { getAllowedSampleSize } from '../../../../utils/get_allowed_sample_size';
|
||||
import { internalStateActions, selectCurrentTab } from '../../state_management/redux';
|
||||
import { internalStateActions } from '../../state_management/redux';
|
||||
|
||||
async function saveDataSource({
|
||||
savedSearch,
|
||||
|
@ -95,7 +95,7 @@ export async function onSaveSearch({
|
|||
}) {
|
||||
const { uiSettings, savedObjectsTagging } = services;
|
||||
const dataView = savedSearch.searchSource.getField('index');
|
||||
const currentTab = selectCurrentTab(state.internalState.getState());
|
||||
const currentTab = state.getCurrentTab();
|
||||
const overriddenVisContextAfterInvalidation = currentTab.overriddenVisContextAfterInvalidation;
|
||||
|
||||
const onSave = async ({
|
||||
|
@ -176,7 +176,9 @@ export async function onSaveSearch({
|
|||
savedSearch.tags = currentTags;
|
||||
}
|
||||
} else {
|
||||
state.internalState.dispatch(internalStateActions.resetOnSavedSearchChange());
|
||||
state.internalState.dispatch(
|
||||
state.injectCurrentTab(internalStateActions.resetOnSavedSearchChange)()
|
||||
);
|
||||
state.appState.resetInitialState();
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import { buildDataTableRecord } from '@kbn/discover-utils';
|
|||
import { dataViewMock, esHitsMockWithSort } from '@kbn/discover-utils/src/__mocks__';
|
||||
import { searchResponseIncompleteWarningLocalCluster } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings';
|
||||
import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock';
|
||||
import type { TabState } from '../state_management/redux';
|
||||
|
||||
jest.mock('./fetch_documents', () => ({
|
||||
fetchDocuments: jest.fn().mockResolvedValue([]),
|
||||
|
@ -57,6 +58,8 @@ describe('test fetchAll', () => {
|
|||
let subjects: SavedSearchData;
|
||||
let deps: Parameters<typeof fetchAll>[2];
|
||||
let searchSource: SearchSource;
|
||||
let getCurrentTab: () => TabState;
|
||||
|
||||
beforeEach(() => {
|
||||
subjects = {
|
||||
main$: new BehaviorSubject<DataMainMsg>({ fetchStatus: FetchStatus.UNINITIALIZED }),
|
||||
|
@ -64,12 +67,12 @@ describe('test fetchAll', () => {
|
|||
totalHits$: new BehaviorSubject<DataTotalHitsMsg>({ fetchStatus: FetchStatus.UNINITIALIZED }),
|
||||
};
|
||||
searchSource = savedSearchMock.searchSource.createChild();
|
||||
|
||||
const { internalState, getCurrentTab: localGetCurrentTab } = getDiscoverStateMock({});
|
||||
deps = {
|
||||
abortController: new AbortController(),
|
||||
inspectorAdapters: { requests: new RequestAdapter() },
|
||||
getAppState: () => ({}),
|
||||
internalState: getDiscoverStateMock({}).internalState,
|
||||
internalState,
|
||||
searchSessionId: '123',
|
||||
initialFetchStatus: FetchStatus.UNINITIALIZED,
|
||||
savedSearch: {
|
||||
|
@ -78,7 +81,7 @@ describe('test fetchAll', () => {
|
|||
},
|
||||
services: discoverServiceMock,
|
||||
};
|
||||
|
||||
getCurrentTab = localGetCurrentTab;
|
||||
mockFetchDocuments.mockReset().mockResolvedValue({ records: [] });
|
||||
mockfetchEsql.mockReset().mockResolvedValue({ records: [] });
|
||||
});
|
||||
|
@ -88,7 +91,7 @@ describe('test fetchAll', () => {
|
|||
|
||||
subjects.main$.subscribe((value) => stateArr.push(value.fetchStatus));
|
||||
|
||||
fetchAll(subjects, false, deps);
|
||||
fetchAll(subjects, false, deps, getCurrentTab);
|
||||
await waitForNextTick();
|
||||
|
||||
expect(stateArr).toEqual([
|
||||
|
@ -106,7 +109,7 @@ describe('test fetchAll', () => {
|
|||
];
|
||||
const documents = hits.map((hit) => buildDataTableRecord(hit, dataViewMock));
|
||||
mockFetchDocuments.mockResolvedValue({ records: documents });
|
||||
fetchAll(subjects, false, deps);
|
||||
fetchAll(subjects, false, deps, getCurrentTab);
|
||||
await waitForNextTick();
|
||||
expect(await collect()).toEqual([
|
||||
{ fetchStatus: FetchStatus.UNINITIALIZED },
|
||||
|
@ -131,7 +134,7 @@ describe('test fetchAll', () => {
|
|||
subjects.totalHits$.next({
|
||||
fetchStatus: FetchStatus.LOADING,
|
||||
});
|
||||
fetchAll(subjects, false, deps);
|
||||
fetchAll(subjects, false, deps, getCurrentTab);
|
||||
await waitForNextTick();
|
||||
subjects.totalHits$.next({
|
||||
fetchStatus: FetchStatus.COMPLETE,
|
||||
|
@ -152,7 +155,7 @@ describe('test fetchAll', () => {
|
|||
subjects.totalHits$.next({
|
||||
fetchStatus: FetchStatus.LOADING,
|
||||
});
|
||||
fetchAll(subjects, false, deps);
|
||||
fetchAll(subjects, false, deps, getCurrentTab);
|
||||
await waitForNextTick();
|
||||
subjects.totalHits$.next({
|
||||
fetchStatus: FetchStatus.COMPLETE,
|
||||
|
@ -177,7 +180,7 @@ describe('test fetchAll', () => {
|
|||
subjects.totalHits$.next({
|
||||
fetchStatus: FetchStatus.LOADING,
|
||||
});
|
||||
fetchAll(subjects, false, deps);
|
||||
fetchAll(subjects, false, deps, getCurrentTab);
|
||||
await waitForNextTick();
|
||||
subjects.totalHits$.next({
|
||||
fetchStatus: FetchStatus.ERROR,
|
||||
|
@ -209,7 +212,7 @@ describe('test fetchAll', () => {
|
|||
subjects.totalHits$.next({
|
||||
fetchStatus: FetchStatus.LOADING,
|
||||
});
|
||||
fetchAll(subjects, false, deps);
|
||||
fetchAll(subjects, false, deps, getCurrentTab);
|
||||
await waitForNextTick();
|
||||
subjects.totalHits$.next({
|
||||
fetchStatus: FetchStatus.COMPLETE,
|
||||
|
@ -249,7 +252,7 @@ describe('test fetchAll', () => {
|
|||
getAppState: () => ({ query }),
|
||||
internalState: getDiscoverStateMock({}).internalState,
|
||||
};
|
||||
fetchAll(subjects, false, deps);
|
||||
fetchAll(subjects, false, deps, getCurrentTab);
|
||||
await waitForNextTick();
|
||||
|
||||
expect(await collect()).toEqual([
|
||||
|
@ -358,7 +361,7 @@ describe('test fetchAll', () => {
|
|||
getAppState: () => ({ query }),
|
||||
internalState: getDiscoverStateMock({}).internalState,
|
||||
};
|
||||
fetchAll(subjects, false, deps);
|
||||
fetchAll(subjects, false, deps, getCurrentTab);
|
||||
deps.abortController.abort();
|
||||
await waitForNextTick();
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ import type {
|
|||
} from '../state_management/discover_data_state_container';
|
||||
import type { DiscoverServices } from '../../../build_services';
|
||||
import { fetchEsql } from './fetch_esql';
|
||||
import { selectCurrentTab, type InternalStateStore } from '../state_management/redux';
|
||||
import { type InternalStateStore, type TabState } from '../state_management/redux';
|
||||
|
||||
export interface FetchDeps {
|
||||
abortController: AbortController;
|
||||
|
@ -59,12 +59,12 @@ export function fetchAll(
|
|||
dataSubjects: SavedSearchData,
|
||||
reset = false,
|
||||
fetchDeps: FetchDeps,
|
||||
getCurrentTab: () => TabState,
|
||||
onFetchRecordsComplete?: () => Promise<void>
|
||||
): Promise<void> {
|
||||
const {
|
||||
initialFetchStatus,
|
||||
getAppState,
|
||||
internalState,
|
||||
services,
|
||||
inspectorAdapters,
|
||||
savedSearch,
|
||||
|
@ -78,7 +78,7 @@ export function fetchAll(
|
|||
const query = getAppState().query;
|
||||
const prevQuery = dataSubjects.documents$.getValue().query;
|
||||
const isEsqlQuery = isOfAggregateQueryType(query);
|
||||
const currentTab = selectCurrentTab(internalState.getState());
|
||||
const currentTab = getCurrentTab();
|
||||
|
||||
if (reset) {
|
||||
sendResetMsg(dataSubjects, initialFetchStatus);
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
createInternalStateStore,
|
||||
createRuntimeStateManager,
|
||||
internalStateActions,
|
||||
CurrentTabProvider,
|
||||
} from './state_management/redux';
|
||||
import type { RootProfileState } from '../../context_awareness';
|
||||
import { useRootProfile, useDefaultAdHocDataViews } from '../../context_awareness';
|
||||
|
@ -75,6 +76,7 @@ export const DiscoverMainRoute = ({
|
|||
urlStateStorage,
|
||||
})
|
||||
);
|
||||
const [initialTabId] = useState(() => internalState.getState().tabs.allIds[0]);
|
||||
const { initializeProfileDataViews } = useDefaultAdHocDataViews({ internalState });
|
||||
const [mainRouteInitializationState, initializeMainRoute] = useAsyncFunction<InitializeMainRoute>(
|
||||
async (loadedRootProfileState) => {
|
||||
|
@ -137,9 +139,11 @@ export const DiscoverMainRoute = ({
|
|||
<InternalStateProvider store={internalState}>
|
||||
<rootProfileState.AppWrapper>
|
||||
{TABS_ENABLED ? (
|
||||
<TabsView sessionViewProps={sessionViewProps} />
|
||||
<TabsView initialTabId={initialTabId} sessionViewProps={sessionViewProps} />
|
||||
) : (
|
||||
<DiscoverSessionView {...sessionViewProps} />
|
||||
<CurrentTabProvider currentTabId={initialTabId}>
|
||||
<DiscoverSessionView {...sessionViewProps} />
|
||||
</CurrentTabProvider>
|
||||
)}
|
||||
</rootProfileState.AppWrapper>
|
||||
</InternalStateProvider>
|
||||
|
|
|
@ -27,7 +27,7 @@ import { dataViewAdHoc } from '../../../__mocks__/data_view_complex';
|
|||
import type { EsHitRecord } from '@kbn/discover-utils';
|
||||
import { buildDataTableRecord } from '@kbn/discover-utils';
|
||||
import { omit } from 'lodash';
|
||||
import { internalStateActions, selectCurrentTab } from '../state_management/redux';
|
||||
import { CurrentTabProvider, internalStateActions } from '../state_management/redux';
|
||||
|
||||
async function getHookProps(
|
||||
query: AggregateQuery | Query | undefined,
|
||||
|
@ -82,9 +82,11 @@ const getDataViewsService = () => {
|
|||
|
||||
const getHookContext = (stateContainer: DiscoverStateContainer) => {
|
||||
return ({ children }: React.PropsWithChildren) => (
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<>{children}</>
|
||||
</DiscoverMainProvider>
|
||||
<CurrentTabProvider currentTabId={stateContainer.getCurrentTab().id}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<>{children}</>
|
||||
</DiscoverMainProvider>
|
||||
</CurrentTabProvider>
|
||||
);
|
||||
};
|
||||
const renderHookWithContext = async (
|
||||
|
@ -506,12 +508,7 @@ describe('useEsqlMode', () => {
|
|||
FetchStatus.LOADING
|
||||
);
|
||||
const documents$ = stateContainer.dataState.data$.documents$;
|
||||
expect(
|
||||
omit(
|
||||
selectCurrentTab(stateContainer.internalState.getState()).resetDefaultProfileState,
|
||||
'resetId'
|
||||
)
|
||||
).toEqual({
|
||||
expect(omit(stateContainer.getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
@ -526,12 +523,7 @@ describe('useEsqlMode', () => {
|
|||
query: { esql: 'from pattern1' },
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
omit(
|
||||
selectCurrentTab(stateContainer.internalState.getState()).resetDefaultProfileState,
|
||||
'resetId'
|
||||
)
|
||||
).toEqual({
|
||||
expect(omit(stateContainer.getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
|
@ -542,10 +534,12 @@ describe('useEsqlMode', () => {
|
|||
query: { esql: 'from pattern1' },
|
||||
});
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
stateContainer.injectCurrentTab(internalStateActions.setResetDefaultProfileState)({
|
||||
resetDefaultProfileState: {
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
stateContainer.appState.update({ query: { esql: 'from pattern1' } });
|
||||
|
@ -554,12 +548,7 @@ describe('useEsqlMode', () => {
|
|||
query: { esql: 'from pattern1' },
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
omit(
|
||||
selectCurrentTab(stateContainer.internalState.getState()).resetDefaultProfileState,
|
||||
'resetId'
|
||||
)
|
||||
).toEqual({
|
||||
expect(omit(stateContainer.getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
@ -575,12 +564,7 @@ describe('useEsqlMode', () => {
|
|||
query: { esql: 'from pattern2' },
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
omit(
|
||||
selectCurrentTab(stateContainer.internalState.getState()).resetDefaultProfileState,
|
||||
'resetId'
|
||||
)
|
||||
).toEqual({
|
||||
expect(omit(stateContainer.getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
|
@ -597,12 +581,7 @@ describe('useEsqlMode', () => {
|
|||
const documents$ = stateContainer.dataState.data$.documents$;
|
||||
const result1 = [buildDataTableRecord({ message: 'foo' } as EsHitRecord)];
|
||||
const result2 = [buildDataTableRecord({ message: 'foo', extension: 'bar' } as EsHitRecord)];
|
||||
expect(
|
||||
omit(
|
||||
selectCurrentTab(stateContainer.internalState.getState()).resetDefaultProfileState,
|
||||
'resetId'
|
||||
)
|
||||
).toEqual({
|
||||
expect(omit(stateContainer.getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
@ -613,12 +592,7 @@ describe('useEsqlMode', () => {
|
|||
result: result1,
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
omit(
|
||||
selectCurrentTab(stateContainer.internalState.getState()).resetDefaultProfileState,
|
||||
'resetId'
|
||||
)
|
||||
).toEqual({
|
||||
expect(omit(stateContainer.getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
@ -630,12 +604,7 @@ describe('useEsqlMode', () => {
|
|||
result: result2,
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
omit(
|
||||
selectCurrentTab(stateContainer.internalState.getState()).resetDefaultProfileState,
|
||||
'resetId'
|
||||
)
|
||||
).toEqual({
|
||||
expect(omit(stateContainer.getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
|
|
@ -17,7 +17,11 @@ import { useSavedSearchInitial } from '../state_management/discover_state_provid
|
|||
import type { DiscoverStateContainer } from '../state_management/discover_state';
|
||||
import { getValidViewMode } from '../utils/get_valid_view_mode';
|
||||
import { FetchStatus } from '../../types';
|
||||
import { internalStateActions, useInternalStateDispatch } from '../state_management/redux';
|
||||
import {
|
||||
internalStateActions,
|
||||
useCurrentTabAction,
|
||||
useInternalStateDispatch,
|
||||
} from '../state_management/redux';
|
||||
|
||||
const MAX_NUM_OF_COLUMNS = 50;
|
||||
|
||||
|
@ -32,6 +36,9 @@ export function useEsqlMode({
|
|||
stateContainer: DiscoverStateContainer;
|
||||
dataViews: DataViewsContract;
|
||||
}) {
|
||||
const setResetDefaultProfileState = useCurrentTabAction(
|
||||
internalStateActions.setResetDefaultProfileState
|
||||
);
|
||||
const dispatch = useInternalStateDispatch();
|
||||
const savedSearch = useSavedSearchInitial();
|
||||
const prev = useRef<{
|
||||
|
@ -96,10 +103,12 @@ export function useEsqlMode({
|
|||
// Reset all default profile state when index pattern changes
|
||||
if (indexPatternChanged) {
|
||||
dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
setResetDefaultProfileState({
|
||||
resetDefaultProfileState: {
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -154,10 +163,12 @@ export function useEsqlMode({
|
|||
// due to transformational commands, reset the associated default profile state
|
||||
if (!indexPatternChanged && allColumnsChanged) {
|
||||
dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
setResetDefaultProfileState({
|
||||
resetDefaultProfileState: {
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -192,5 +203,5 @@ export function useEsqlMode({
|
|||
cleanup();
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [dataViews, stateContainer, savedSearch, cleanup, dispatch]);
|
||||
}, [dataViews, stateContainer, savedSearch, cleanup, dispatch, setResetDefaultProfileState]);
|
||||
}
|
||||
|
|
|
@ -22,14 +22,20 @@ import type { DiscoverSavedSearchContainer } from './discover_saved_search_conta
|
|||
import { getSavedSearchContainer } from './discover_saved_search_container';
|
||||
import { getDiscoverGlobalStateContainer } from './discover_global_state_container';
|
||||
import { omit } from 'lodash';
|
||||
import type { InternalStateStore } from './redux';
|
||||
import { createInternalStateStore, createRuntimeStateManager, selectCurrentTab } from './redux';
|
||||
import type { InternalStateStore, TabState } from './redux';
|
||||
import {
|
||||
createInternalStateStore,
|
||||
createRuntimeStateManager,
|
||||
createTabActionInjector,
|
||||
selectTab,
|
||||
} from './redux';
|
||||
import { mockCustomizationContext } from '../../../customizations/__mocks__/customization_context';
|
||||
|
||||
let history: History;
|
||||
let stateStorage: IKbnUrlStateStorage;
|
||||
let internalState: InternalStateStore;
|
||||
let savedSearchState: DiscoverSavedSearchContainer;
|
||||
let getCurrentTab: () => TabState;
|
||||
|
||||
describe('Test discover app state container', () => {
|
||||
beforeEach(async () => {
|
||||
|
@ -51,6 +57,8 @@ describe('Test discover app state container', () => {
|
|||
globalStateContainer: getDiscoverGlobalStateContainer(stateStorage),
|
||||
internalState,
|
||||
});
|
||||
getCurrentTab = () =>
|
||||
selectTab(internalState.getState(), internalState.getState().tabs.allIds[0]);
|
||||
});
|
||||
|
||||
const getStateContainer = () =>
|
||||
|
@ -59,6 +67,7 @@ describe('Test discover app state container', () => {
|
|||
internalState,
|
||||
savedSearchContainer: savedSearchState,
|
||||
services: discoverServiceMock,
|
||||
injectCurrentTab: createTabActionInjector(getCurrentTab().id),
|
||||
});
|
||||
|
||||
test('hasChanged returns whether the current state has changed', async () => {
|
||||
|
@ -274,17 +283,13 @@ describe('Test discover app state container', () => {
|
|||
describe('initAndSync', () => {
|
||||
it('should call setResetDefaultProfileState correctly with no initial state', () => {
|
||||
const state = getStateContainer();
|
||||
expect(
|
||||
omit(selectCurrentTab(internalState.getState()).resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
expect(omit(getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
});
|
||||
state.initAndSync();
|
||||
expect(
|
||||
omit(selectCurrentTab(internalState.getState()).resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
expect(omit(getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
|
@ -295,17 +300,13 @@ describe('Test discover app state container', () => {
|
|||
const stateStorageGetSpy = jest.spyOn(stateStorage, 'get');
|
||||
stateStorageGetSpy.mockReturnValue({ columns: ['test'] });
|
||||
const state = getStateContainer();
|
||||
expect(
|
||||
omit(selectCurrentTab(internalState.getState()).resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
expect(omit(getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
});
|
||||
state.initAndSync();
|
||||
expect(
|
||||
omit(selectCurrentTab(internalState.getState()).resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
expect(omit(getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
|
@ -316,17 +317,13 @@ describe('Test discover app state container', () => {
|
|||
const stateStorageGetSpy = jest.spyOn(stateStorage, 'get');
|
||||
stateStorageGetSpy.mockReturnValue({ rowHeight: 5 });
|
||||
const state = getStateContainer();
|
||||
expect(
|
||||
omit(selectCurrentTab(internalState.getState()).resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
expect(omit(getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
});
|
||||
state.initAndSync();
|
||||
expect(
|
||||
omit(selectCurrentTab(internalState.getState()).resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
expect(omit(getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
breakdownField: true,
|
||||
|
@ -343,17 +340,13 @@ describe('Test discover app state container', () => {
|
|||
managed: false,
|
||||
});
|
||||
const state = getStateContainer();
|
||||
expect(
|
||||
omit(selectCurrentTab(internalState.getState()).resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
expect(omit(getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
});
|
||||
state.initAndSync();
|
||||
expect(
|
||||
omit(selectCurrentTab(internalState.getState()).resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
expect(omit(getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
|
|
@ -41,7 +41,7 @@ import {
|
|||
isEsqlSource,
|
||||
} from '../../../../common/data_sources';
|
||||
import type { DiscoverSavedSearchContainer } from './discover_saved_search_container';
|
||||
import type { InternalStateStore } from './redux';
|
||||
import type { InternalStateStore, TabActionInjector } from './redux';
|
||||
import { internalStateActions } from './redux';
|
||||
import { APP_STATE_URL_KEY } from '../../../../common';
|
||||
|
||||
|
@ -185,11 +185,13 @@ export const getDiscoverAppStateContainer = ({
|
|||
internalState,
|
||||
savedSearchContainer,
|
||||
services,
|
||||
injectCurrentTab,
|
||||
}: {
|
||||
stateStorage: IKbnUrlStateStorage;
|
||||
internalState: InternalStateStore;
|
||||
savedSearchContainer: DiscoverSavedSearchContainer;
|
||||
services: DiscoverServices;
|
||||
injectCurrentTab: TabActionInjector;
|
||||
}): DiscoverAppStateContainer => {
|
||||
let initialState = getInitialState({
|
||||
initialUrlState: getCurrentUrlState(stateStorage, services),
|
||||
|
@ -267,10 +269,12 @@ export const getDiscoverAppStateContainer = ({
|
|||
|
||||
// Only set default state which is not already set in the URL
|
||||
internalState.dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: columns === undefined,
|
||||
rowHeight: rowHeight === undefined,
|
||||
breakdownField: breakdownField === undefined,
|
||||
injectCurrentTab(internalStateActions.setResetDefaultProfileState)({
|
||||
resetDefaultProfileState: {
|
||||
columns: columns === undefined,
|
||||
rowHeight: rowHeight === undefined,
|
||||
breakdownField: breakdownField === undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import type { DataDocuments$ } from './discover_data_state_container';
|
|||
import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock';
|
||||
import { fetchDocuments } from '../data_fetching/fetch_documents';
|
||||
import { omit } from 'lodash';
|
||||
import { internalStateActions, selectCurrentTab } from './redux';
|
||||
import { internalStateActions } from './redux';
|
||||
|
||||
jest.mock('../data_fetching/fetch_documents', () => ({
|
||||
fetchDocuments: jest.fn().mockResolvedValue({ records: [] }),
|
||||
|
@ -178,10 +178,12 @@ describe('test getDataStateContainer', () => {
|
|||
await discoverServiceMock.profilesManager.resolveDataSourceProfile({});
|
||||
stateContainer.actions.setDataView(dataViewMock);
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
stateContainer.injectCurrentTab(internalStateActions.setResetDefaultProfileState)({
|
||||
resetDefaultProfileState: {
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -194,12 +196,7 @@ describe('test getDataStateContainer', () => {
|
|||
await waitFor(() => {
|
||||
expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.COMPLETE);
|
||||
});
|
||||
expect(
|
||||
omit(
|
||||
selectCurrentTab(stateContainer.internalState.getState()).resetDefaultProfileState,
|
||||
'resetId'
|
||||
)
|
||||
).toEqual({
|
||||
expect(omit(stateContainer.getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
@ -218,10 +215,12 @@ describe('test getDataStateContainer', () => {
|
|||
await discoverServiceMock.profilesManager.resolveDataSourceProfile({});
|
||||
stateContainer.actions.setDataView(dataViewMock);
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
stateContainer.injectCurrentTab(internalStateActions.setResetDefaultProfileState)({
|
||||
resetDefaultProfileState: {
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
dataState.data$.totalHits$.next({
|
||||
|
@ -232,12 +231,7 @@ describe('test getDataStateContainer', () => {
|
|||
await waitFor(() => {
|
||||
expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.COMPLETE);
|
||||
});
|
||||
expect(
|
||||
omit(
|
||||
selectCurrentTab(stateContainer.internalState.getState()).resetDefaultProfileState,
|
||||
'resetId'
|
||||
)
|
||||
).toEqual({
|
||||
expect(omit(stateContainer.getCurrentTab().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
|
|
@ -30,8 +30,8 @@ import { fetchAll, fetchMoreDocuments } from '../data_fetching/fetch_all';
|
|||
import { sendResetMsg } from '../hooks/use_saved_search_messages';
|
||||
import { getFetch$ } from '../data_fetching/get_fetch_observable';
|
||||
import { getDefaultProfileState } from './utils/get_default_profile_state';
|
||||
import type { InternalStateStore, RuntimeStateManager } from './redux';
|
||||
import { internalStateActions, selectCurrentTab, selectCurrentTabRuntimeState } from './redux';
|
||||
import type { InternalStateStore, RuntimeStateManager, TabActionInjector, TabState } from './redux';
|
||||
import { internalStateActions, selectTabRuntimeState } from './redux';
|
||||
|
||||
export interface SavedSearchData {
|
||||
main$: DataMain$;
|
||||
|
@ -136,6 +136,8 @@ export function getDataStateContainer({
|
|||
runtimeStateManager,
|
||||
getSavedSearch,
|
||||
setDataView,
|
||||
injectCurrentTab,
|
||||
getCurrentTab,
|
||||
}: {
|
||||
services: DiscoverServices;
|
||||
searchSessionManager: DiscoverSearchSessionManager;
|
||||
|
@ -144,6 +146,8 @@ export function getDataStateContainer({
|
|||
runtimeStateManager: RuntimeStateManager;
|
||||
getSavedSearch: () => SavedSearch;
|
||||
setDataView: (dataView: DataView) => void;
|
||||
injectCurrentTab: TabActionInjector;
|
||||
getCurrentTab: () => TabState;
|
||||
}): DiscoverDataStateContainer {
|
||||
const { data, uiSettings, toastNotifications, profilesManager } = services;
|
||||
const { timefilter } = data.query.timefilter;
|
||||
|
@ -251,9 +255,11 @@ export function getDataStateContainer({
|
|||
}
|
||||
|
||||
internalState.dispatch(
|
||||
internalStateActions.setDataRequestParams({
|
||||
timeRangeAbsolute: timefilter.getAbsoluteTime(),
|
||||
timeRangeRelative: timefilter.getTime(),
|
||||
injectCurrentTab(internalStateActions.setDataRequestParams)({
|
||||
dataRequestParams: {
|
||||
timeRangeAbsolute: timefilter.getAbsoluteTime(),
|
||||
timeRangeRelative: timefilter.getTime(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -263,12 +269,8 @@ export function getDataStateContainer({
|
|||
query: appStateContainer.getState().query,
|
||||
});
|
||||
|
||||
const currentInternalState = internalState.getState();
|
||||
const { resetDefaultProfileState } = selectCurrentTab(currentInternalState);
|
||||
const { currentDataView$ } = selectCurrentTabRuntimeState(
|
||||
currentInternalState,
|
||||
runtimeStateManager
|
||||
);
|
||||
const { id: currentTabId, resetDefaultProfileState } = getCurrentTab();
|
||||
const { currentDataView$ } = selectTabRuntimeState(runtimeStateManager, currentTabId);
|
||||
const dataView = currentDataView$.getValue();
|
||||
const defaultProfileState = dataView
|
||||
? getDefaultProfileState({ profilesManager, resetDefaultProfileState, dataView })
|
||||
|
@ -296,9 +298,9 @@ export function getDataStateContainer({
|
|||
abortController,
|
||||
...commonFetchDeps,
|
||||
},
|
||||
getCurrentTab,
|
||||
async () => {
|
||||
const { resetDefaultProfileState: currentResetDefaultProfileState } =
|
||||
selectCurrentTab(internalState.getState());
|
||||
const { resetDefaultProfileState: currentResetDefaultProfileState } = getCurrentTab();
|
||||
|
||||
if (currentResetDefaultProfileState.resetId !== resetDefaultProfileState.resetId) {
|
||||
return;
|
||||
|
@ -318,10 +320,12 @@ export function getDataStateContainer({
|
|||
// Clear the default profile state flags after the data fetching
|
||||
// is done so refetches don't reset the state again
|
||||
internalState.dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
injectCurrentTab(internalStateActions.setResetDefaultProfileState)({
|
||||
resetDefaultProfileState: {
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
import type { DiscoverStateContainer } from './discover_state';
|
||||
import { createSearchSessionRestorationDataProvider } from './discover_state';
|
||||
import { internalStateActions, selectCurrentTab, selectCurrentTabRuntimeState } from './redux';
|
||||
import { internalStateActions, selectTabRuntimeState } from './redux';
|
||||
import type { History } from 'history';
|
||||
import { createBrowserHistory, createMemoryHistory } from 'history';
|
||||
import { createSearchSourceMock, dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||
|
@ -261,12 +261,13 @@ describe('Discover state', () => {
|
|||
};
|
||||
const { state } = await getState('/', { savedSearch: nextSavedSearch });
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
state.actions.initializeAndSync();
|
||||
|
@ -443,19 +444,19 @@ describe('Discover state', () => {
|
|||
test('setDataView', async () => {
|
||||
const { state, runtimeStateManager } = await getState('');
|
||||
expect(
|
||||
selectCurrentTabRuntimeState(
|
||||
state.internalState.getState(),
|
||||
runtimeStateManager
|
||||
selectTabRuntimeState(
|
||||
runtimeStateManager,
|
||||
state.getCurrentTab().id
|
||||
).currentDataView$.getValue()
|
||||
).toBeUndefined();
|
||||
state.actions.setDataView(dataViewMock);
|
||||
expect(
|
||||
selectCurrentTabRuntimeState(
|
||||
state.internalState.getState(),
|
||||
runtimeStateManager
|
||||
selectTabRuntimeState(
|
||||
runtimeStateManager,
|
||||
state.getCurrentTab().id
|
||||
).currentDataView$.getValue()
|
||||
).toBe(dataViewMock);
|
||||
expect(selectCurrentTab(state.internalState.getState()).dataViewId).toBe(dataViewMock.id);
|
||||
expect(state.getCurrentTab().dataViewId).toBe(dataViewMock.id);
|
||||
});
|
||||
|
||||
test('fetchData', async () => {
|
||||
|
@ -464,12 +465,13 @@ describe('Discover state', () => {
|
|||
await state.internalState.dispatch(internalStateActions.loadDataViewList());
|
||||
expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.LOADING);
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
state.actions.initializeAndSync();
|
||||
|
@ -492,12 +494,13 @@ describe('Discover state', () => {
|
|||
const { state, getCurrentUrl } = await getState('');
|
||||
await state.internalState.dispatch(internalStateActions.loadDataViewList());
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
const newSavedSearch = state.savedSearchState.getState();
|
||||
|
@ -533,12 +536,13 @@ describe('Discover state', () => {
|
|||
test('loadNewSavedSearch given an empty URL using loadSavedSearch', async () => {
|
||||
const { state, getCurrentUrl } = await getState('/');
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
const newSavedSearch = state.savedSearchState.getState();
|
||||
|
@ -558,12 +562,13 @@ describe('Discover state', () => {
|
|||
{ isEmptyUrl: false }
|
||||
);
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
const newSavedSearch = state.savedSearchState.getState();
|
||||
|
@ -583,12 +588,13 @@ describe('Discover state', () => {
|
|||
{ isEmptyUrl: false }
|
||||
);
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
const newSavedSearch = state.savedSearchState.getState();
|
||||
|
@ -619,12 +625,13 @@ describe('Discover state', () => {
|
|||
return Promise.resolve(savedSearchWithDefaults);
|
||||
});
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: 'the-saved-search-id',
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: 'the-saved-search-id',
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
const newSavedSearch = state.savedSearchState.getState();
|
||||
|
@ -645,12 +652,13 @@ describe('Discover state', () => {
|
|||
isEmptyUrl: false,
|
||||
});
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
state.actions.initializeAndSync();
|
||||
|
@ -675,12 +683,13 @@ describe('Discover state', () => {
|
|||
isEmptyUrl: false,
|
||||
});
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
state.actions.initializeAndSync();
|
||||
|
@ -706,12 +715,13 @@ describe('Discover state', () => {
|
|||
isEmptyUrl: false,
|
||||
});
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
state.actions.initializeAndSync();
|
||||
|
@ -737,12 +747,13 @@ describe('Discover state', () => {
|
|||
isEmptyUrl: false,
|
||||
});
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
state.actions.initializeAndSync();
|
||||
|
@ -755,12 +766,13 @@ describe('Discover state', () => {
|
|||
const url = '/#?_a=(hideChart:true,columns:!(message))&_g=()';
|
||||
const { state } = await getState(url, { savedSearch: savedSearchMock });
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(state.savedSearchState.getState().hideChart).toBe(undefined);
|
||||
|
@ -771,12 +783,13 @@ describe('Discover state', () => {
|
|||
const url = '/#?_a=(dataSource:(dataViewId:abc,type:dataView))&_g=()';
|
||||
const { state } = await getState(url, { savedSearch: savedSearchMock, isEmptyUrl: false });
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(state.savedSearchState.getState().searchSource.getField('index')?.id).toBe(
|
||||
|
@ -794,12 +807,13 @@ describe('Discover state', () => {
|
|||
"/#?_a=(dataSource:(dataViewId:abcde,type:dataView),query:(esql:'FROM test'))&_g=()";
|
||||
const { state } = await getState(url, { savedSearch: savedSearchMock, isEmptyUrl: false });
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(state.appState.getState().dataSource).toEqual(createEsqlDataSource());
|
||||
|
@ -810,12 +824,13 @@ describe('Discover state', () => {
|
|||
const url = '/#?_a=(dataSource:(dataViewId:abc,type:dataView))&_g=()';
|
||||
const { state } = await getState(url, { savedSearch: savedSearchMock, isEmptyUrl: false });
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(state.savedSearchState.getState().searchSource.getField('index')?.id).toBe(
|
||||
|
@ -845,12 +860,13 @@ describe('Discover state', () => {
|
|||
return Promise.resolve(savedSearchWithDefaults);
|
||||
});
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(state.savedSearchState.getState().searchSource.getField('index')?.id).toBe(
|
||||
|
@ -872,12 +888,13 @@ describe('Discover state', () => {
|
|||
return Promise.resolve(savedSearchWithDefaults);
|
||||
});
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: 'the-saved-search-id-with-timefield',
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: 'the-saved-search-id-with-timefield',
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(state.savedSearchState.getState().searchSource.getField('index')?.id).toBe(
|
||||
|
@ -899,13 +916,16 @@ describe('Discover state', () => {
|
|||
return Promise.resolve(savedSearchWithDefaults);
|
||||
});
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: {
|
||||
dataSource: createDataViewDataSource({ dataViewId: 'index-pattern-with-timefield-id' }),
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: {
|
||||
dataSource: createDataViewDataSource({
|
||||
dataViewId: 'index-pattern-with-timefield-id',
|
||||
}),
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -929,12 +949,13 @@ describe('Discover state', () => {
|
|||
isPersisted: () => false,
|
||||
}));
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: dataViewSpecMock,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: dataViewSpecMock,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(state.savedSearchState.getInitial$().getValue().id).toEqual(undefined);
|
||||
|
@ -952,12 +973,13 @@ describe('Discover state', () => {
|
|||
test('loadSavedSearch resetting query & filters of data service', async () => {
|
||||
const { state } = await getState('/', { savedSearch: savedSearchMock });
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(discoverServiceMock.data.query.queryString.clearQuery).toHaveBeenCalled();
|
||||
|
@ -972,12 +994,13 @@ describe('Discover state', () => {
|
|||
savedSearchWithQueryAndFilters.searchSource.setField('filter', filters);
|
||||
const { state } = await getState('/', { savedSearch: savedSearchWithQueryAndFilters });
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(discoverServiceMock.data.query.queryString.setQuery).toHaveBeenCalledWith(query);
|
||||
|
@ -991,12 +1014,13 @@ describe('Discover state', () => {
|
|||
const adHocDataViewId = savedSearchAdHoc.searchSource.getField('index')!.id;
|
||||
const { state } = await getState('/', { savedSearch: savedSearchAdHocCopy });
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchAdHoc.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchAdHoc.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(state.appState.getState().dataSource).toEqual(
|
||||
|
@ -1014,12 +1038,13 @@ describe('Discover state', () => {
|
|||
isEmptyUrl: false,
|
||||
});
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMockWithESQL.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMockWithESQL.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
const nextSavedSearch = state.savedSearchState.getState();
|
||||
|
@ -1059,12 +1084,13 @@ describe('Discover state', () => {
|
|||
const { actions, savedSearchState, dataState } = state;
|
||||
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
actions.initializeAndSync();
|
||||
|
@ -1094,20 +1120,19 @@ describe('Discover state', () => {
|
|||
test('onDataViewCreated - persisted data view', async () => {
|
||||
const { state } = await getState('/', { savedSearch: savedSearchMock });
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
state.actions.initializeAndSync();
|
||||
await state.actions.onDataViewCreated(dataViewComplexMock);
|
||||
await waitFor(() => {
|
||||
expect(selectCurrentTab(state.internalState.getState()).dataViewId).toBe(
|
||||
dataViewComplexMock.id
|
||||
);
|
||||
expect(state.getCurrentTab().dataViewId).toBe(dataViewComplexMock.id);
|
||||
});
|
||||
expect(state.appState.getState().dataSource).toEqual(
|
||||
createDataViewDataSource({ dataViewId: dataViewComplexMock.id! })
|
||||
|
@ -1121,12 +1146,13 @@ describe('Discover state', () => {
|
|||
test('onDataViewCreated - ad-hoc data view', async () => {
|
||||
const { state } = await getState('/', { savedSearch: savedSearchMock });
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
state.actions.initializeAndSync();
|
||||
|
@ -1137,7 +1163,7 @@ describe('Discover state', () => {
|
|||
);
|
||||
await state.actions.onDataViewCreated(dataViewAdHoc);
|
||||
await waitFor(() => {
|
||||
expect(selectCurrentTab(state.internalState.getState()).dataViewId).toBe(dataViewAdHoc.id);
|
||||
expect(state.getCurrentTab().dataViewId).toBe(dataViewAdHoc.id);
|
||||
});
|
||||
expect(state.appState.getState().dataSource).toEqual(
|
||||
createDataViewDataSource({ dataViewId: dataViewAdHoc.id! })
|
||||
|
@ -1151,22 +1177,21 @@ describe('Discover state', () => {
|
|||
test('onDataViewEdited - persisted data view', async () => {
|
||||
const { state } = await getState('/', { savedSearch: savedSearchMock });
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
const selectedDataViewId = selectCurrentTab(state.internalState.getState()).dataViewId;
|
||||
const selectedDataViewId = state.getCurrentTab().dataViewId;
|
||||
expect(selectedDataViewId).toBe(dataViewMock.id);
|
||||
state.actions.initializeAndSync();
|
||||
await state.actions.onDataViewEdited(dataViewMock);
|
||||
await waitFor(() => {
|
||||
expect(selectCurrentTab(state.internalState.getState()).dataViewId).toBe(
|
||||
selectedDataViewId
|
||||
);
|
||||
expect(state.getCurrentTab().dataViewId).toBe(selectedDataViewId);
|
||||
});
|
||||
state.actions.stopSyncing();
|
||||
});
|
||||
|
@ -1178,7 +1203,7 @@ describe('Discover state', () => {
|
|||
const previousId = dataViewAdHoc.id;
|
||||
await state.actions.onDataViewEdited(dataViewAdHoc);
|
||||
await waitFor(() => {
|
||||
expect(selectCurrentTab(state.internalState.getState()).dataViewId).not.toBe(previousId);
|
||||
expect(state.getCurrentTab().dataViewId).not.toBe(previousId);
|
||||
});
|
||||
state.actions.stopSyncing();
|
||||
});
|
||||
|
@ -1187,12 +1212,13 @@ describe('Discover state', () => {
|
|||
const { state } = await getState('/', { savedSearch: savedSearchMock });
|
||||
state.actions.initializeAndSync();
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
state.savedSearchState.update({ nextState: { hideChart: true } });
|
||||
|
@ -1208,23 +1234,25 @@ describe('Discover state', () => {
|
|||
{ savedSearch: savedSearchMock, isEmptyUrl: false }
|
||||
);
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(state.appState.get().filters).toHaveLength(1);
|
||||
history.push('/');
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: undefined,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(state.appState.get().filters).toBeUndefined();
|
||||
|
@ -1233,12 +1261,13 @@ describe('Discover state', () => {
|
|||
test('onCreateDefaultAdHocDataView', async () => {
|
||||
const { state } = await getState('/', { savedSearch: savedSearchMock });
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
state.actions.initializeAndSync();
|
||||
|
@ -1254,12 +1283,13 @@ describe('Discover state', () => {
|
|||
const { state, getCurrentUrl } = await getState('/', { savedSearch: savedSearchMock });
|
||||
// Load a given persisted saved search
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
state.actions.initializeAndSync();
|
||||
|
@ -1267,7 +1297,7 @@ describe('Discover state', () => {
|
|||
const initialUrlState =
|
||||
'/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),dataSource:(dataViewId:the-data-view-id,type:dataView),interval:auto,sort:!())';
|
||||
expect(getCurrentUrl()).toBe(initialUrlState);
|
||||
expect(selectCurrentTab(state.internalState.getState()).dataViewId).toBe(dataViewMock.id!);
|
||||
expect(state.getCurrentTab().dataViewId).toBe(dataViewMock.id!);
|
||||
|
||||
// Change the data view, this should change the URL and trigger a fetch
|
||||
await state.actions.onChangeDataView(dataViewComplexMock.id!);
|
||||
|
@ -1278,9 +1308,7 @@ describe('Discover state', () => {
|
|||
await waitFor(() => {
|
||||
expect(state.dataState.fetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(selectCurrentTab(state.internalState.getState()).dataViewId).toBe(
|
||||
dataViewComplexMock.id!
|
||||
);
|
||||
expect(state.getCurrentTab().dataViewId).toBe(dataViewComplexMock.id!);
|
||||
|
||||
// Undo all changes to the saved search, this should trigger a fetch, again
|
||||
await state.actions.undoSavedSearchChanges();
|
||||
|
@ -1289,7 +1317,7 @@ describe('Discover state', () => {
|
|||
await waitFor(() => {
|
||||
expect(state.dataState.fetch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
expect(selectCurrentTab(state.internalState.getState()).dataViewId).toBe(dataViewMock.id!);
|
||||
expect(state.getCurrentTab().dataViewId).toBe(dataViewMock.id!);
|
||||
|
||||
state.actions.stopSyncing();
|
||||
});
|
||||
|
@ -1308,12 +1336,13 @@ describe('Discover state', () => {
|
|||
discoverServiceMock.data.query.timefilter.timefilter.setTime = setTime;
|
||||
discoverServiceMock.data.query.timefilter.timefilter.setRefreshInterval = setRefreshInterval;
|
||||
await state.internalState.dispatch(
|
||||
internalStateActions.initializeSession({
|
||||
stateContainer: state,
|
||||
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
state.injectCurrentTab(internalStateActions.initializeSession)({
|
||||
initializeSessionParams: {
|
||||
stateContainer: state,
|
||||
discoverSessionId: savedSearchMock.id,
|
||||
dataViewSpec: undefined,
|
||||
defaultUrlState: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(setTime).toHaveBeenCalledTimes(1);
|
||||
|
|
|
@ -43,12 +43,21 @@ import {
|
|||
DataSourceType,
|
||||
isDataSourceType,
|
||||
} from '../../../../common/data_sources';
|
||||
import type { InternalStateStore, RuntimeStateManager } from './redux';
|
||||
import { internalStateActions, selectCurrentTabRuntimeState } from './redux';
|
||||
import type { InternalStateStore, RuntimeStateManager, TabActionInjector, TabState } from './redux';
|
||||
import {
|
||||
createTabActionInjector,
|
||||
internalStateActions,
|
||||
selectTab,
|
||||
selectTabRuntimeState,
|
||||
} from './redux';
|
||||
import type { DiscoverSavedSearchContainer } from './discover_saved_search_container';
|
||||
import { getSavedSearchContainer } from './discover_saved_search_container';
|
||||
|
||||
export interface DiscoverStateContainerParams {
|
||||
/**
|
||||
* The ID of the tab associated with this state container
|
||||
*/
|
||||
tabId: string;
|
||||
/**
|
||||
* The current savedSearch
|
||||
*/
|
||||
|
@ -111,6 +120,14 @@ export interface DiscoverStateContainer {
|
|||
* Internal shared state that's used at several places in the UI
|
||||
*/
|
||||
internalState: InternalStateStore;
|
||||
/**
|
||||
* Injects the current tab into a given internalState action
|
||||
*/
|
||||
injectCurrentTab: TabActionInjector;
|
||||
/**
|
||||
* Gets the state of the current tab
|
||||
*/
|
||||
getCurrentTab: () => TabState;
|
||||
/**
|
||||
* State manager for runtime state that can't be stored in Redux
|
||||
*/
|
||||
|
@ -220,6 +237,7 @@ export interface DiscoverStateContainer {
|
|||
* Used to sync URL with UI state
|
||||
*/
|
||||
export function getDiscoverStateContainer({
|
||||
tabId,
|
||||
services,
|
||||
customizationContext,
|
||||
stateStorageContainer,
|
||||
|
@ -228,6 +246,8 @@ export function getDiscoverStateContainer({
|
|||
}: DiscoverStateContainerParams): DiscoverStateContainer {
|
||||
const storeInSessionStorage = services.uiSettings.get('state:storeInSessionStorage');
|
||||
const toasts = services.core.notifications.toasts;
|
||||
const injectCurrentTab = createTabActionInjector(tabId);
|
||||
const getCurrentTab = () => selectTab(internalState.getState(), tabId);
|
||||
|
||||
/**
|
||||
* state storage for state in the URL
|
||||
|
@ -271,6 +291,7 @@ export function getDiscoverStateContainer({
|
|||
internalState,
|
||||
savedSearchContainer,
|
||||
services,
|
||||
injectCurrentTab,
|
||||
});
|
||||
|
||||
const pauseAutoRefreshInterval = async (dataView: DataView) => {
|
||||
|
@ -286,7 +307,7 @@ export function getDiscoverStateContainer({
|
|||
};
|
||||
|
||||
const setDataView = (dataView: DataView) => {
|
||||
internalState.dispatch(internalStateActions.setDataView(dataView));
|
||||
internalState.dispatch(injectCurrentTab(internalStateActions.setDataView)({ dataView }));
|
||||
pauseAutoRefreshInterval(dataView);
|
||||
savedSearchContainer.getState().searchSource.setField('index', dataView);
|
||||
};
|
||||
|
@ -299,6 +320,8 @@ export function getDiscoverStateContainer({
|
|||
runtimeStateManager,
|
||||
getSavedSearch: savedSearchContainer.getState,
|
||||
setDataView,
|
||||
injectCurrentTab,
|
||||
getCurrentTab,
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -306,10 +329,7 @@ export function getDiscoverStateContainer({
|
|||
* This is to prevent duplicate ids messing with our system
|
||||
*/
|
||||
const updateAdHocDataViewId = async () => {
|
||||
const { currentDataView$ } = selectCurrentTabRuntimeState(
|
||||
internalState.getState(),
|
||||
runtimeStateManager
|
||||
);
|
||||
const { currentDataView$ } = selectTabRuntimeState(runtimeStateManager, tabId);
|
||||
const prevDataView = currentDataView$.getValue();
|
||||
if (!prevDataView || prevDataView.isPersisted()) return;
|
||||
|
||||
|
@ -457,10 +477,7 @@ export function getDiscoverStateContainer({
|
|||
|
||||
// updates saved search when query or filters change, triggers data fetching
|
||||
const filterUnsubscribe = merge(services.filterManager.getFetches$()).subscribe(() => {
|
||||
const { currentDataView$ } = selectCurrentTabRuntimeState(
|
||||
internalState.getState(),
|
||||
runtimeStateManager
|
||||
);
|
||||
const { currentDataView$ } = selectTabRuntimeState(runtimeStateManager, tabId);
|
||||
savedSearchContainer.update({
|
||||
nextDataView: currentDataView$.getValue(),
|
||||
nextState: appStateContainer.getState(),
|
||||
|
@ -531,6 +548,8 @@ export function getDiscoverStateContainer({
|
|||
internalState,
|
||||
runtimeStateManager,
|
||||
appState: appStateContainer,
|
||||
injectCurrentTab,
|
||||
getCurrentTab,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -560,7 +579,7 @@ export function getDiscoverStateContainer({
|
|||
});
|
||||
}
|
||||
|
||||
internalState.dispatch(internalStateActions.resetOnSavedSearchChange());
|
||||
internalState.dispatch(injectCurrentTab(internalStateActions.resetOnSavedSearchChange)());
|
||||
await appStateContainer.replaceUrlState(newAppState);
|
||||
return nextSavedSearch;
|
||||
};
|
||||
|
@ -592,6 +611,8 @@ export function getDiscoverStateContainer({
|
|||
globalState: globalStateContainer,
|
||||
appState: appStateContainer,
|
||||
internalState,
|
||||
injectCurrentTab,
|
||||
getCurrentTab,
|
||||
runtimeStateManager,
|
||||
dataState: dataStateContainer,
|
||||
savedSearchState: savedSearchContainer,
|
||||
|
|
|
@ -9,20 +9,31 @@
|
|||
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { differenceBy } from 'lodash';
|
||||
import { internalStateSlice, type InternalStateThunkActionCreator } from '../internal_state';
|
||||
import {
|
||||
internalStateSlice,
|
||||
type TabActionPayload,
|
||||
type InternalStateThunkActionCreator,
|
||||
} from '../internal_state';
|
||||
import { selectTabRuntimeState } from '../runtime_state';
|
||||
import { createInternalStateAsyncThunk } from '../utils';
|
||||
import { selectCurrentTabRuntimeState } from '../runtime_state';
|
||||
|
||||
export const loadDataViewList = createInternalStateAsyncThunk(
|
||||
'internalState/loadDataViewList',
|
||||
async (_, { extra: { services } }) => services.dataViews.getIdsWithTitle(true)
|
||||
);
|
||||
|
||||
export const setDataView: InternalStateThunkActionCreator<[DataView]> =
|
||||
(dataView) =>
|
||||
(dispatch, getState, { runtimeStateManager }) => {
|
||||
dispatch(internalStateSlice.actions.setDataViewId(dataView.id));
|
||||
const { currentDataView$ } = selectCurrentTabRuntimeState(getState(), runtimeStateManager);
|
||||
export const setDataView: InternalStateThunkActionCreator<
|
||||
[TabActionPayload<{ dataView: DataView }>]
|
||||
> =
|
||||
({ tabId, dataView }) =>
|
||||
(dispatch, _, { runtimeStateManager }) => {
|
||||
dispatch(
|
||||
internalStateSlice.actions.setDataViewId({
|
||||
tabId,
|
||||
dataViewId: dataView.id,
|
||||
})
|
||||
);
|
||||
const { currentDataView$ } = selectTabRuntimeState(runtimeStateManager, tabId);
|
||||
currentDataView$.next(dataView);
|
||||
};
|
||||
|
||||
|
|
|
@ -12,7 +12,11 @@ import { isOfAggregateQueryType } from '@kbn/es-query';
|
|||
import { getSavedSearchFullPathUrl } from '@kbn/saved-search-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { cloneDeep, isEqual } from 'lodash';
|
||||
import { internalStateSlice, type InternalStateThunkActionCreator } from '../internal_state';
|
||||
import {
|
||||
internalStateSlice,
|
||||
type TabActionPayload,
|
||||
type InternalStateThunkActionCreator,
|
||||
} from '../internal_state';
|
||||
import {
|
||||
getInitialState,
|
||||
type AppStateUrl,
|
||||
|
@ -30,7 +34,7 @@ import { isRefreshIntervalValid, isTimeRangeValid } from '../../../../../utils/v
|
|||
import { getValidFilters } from '../../../../../utils/get_valid_filters';
|
||||
import { updateSavedSearch } from '../../utils/update_saved_search';
|
||||
import { APP_STATE_URL_KEY } from '../../../../../../common';
|
||||
import { selectCurrentTabRuntimeState } from '../runtime_state';
|
||||
import { selectTabRuntimeState } from '../runtime_state';
|
||||
|
||||
export interface InitializeSessionParams {
|
||||
stateContainer: DiscoverStateContainer;
|
||||
|
@ -40,16 +44,19 @@ export interface InitializeSessionParams {
|
|||
}
|
||||
|
||||
export const initializeSession: InternalStateThunkActionCreator<
|
||||
[InitializeSessionParams],
|
||||
[TabActionPayload<{ initializeSessionParams: InitializeSessionParams }>],
|
||||
Promise<{ showNoDataPage: boolean }>
|
||||
> =
|
||||
({ stateContainer, discoverSessionId, dataViewSpec, defaultUrlState }) =>
|
||||
({
|
||||
tabId,
|
||||
initializeSessionParams: { stateContainer, discoverSessionId, dataViewSpec, defaultUrlState },
|
||||
}) =>
|
||||
async (
|
||||
dispatch,
|
||||
getState,
|
||||
{ services, customizationContext, runtimeStateManager, urlStateStorage }
|
||||
) => {
|
||||
dispatch(internalStateSlice.actions.resetOnSavedSearchChange());
|
||||
dispatch(internalStateSlice.actions.resetOnSavedSearchChange({ tabId }));
|
||||
|
||||
/**
|
||||
* "No data" checks
|
||||
|
@ -109,7 +116,7 @@ export const initializeSession: InternalStateThunkActionCreator<
|
|||
let dataView: DataView;
|
||||
|
||||
if (isOfAggregateQueryType(initialQuery)) {
|
||||
const { currentDataView$ } = selectCurrentTabRuntimeState(getState(), runtimeStateManager);
|
||||
const { currentDataView$ } = selectTabRuntimeState(runtimeStateManager, tabId);
|
||||
|
||||
// Regardless of what was requested, we always use ad hoc data views for ES|QL
|
||||
dataView = await getEsqlDataView(
|
||||
|
@ -134,7 +141,7 @@ export const initializeSession: InternalStateThunkActionCreator<
|
|||
dataView = result.dataView;
|
||||
}
|
||||
|
||||
dispatch(setDataView(dataView));
|
||||
dispatch(setDataView({ tabId, dataView }));
|
||||
|
||||
if (!dataView.isPersisted()) {
|
||||
dispatch(appendAdHocDataViews(dataView));
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import type { TabbedContentState } from '@kbn/unified-tabs/src/components/tabbed_content/tabbed_content';
|
||||
import { differenceBy } from 'lodash';
|
||||
import type { TabState } from '../types';
|
||||
import { selectAllTabs, selectCurrentTab } from '../selectors';
|
||||
import { selectAllTabs, selectTab } from '../selectors';
|
||||
import {
|
||||
defaultTabState,
|
||||
internalStateSlice,
|
||||
|
@ -39,15 +39,16 @@ export const setTabs: InternalStateThunkActionCreator<
|
|||
};
|
||||
|
||||
export interface UpdateTabsParams {
|
||||
currentTabId: string;
|
||||
updateState: TabbedContentState;
|
||||
stopSyncing?: () => void;
|
||||
}
|
||||
|
||||
export const updateTabs: InternalStateThunkActionCreator<[UpdateTabsParams], Promise<void>> =
|
||||
({ updateState: { items, selectedItem }, stopSyncing }) =>
|
||||
({ currentTabId, updateState: { items, selectedItem }, stopSyncing }) =>
|
||||
async (dispatch, getState, { urlStateStorage }) => {
|
||||
const currentState = getState();
|
||||
const currentTab = selectCurrentTab(currentState);
|
||||
const currentTab = selectTab(currentState, currentTabId);
|
||||
let updatedTabs = items.map<TabState>((item) => {
|
||||
const existingTab = currentState.tabs.byId[item.id];
|
||||
return existingTab ? { ...existingTab, ...item } : { ...defaultTabState, ...item };
|
||||
|
@ -77,10 +78,5 @@ export const updateTabs: InternalStateThunkActionCreator<[UpdateTabsParams], Pro
|
|||
}
|
||||
}
|
||||
|
||||
dispatch(
|
||||
setTabs({
|
||||
allTabs: updatedTabs,
|
||||
selectedTabId: selectedItem?.id ?? currentTab.id,
|
||||
})
|
||||
);
|
||||
dispatch(setTabs({ allTabs: updatedTabs }));
|
||||
};
|
||||
|
|
|
@ -18,8 +18,13 @@ import {
|
|||
import React, { type PropsWithChildren, useMemo, createContext } from 'react';
|
||||
import { useAdHocDataViews } from './runtime_state';
|
||||
import type { DiscoverInternalState, TabState } from './types';
|
||||
import { type InternalStateDispatch, type InternalStateStore } from './internal_state';
|
||||
import { selectCurrentTab } from './selectors';
|
||||
import {
|
||||
type TabActionPayload,
|
||||
type InternalStateDispatch,
|
||||
type InternalStateStore,
|
||||
} from './internal_state';
|
||||
import { selectTab } from './selectors';
|
||||
import { type TabActionInjector, createTabActionInjector } from './utils';
|
||||
|
||||
const internalStateContext = createContext<ReactReduxContextValue>(
|
||||
// Recommended approach for versions of Redux prior to v9:
|
||||
|
@ -42,8 +47,46 @@ export const useInternalStateDispatch: () => InternalStateDispatch =
|
|||
export const useInternalStateSelector: TypedUseSelectorHook<DiscoverInternalState> =
|
||||
createSelectorHook(internalStateContext);
|
||||
|
||||
export const useCurrentTabSelector: TypedUseSelectorHook<TabState> = (selector) =>
|
||||
selector(useInternalStateSelector(selectCurrentTab));
|
||||
interface CurrentTabContextValue {
|
||||
currentTabId: string;
|
||||
injectCurrentTab: TabActionInjector;
|
||||
}
|
||||
|
||||
const currentTabContext = createContext<CurrentTabContextValue | undefined>(undefined);
|
||||
|
||||
export const CurrentTabProvider = ({
|
||||
currentTabId,
|
||||
children,
|
||||
}: PropsWithChildren<{ currentTabId: string }>) => {
|
||||
const contextValue = useMemo<CurrentTabContextValue>(
|
||||
() => ({ currentTabId, injectCurrentTab: createTabActionInjector(currentTabId) }),
|
||||
[currentTabId]
|
||||
);
|
||||
|
||||
return <currentTabContext.Provider value={contextValue}>{children}</currentTabContext.Provider>;
|
||||
};
|
||||
|
||||
export const useCurrentTabContext = () => {
|
||||
const context = React.useContext(currentTabContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useCurrentTabContext must be used within a CurrentTabProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
export const useCurrentTabSelector: TypedUseSelectorHook<TabState> = (selector) => {
|
||||
const { currentTabId } = useCurrentTabContext();
|
||||
return useInternalStateSelector((state) => selector(selectTab(state, currentTabId)));
|
||||
};
|
||||
|
||||
export const useCurrentTabAction = <TPayload extends TabActionPayload, TReturn>(
|
||||
actionCreator: (params: TPayload) => TReturn
|
||||
) => {
|
||||
const { injectCurrentTab } = useCurrentTabContext();
|
||||
return useMemo(() => injectCurrentTab(actionCreator), [actionCreator, injectCurrentTab]);
|
||||
};
|
||||
|
||||
export const useDataViewsForPicker = () => {
|
||||
const originalAdHocDataViews = useAdHocDataViews();
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
|
||||
export type { DiscoverInternalState, TabState, InternalStateDataRequestParams } from './types';
|
||||
|
||||
export { type InternalStateStore, createInternalStateStore, createTabItem } from './internal_state';
|
||||
export { type InternalStateStore, createInternalStateStore } from './internal_state';
|
||||
|
||||
export const internalStateActions = {
|
||||
...omit(
|
||||
|
@ -47,18 +47,23 @@ export {
|
|||
InternalStateProvider,
|
||||
useInternalStateDispatch,
|
||||
useInternalStateSelector,
|
||||
CurrentTabProvider,
|
||||
useCurrentTabSelector,
|
||||
useCurrentTabAction,
|
||||
useDataViewsForPicker,
|
||||
} from './hooks';
|
||||
|
||||
export { selectAllTabs, selectCurrentTab } from './selectors';
|
||||
export { selectAllTabs, selectTab } from './selectors';
|
||||
|
||||
export {
|
||||
type RuntimeStateManager,
|
||||
createRuntimeStateManager,
|
||||
useRuntimeState,
|
||||
selectCurrentTabRuntimeState,
|
||||
selectTabRuntimeState,
|
||||
useCurrentTabRuntimeState,
|
||||
RuntimeStateProvider,
|
||||
useCurrentDataView,
|
||||
useAdHocDataViews,
|
||||
} from './runtime_state';
|
||||
|
||||
export { type TabActionInjector, createTabActionInjector, createTabItem } from './utils';
|
||||
|
|
|
@ -12,8 +12,8 @@ import {
|
|||
createInternalStateStore,
|
||||
createRuntimeStateManager,
|
||||
internalStateActions,
|
||||
selectCurrentTab,
|
||||
selectCurrentTabRuntimeState,
|
||||
selectTab,
|
||||
selectTabRuntimeState,
|
||||
} from '.';
|
||||
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
|
||||
import { mockCustomizationContext } from '../../../../customizations/__mocks__/customization_context';
|
||||
|
@ -28,14 +28,15 @@ describe('InternalStateStore', () => {
|
|||
runtimeStateManager,
|
||||
urlStateStorage: createKbnUrlStateStorage(),
|
||||
});
|
||||
expect(selectCurrentTab(store.getState()).dataViewId).toBeUndefined();
|
||||
const tabId = store.getState().tabs.allIds[0];
|
||||
expect(selectTab(store.getState(), tabId).dataViewId).toBeUndefined();
|
||||
expect(
|
||||
selectCurrentTabRuntimeState(store.getState(), runtimeStateManager).currentDataView$.value
|
||||
selectTabRuntimeState(runtimeStateManager, tabId).currentDataView$.value
|
||||
).toBeUndefined();
|
||||
store.dispatch(internalStateActions.setDataView(dataViewMock));
|
||||
expect(selectCurrentTab(store.getState()).dataViewId).toBe(dataViewMock.id);
|
||||
expect(
|
||||
selectCurrentTabRuntimeState(store.getState(), runtimeStateManager).currentDataView$.value
|
||||
).toBe(dataViewMock);
|
||||
store.dispatch(internalStateActions.setDataView({ tabId, dataView: dataViewMock }));
|
||||
expect(selectTab(store.getState(), tabId).dataViewId).toBe(dataViewMock.id);
|
||||
expect(selectTabRuntimeState(runtimeStateManager, tabId).currentDataView$.value).toBe(
|
||||
dataViewMock
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
type ThunkDispatch,
|
||||
} from '@reduxjs/toolkit';
|
||||
import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { TabItem } from '@kbn/unified-tabs';
|
||||
import type { DiscoverCustomizationContext } from '../../../../customizations';
|
||||
import type { DiscoverServices } from '../../../../build_services';
|
||||
|
@ -29,12 +28,8 @@ import {
|
|||
type TabState,
|
||||
} from './types';
|
||||
import { loadDataViewList, setTabs } from './actions';
|
||||
import { selectAllTabs, selectCurrentTab } from './selectors';
|
||||
|
||||
const DEFAULT_TAB_LABEL = i18n.translate('discover.defaultTabLabel', {
|
||||
defaultMessage: 'Untitled session',
|
||||
});
|
||||
const DEFAULT_TAB_REGEX = new RegExp(`^${DEFAULT_TAB_LABEL}( \\d+)?$`);
|
||||
import { selectAllTabs } from './selectors';
|
||||
import { createTabItem } from './utils';
|
||||
|
||||
export const defaultTabState: Omit<TabState, keyof TabItem> = {
|
||||
dataViewId: undefined,
|
||||
|
@ -67,23 +62,22 @@ const initialState: DiscoverInternalState = {
|
|||
savedDataViews: [],
|
||||
expandedDoc: undefined,
|
||||
isESQLToDataViewTransitionModalVisible: false,
|
||||
tabs: { byId: {}, allIds: [], currentId: '' },
|
||||
tabs: { byId: {}, allIds: [] },
|
||||
};
|
||||
|
||||
export const createTabItem = (allTabs: TabState[]): TabItem => {
|
||||
const id = uuidv4();
|
||||
const untitledTabCount = allTabs.filter((tab) => DEFAULT_TAB_REGEX.test(tab.label.trim())).length;
|
||||
const label =
|
||||
untitledTabCount > 0 ? `${DEFAULT_TAB_LABEL} ${untitledTabCount}` : DEFAULT_TAB_LABEL;
|
||||
export type TabActionPayload<T extends { [key: string]: unknown } = {}> = { tabId: string } & T;
|
||||
|
||||
return { id, label };
|
||||
};
|
||||
type TabAction<T extends { [key: string]: unknown } = {}> = PayloadAction<TabActionPayload<T>>;
|
||||
|
||||
const withCurrentTab = (state: DiscoverInternalState, fn: (tab: TabState) => void) => {
|
||||
const currentTab = selectCurrentTab(state);
|
||||
const withTab = <TAction extends TabAction>(
|
||||
state: DiscoverInternalState,
|
||||
action: TAction,
|
||||
fn: (tab: TabState) => void
|
||||
) => {
|
||||
const tab = state.tabs.byId[action.payload.tabId];
|
||||
|
||||
if (currentTab) {
|
||||
fn(currentTab);
|
||||
if (tab) {
|
||||
fn(tab);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -98,7 +92,7 @@ export const internalStateSlice = createSlice({
|
|||
state.initializationState = action.payload;
|
||||
},
|
||||
|
||||
setTabs: (state, action: PayloadAction<{ allTabs: TabState[]; selectedTabId: string }>) => {
|
||||
setTabs: (state, action: PayloadAction<{ allTabs: TabState[] }>) => {
|
||||
state.tabs.byId = action.payload.allTabs.reduce<Record<string, TabState>>(
|
||||
(acc, tab) => ({
|
||||
...acc,
|
||||
|
@ -107,21 +101,20 @@ export const internalStateSlice = createSlice({
|
|||
{}
|
||||
);
|
||||
state.tabs.allIds = action.payload.allTabs.map((tab) => tab.id);
|
||||
state.tabs.currentId = action.payload.selectedTabId;
|
||||
},
|
||||
|
||||
setDataViewId: (state, action: PayloadAction<string | undefined>) =>
|
||||
withCurrentTab(state, (tab) => {
|
||||
if (action.payload !== tab.dataViewId) {
|
||||
setDataViewId: (state, action: TabAction<{ dataViewId: string | undefined }>) =>
|
||||
withTab(state, action, (tab) => {
|
||||
if (action.payload.dataViewId !== tab.dataViewId) {
|
||||
state.expandedDoc = undefined;
|
||||
}
|
||||
|
||||
tab.dataViewId = action.payload;
|
||||
tab.dataViewId = action.payload.dataViewId;
|
||||
}),
|
||||
|
||||
setIsDataViewLoading: (state, action: PayloadAction<boolean>) =>
|
||||
withCurrentTab(state, (tab) => {
|
||||
tab.isDataViewLoading = action.payload;
|
||||
setIsDataViewLoading: (state, action: TabAction<{ isDataViewLoading: boolean }>) =>
|
||||
withTab(state, action, (tab) => {
|
||||
tab.isDataViewLoading = action.payload.isDataViewLoading;
|
||||
}),
|
||||
|
||||
setDefaultProfileAdHocDataViewIds: (state, action: PayloadAction<string[]>) => {
|
||||
|
@ -132,17 +125,23 @@ export const internalStateSlice = createSlice({
|
|||
state.expandedDoc = action.payload;
|
||||
},
|
||||
|
||||
setDataRequestParams: (state, action: PayloadAction<InternalStateDataRequestParams>) =>
|
||||
withCurrentTab(state, (tab) => {
|
||||
tab.dataRequestParams = action.payload;
|
||||
setDataRequestParams: (
|
||||
state,
|
||||
action: TabAction<{ dataRequestParams: InternalStateDataRequestParams }>
|
||||
) =>
|
||||
withTab(state, action, (tab) => {
|
||||
tab.dataRequestParams = action.payload.dataRequestParams;
|
||||
}),
|
||||
|
||||
setOverriddenVisContextAfterInvalidation: (
|
||||
state,
|
||||
action: PayloadAction<TabState['overriddenVisContextAfterInvalidation']>
|
||||
action: TabAction<{
|
||||
overriddenVisContextAfterInvalidation: TabState['overriddenVisContextAfterInvalidation'];
|
||||
}>
|
||||
) =>
|
||||
withCurrentTab(state, (tab) => {
|
||||
tab.overriddenVisContextAfterInvalidation = action.payload;
|
||||
withTab(state, action, (tab) => {
|
||||
tab.overriddenVisContextAfterInvalidation =
|
||||
action.payload.overriddenVisContextAfterInvalidation;
|
||||
}),
|
||||
|
||||
setIsESQLToDataViewTransitionModalVisible: (state, action: PayloadAction<boolean>) => {
|
||||
|
@ -151,26 +150,32 @@ export const internalStateSlice = createSlice({
|
|||
|
||||
setResetDefaultProfileState: {
|
||||
prepare: (
|
||||
resetDefaultProfileState: Omit<TabState['resetDefaultProfileState'], 'resetId'>
|
||||
payload: TabActionPayload<{
|
||||
resetDefaultProfileState: Omit<TabState['resetDefaultProfileState'], 'resetId'>;
|
||||
}>
|
||||
) => ({
|
||||
payload: {
|
||||
...resetDefaultProfileState,
|
||||
resetId: uuidv4(),
|
||||
...payload,
|
||||
resetDefaultProfileState: {
|
||||
...payload.resetDefaultProfileState,
|
||||
resetId: uuidv4(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
reducer: (state, action: PayloadAction<TabState['resetDefaultProfileState']>) =>
|
||||
withCurrentTab(state, (tab) => {
|
||||
tab.resetDefaultProfileState = action.payload;
|
||||
reducer: (
|
||||
state,
|
||||
action: TabAction<{ resetDefaultProfileState: TabState['resetDefaultProfileState'] }>
|
||||
) =>
|
||||
withTab(state, action, (tab) => {
|
||||
tab.resetDefaultProfileState = action.payload.resetDefaultProfileState;
|
||||
}),
|
||||
},
|
||||
|
||||
resetOnSavedSearchChange: (state) => {
|
||||
withCurrentTab(state, (tab) => {
|
||||
resetOnSavedSearchChange: (state, action: TabAction) =>
|
||||
withTab(state, action, (tab) => {
|
||||
tab.overriddenVisContextAfterInvalidation = undefined;
|
||||
});
|
||||
|
||||
state.expandedDoc = undefined;
|
||||
},
|
||||
state.expandedDoc = undefined;
|
||||
}),
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(loadDataViewList.fulfilled, (state, action) => {
|
||||
|
@ -198,7 +203,7 @@ export const createInternalStateStore = (options: InternalStateThunkDependencies
|
|||
...defaultTabState,
|
||||
...createTabItem(selectAllTabs(store.getState())),
|
||||
};
|
||||
store.dispatch(setTabs({ allTabs: [defaultTab], selectedTabId: defaultTab.id }));
|
||||
store.dispatch(setTabs({ allTabs: [defaultTab] }));
|
||||
|
||||
return store;
|
||||
};
|
||||
|
|
|
@ -11,8 +11,7 @@ import type { DataView } from '@kbn/data-views-plugin/common';
|
|||
import React, { type PropsWithChildren, createContext, useContext, useMemo } from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { useInternalStateSelector } from './hooks';
|
||||
import type { DiscoverInternalState } from './types';
|
||||
import { useCurrentTabContext } from './hooks';
|
||||
|
||||
interface DiscoverRuntimeState {
|
||||
adHocDataViews: DataView[];
|
||||
|
@ -46,22 +45,15 @@ export const createTabRuntimeState = (): ReactiveTabRuntimeState => ({
|
|||
export const useRuntimeState = <T,>(stateSubject$: BehaviorSubject<T>) =>
|
||||
useObservable(stateSubject$, stateSubject$.getValue());
|
||||
|
||||
export const selectCurrentTabRuntimeState = (
|
||||
internalState: DiscoverInternalState,
|
||||
runtimeStateManager: RuntimeStateManager
|
||||
) => {
|
||||
const currentTabId = internalState.tabs.currentId;
|
||||
return runtimeStateManager.tabs.byId[currentTabId];
|
||||
};
|
||||
export const selectTabRuntimeState = (runtimeStateManager: RuntimeStateManager, tabId: string) =>
|
||||
runtimeStateManager.tabs.byId[tabId];
|
||||
|
||||
export const useCurrentTabRuntimeState = <T,>(
|
||||
runtimeStateManager: RuntimeStateManager,
|
||||
selector: (tab: ReactiveTabRuntimeState) => BehaviorSubject<T>
|
||||
) => {
|
||||
const tab = useInternalStateSelector((state) =>
|
||||
selectCurrentTabRuntimeState(state, runtimeStateManager)
|
||||
);
|
||||
return useRuntimeState(selector(tab));
|
||||
const { currentTabId } = useCurrentTabContext();
|
||||
return useRuntimeState(selector(selectTabRuntimeState(runtimeStateManager, currentTabId)));
|
||||
};
|
||||
|
||||
type CombinedRuntimeState = DiscoverRuntimeState & TabRuntimeState;
|
||||
|
|
|
@ -12,5 +12,4 @@ import type { DiscoverInternalState } from './types';
|
|||
export const selectAllTabs = (state: DiscoverInternalState) =>
|
||||
state.tabs.allIds.map((id) => state.tabs.byId[id]);
|
||||
|
||||
export const selectCurrentTab = (state: DiscoverInternalState) =>
|
||||
state.tabs.byId[state.tabs.currentId];
|
||||
export const selectTab = (state: DiscoverInternalState, tabId: string) => state.tabs.byId[tabId];
|
||||
|
|
|
@ -70,6 +70,5 @@ export interface DiscoverInternalState {
|
|||
tabs: {
|
||||
byId: Record<string, TabState>;
|
||||
allIds: string[];
|
||||
currentId: string;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,9 +7,16 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { TabItem } from '@kbn/unified-tabs';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import type { DiscoverInternalState } from './types';
|
||||
import type { InternalStateDispatch, InternalStateThunkDependencies } from './internal_state';
|
||||
import type { DiscoverInternalState, TabState } from './types';
|
||||
import type {
|
||||
InternalStateDispatch,
|
||||
InternalStateThunkDependencies,
|
||||
TabActionPayload,
|
||||
} from './internal_state';
|
||||
|
||||
// For some reason if this is not explicitly typed, TypeScript fails with the following error:
|
||||
// TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.
|
||||
|
@ -23,3 +30,29 @@ type CreateInternalStateAsyncThunk = ReturnType<
|
|||
|
||||
export const createInternalStateAsyncThunk: CreateInternalStateAsyncThunk =
|
||||
createAsyncThunk.withTypes();
|
||||
|
||||
type WithoutTabId<TPayload extends TabActionPayload> = Omit<TPayload, 'tabId'>;
|
||||
type VoidIfEmpty<T> = keyof T extends never ? void : T;
|
||||
|
||||
export const createTabActionInjector =
|
||||
(tabId: string) =>
|
||||
<TPayload extends TabActionPayload, TReturn>(actionCreator: (params: TPayload) => TReturn) =>
|
||||
(payload: VoidIfEmpty<WithoutTabId<TPayload>>) => {
|
||||
return actionCreator({ ...(payload ?? {}), tabId } as TPayload);
|
||||
};
|
||||
|
||||
export type TabActionInjector = ReturnType<typeof createTabActionInjector>;
|
||||
|
||||
const DEFAULT_TAB_LABEL = i18n.translate('discover.defaultTabLabel', {
|
||||
defaultMessage: 'Untitled session',
|
||||
});
|
||||
const DEFAULT_TAB_REGEX = new RegExp(`^${DEFAULT_TAB_LABEL}( \\d+)?$`);
|
||||
|
||||
export const createTabItem = (allTabs: TabState[]): TabItem => {
|
||||
const id = uuid();
|
||||
const untitledTabCount = allTabs.filter((tab) => DEFAULT_TAB_REGEX.test(tab.label.trim())).length;
|
||||
const label =
|
||||
untitledTabCount > 0 ? `${DEFAULT_TAB_LABEL} ${untitledTabCount}` : DEFAULT_TAB_LABEL;
|
||||
|
||||
return { id, label };
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ import { discoverServiceMock } from '../../../../__mocks__/services';
|
|||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock';
|
||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||
import { createRuntimeStateManager, internalStateActions, selectCurrentTab } from '../redux';
|
||||
import { createRuntimeStateManager, internalStateActions } from '../redux';
|
||||
|
||||
const setupTestParams = (dataView: DataView | undefined) => {
|
||||
const savedSearch = savedSearchMock;
|
||||
|
@ -25,7 +25,9 @@ const setupTestParams = (dataView: DataView | undefined) => {
|
|||
const runtimeStateManager = createRuntimeStateManager();
|
||||
const discoverState = getDiscoverStateMock({ savedSearch, runtimeStateManager });
|
||||
discoverState.internalState.dispatch(
|
||||
internalStateActions.setDataView(savedSearch.searchSource.getField('index')!)
|
||||
discoverState.injectCurrentTab(internalStateActions.setDataView)({
|
||||
dataView: savedSearch.searchSource.getField('index')!,
|
||||
})
|
||||
);
|
||||
services.dataViews.get = jest.fn(() => Promise.resolve(dataView as DataView));
|
||||
discoverState.appState.update = jest.fn();
|
||||
|
@ -34,6 +36,8 @@ const setupTestParams = (dataView: DataView | undefined) => {
|
|||
appState: discoverState.appState,
|
||||
internalState: discoverState.internalState,
|
||||
runtimeStateManager,
|
||||
injectCurrentTab: discoverState.injectCurrentTab,
|
||||
getCurrentTab: discoverState.getCurrentTab,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -41,41 +45,41 @@ describe('changeDataView', () => {
|
|||
it('should set the right app state when a valid data view (which includes the preconfigured default column) to switch to is given', async () => {
|
||||
const params = setupTestParams(dataViewWithDefaultColumnMock);
|
||||
const promise = changeDataView({ dataViewId: dataViewWithDefaultColumnMock.id!, ...params });
|
||||
expect(selectCurrentTab(params.internalState.getState()).isDataViewLoading).toBe(true);
|
||||
expect(params.getCurrentTab().isDataViewLoading).toBe(true);
|
||||
await promise;
|
||||
expect(params.appState.update).toHaveBeenCalledWith({
|
||||
columns: ['default_column'], // default_column would be added as dataViewWithDefaultColumn has it as a mapped field
|
||||
dataSource: createDataViewDataSource({ dataViewId: 'data-view-with-user-default-column-id' }),
|
||||
sort: [['@timestamp', 'desc']],
|
||||
});
|
||||
expect(selectCurrentTab(params.internalState.getState()).isDataViewLoading).toBe(false);
|
||||
expect(params.getCurrentTab().isDataViewLoading).toBe(false);
|
||||
});
|
||||
|
||||
it('should set the right app state when a valid data view to switch to is given', async () => {
|
||||
const params = setupTestParams(dataViewComplexMock);
|
||||
const promise = changeDataView({ dataViewId: dataViewComplexMock.id!, ...params });
|
||||
expect(selectCurrentTab(params.internalState.getState()).isDataViewLoading).toBe(true);
|
||||
expect(params.getCurrentTab().isDataViewLoading).toBe(true);
|
||||
await promise;
|
||||
expect(params.appState.update).toHaveBeenCalledWith({
|
||||
columns: [], // default_column would not be added as dataViewComplexMock does not have it as a mapped field
|
||||
dataSource: createDataViewDataSource({ dataViewId: 'data-view-with-various-field-types-id' }),
|
||||
sort: [['data', 'desc']],
|
||||
});
|
||||
expect(selectCurrentTab(params.internalState.getState()).isDataViewLoading).toBe(false);
|
||||
expect(params.getCurrentTab().isDataViewLoading).toBe(false);
|
||||
});
|
||||
|
||||
it('should not set the app state when an invalid data view to switch to is given', async () => {
|
||||
const params = setupTestParams(undefined);
|
||||
const promise = changeDataView({ dataViewId: 'data-view-with-various-field-types', ...params });
|
||||
expect(selectCurrentTab(params.internalState.getState()).isDataViewLoading).toBe(true);
|
||||
expect(params.getCurrentTab().isDataViewLoading).toBe(true);
|
||||
await promise;
|
||||
expect(params.appState.update).not.toHaveBeenCalled();
|
||||
expect(selectCurrentTab(params.internalState.getState()).isDataViewLoading).toBe(false);
|
||||
expect(params.getCurrentTab().isDataViewLoading).toBe(false);
|
||||
});
|
||||
|
||||
it('should call setResetDefaultProfileState correctly when switching data view', async () => {
|
||||
const params = setupTestParams(dataViewComplexMock);
|
||||
expect(selectCurrentTab(params.internalState.getState()).resetDefaultProfileState).toEqual(
|
||||
expect(params.getCurrentTab().resetDefaultProfileState).toEqual(
|
||||
expect.objectContaining({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
|
@ -83,7 +87,7 @@ describe('changeDataView', () => {
|
|||
})
|
||||
);
|
||||
await changeDataView({ dataViewId: dataViewComplexMock.id!, ...params });
|
||||
expect(selectCurrentTab(params.internalState.getState()).resetDefaultProfileState).toEqual(
|
||||
expect(params.getCurrentTab().resetDefaultProfileState).toEqual(
|
||||
expect.objectContaining({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
|
|
|
@ -20,9 +20,11 @@ import type { DiscoverServices } from '../../../../build_services';
|
|||
import { getDataViewAppState } from './get_switch_data_view_app_state';
|
||||
import {
|
||||
internalStateActions,
|
||||
selectCurrentTabRuntimeState,
|
||||
selectTabRuntimeState,
|
||||
type InternalStateStore,
|
||||
type RuntimeStateManager,
|
||||
type TabActionInjector,
|
||||
type TabState,
|
||||
} from '../redux';
|
||||
|
||||
/**
|
||||
|
@ -34,25 +36,29 @@ export async function changeDataView({
|
|||
internalState,
|
||||
runtimeStateManager,
|
||||
appState,
|
||||
injectCurrentTab,
|
||||
getCurrentTab,
|
||||
}: {
|
||||
dataViewId: string | DataView;
|
||||
services: DiscoverServices;
|
||||
internalState: InternalStateStore;
|
||||
runtimeStateManager: RuntimeStateManager;
|
||||
appState: DiscoverAppStateContainer;
|
||||
injectCurrentTab: TabActionInjector;
|
||||
getCurrentTab: () => TabState;
|
||||
}) {
|
||||
addLog('[ui] changeDataView', { id: dataViewId });
|
||||
|
||||
const { dataViews, uiSettings } = services;
|
||||
const { currentDataView$ } = selectCurrentTabRuntimeState(
|
||||
internalState.getState(),
|
||||
runtimeStateManager
|
||||
);
|
||||
const { id: currentTabId } = getCurrentTab();
|
||||
const { currentDataView$ } = selectTabRuntimeState(runtimeStateManager, currentTabId);
|
||||
const currentDataView = currentDataView$.getValue();
|
||||
const state = appState.getState();
|
||||
let nextDataView: DataView | null = null;
|
||||
|
||||
internalState.dispatch(internalStateActions.setIsDataViewLoading(true));
|
||||
internalState.dispatch(
|
||||
injectCurrentTab(internalStateActions.setIsDataViewLoading)({ isDataViewLoading: true })
|
||||
);
|
||||
|
||||
try {
|
||||
nextDataView =
|
||||
|
@ -71,10 +77,12 @@ export async function changeDataView({
|
|||
if (nextDataView && currentDataView) {
|
||||
// Reset the default profile state if we are switching to a different data view
|
||||
internalState.dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
injectCurrentTab(internalStateActions.setResetDefaultProfileState)({
|
||||
resetDefaultProfileState: {
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -96,5 +104,7 @@ export async function changeDataView({
|
|||
}
|
||||
}
|
||||
|
||||
internalState.dispatch(internalStateActions.setIsDataViewLoading(false));
|
||||
internalState.dispatch(
|
||||
injectCurrentTab(internalStateActions.setIsDataViewLoading)({ isDataViewLoading: false })
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue