From 2fb0b2e0ba148f2b7590d1656376e630675d59c6 Mon Sep 17 00:00:00 2001 From: Luke Gmys <11671118+lgestc@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:23:53 +0200 Subject: [PATCH] [Security Solution] Fix initial data view flash (#225675) ## Summary Solves the initial UI "flash" when new data view picker is enabled and the application is starting. Compared to previous approach, we are not doing default data view provider and just check the loading state before telling the user no indices are matched. ## Testing Head to timelines page or explore pages... should not see the data flash with this feature flag on. Brief blank page could occur, as the proper loading screens would be required. ``` xpack.securitySolution.enableExperimental: ['newDataViewPickerEnabled'] ``` ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit 209c2988b5fa3b62addfdca081114956af77fcb5) # Conflicts: # x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx # x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_privileged_user_monitoring_page.tsx # x-pack/solutions/security/plugins/security_solution/public/explore/network/pages/network.tsx --- .../common/components/page_loader/index.tsx | 13 ++ .../hooks/use_data_view.test.ts | 149 ++++++++++++++++-- .../data_view_manager/hooks/use_data_view.ts | 40 +++-- .../pages/entity_analytics_dashboard.tsx | 5 + .../explore/hosts/pages/details/index.tsx | 7 +- .../public/explore/hosts/pages/hosts.tsx | 7 +- .../explore/network/pages/details/index.tsx | 7 +- .../public/explore/network/pages/network.tsx | 8 +- .../explore/users/pages/details/index.tsx | 7 +- .../public/explore/users/pages/users.tsx | 7 +- .../components/network_details.tsx | 7 +- .../public/overview/pages/data_quality.tsx | 5 + .../overview/pages/detection_response.tsx | 5 + .../public/overview/pages/overview.tsx | 7 +- .../containers/use_signal_helpers.tsx | 17 +- .../timelines/pages/timelines_page.test.tsx | 2 +- .../public/timelines/pages/timelines_page.tsx | 9 +- 17 files changed, 259 insertions(+), 43 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/components/page_loader/index.tsx diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/page_loader/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/components/page_loader/index.tsx new file mode 100644 index 0000000000000..a970c022ded80 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/page_loader/index.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiEmptyPrompt, EuiLoadingLogo } from '@elastic/eui'; + +export const PageLoader = () => ( + } /> +); diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view.test.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view.test.ts index f488b465b7cb9..c1b6437a79db8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view.test.ts @@ -5,13 +5,14 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react'; -import { TestProviders } from '../../common/mock'; -import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, DataViewManagerScopeName } from '../constants'; +import { act, renderHook, waitFor } from '@testing-library/react'; +import { DataView } from '@kbn/data-views-plugin/public'; +import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, DataViewManagerScopeName } from '../constants'; import { useDataView } from './use_data_view'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { useSelector } from 'react-redux'; +import type { FieldFormatsStartCommon } from '@kbn/field-formats-plugin/common'; jest.mock('../../common/hooks/use_experimental_features'); @@ -20,32 +21,148 @@ jest.mock('react-redux', () => ({ useSelector: jest.fn(), })); +const mockGet = jest.fn(); +const mockToastsDanger = jest.fn(); + +const mockNotifications = { + toasts: { + danger: mockToastsDanger, + }, +}; + +const mockDataViews = { get: mockGet }; + +const fakeDataView = new DataView({ + spec: { + id: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, + }, + fieldFormats: {} as FieldFormatsStartCommon, +}); + +jest.mock('../../common/lib/kibana', () => { + const actual = jest.requireActual('../../common/lib/kibana'); + return { + ...actual, + useKibana: () => ({ + services: { + dataViews: mockDataViews, + }, + notifications: mockNotifications, + }), + }; +}); + describe('useDataView', () => { beforeEach(() => { + jest.clearAllMocks(); jest.mocked(useIsExperimentalFeatureEnabled).mockReturnValue(true); jest .mocked(useSelector) .mockReturnValue({ dataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, status: 'ready' }); }); - describe('when data view is available', () => { - it('should return DataView instance', async () => { - const wrapper = renderHook(() => useDataView(DataViewManagerScopeName.default), { - wrapper: TestProviders, - }); + it('should return DataView instance when data view is available', async () => { + mockGet.mockResolvedValue(fakeDataView); + + const { result } = renderHook(() => useDataView()); + + expect(result.current.dataView).not.toBe(undefined); + expect(result.current.dataView.id).toBe(undefined); + expect(result.current.status).toBe('pristine'); + + // NOTE: should switch to ready almost immediately + await waitFor(() => { + expect(result.current.status).toEqual('ready'); + }); + + expect(result.current.dataView.id).toBe(DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID); + }); + + it('should set status to loading on subsequent calls after first load', async () => { + mockGet.mockResolvedValue(fakeDataView); + + const { result, rerender } = renderHook(() => useDataView()); + + // First load, no loading state + expect(result.current.status).toBe('pristine'); + await waitFor(() => { + if (result.current.status === 'loading') { + // if loading is returned here, we have an error. until the first data view is loaded from the service, we want to stay "pristine". + // this is because there are elements on some pages that depending on this behavior. + return; + } + + expect(result.current.status).toEqual('ready'); + }); + + jest + .mocked(useSelector) + .mockReturnValue({ dataViewId: 'different-data-view', status: 'ready' }); + + // Dont await on purpose + act(() => rerender()); + + // Should be loading at some point + await waitFor(() => { + expect(result.current.status).toEqual('loading'); + }); + }); + + it('should not call get if newDataViewPickerEnabled is false', async () => { + jest.mocked(useIsExperimentalFeatureEnabled).mockReturnValue(false); + + const { result, rerender } = renderHook(() => useDataView(DataViewManagerScopeName.default)); + + await act(async () => rerender(DataViewManagerScopeName.default)); + expect(mockGet).not.toHaveBeenCalled(); + expect(result.current.status).toBe('pristine'); + }); + + it('should not call get if dataViewId is missing', async () => { + jest.mocked(useSelector).mockReturnValue({ dataViewId: undefined, status: 'ready' }); + + const { result, rerender } = renderHook(() => useDataView(DataViewManagerScopeName.default)); + + await act(async () => rerender(DataViewManagerScopeName.default)); + expect(mockGet).not.toHaveBeenCalled(); + expect(result.current.status).toBe('pristine'); + }); + + it('should not call get if status is not ready', async () => { + jest + .mocked(useSelector) + .mockReturnValue({ dataViewId: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, status: 'loading' }); + + const { result, rerender } = renderHook(() => useDataView(DataViewManagerScopeName.default)); - await act(async () => wrapper.rerender(DataViewManagerScopeName.default)); - expect(wrapper.result.current.dataView).toBeTruthy(); + await act(async () => rerender(DataViewManagerScopeName.default)); + expect(mockGet).not.toHaveBeenCalled(); + expect(result.current.status).toBe('pristine'); + }); + + it('should set status to error and call toasts.danger on get error', async () => { + mockGet.mockRejectedValue(new Error('fail!')); + + const { result, rerender } = renderHook(() => useDataView(DataViewManagerScopeName.default)); + + await act(async () => rerender(DataViewManagerScopeName.default)); + expect(result.current.status).toBe('error'); + expect(mockToastsDanger).toHaveBeenCalledWith({ + title: 'Error retrieving data view', + body: expect.stringContaining('fail!'), }); }); - describe('when data view fields are not available', () => { - it('should return undefined', () => { - const wrapper = renderHook(() => useDataView(DataViewManagerScopeName.default), { - wrapper: TestProviders, - }); + it('should handle unknown error shape gracefully', async () => { + mockGet.mockRejectedValue({}); + + const { result, rerender } = renderHook(() => useDataView(DataViewManagerScopeName.default)); - expect(wrapper.result.current.dataView).toBeUndefined(); + await act(async () => rerender(DataViewManagerScopeName.default)); + expect(result.current.status).toBe('error'); + expect(mockToastsDanger).toHaveBeenCalledWith({ + title: 'Error retrieving data view', + body: expect.stringContaining('unknown'), }); }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view.ts index 92b01544b2504..5508cef1ce4c3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view.ts @@ -5,23 +5,28 @@ * 2.0. */ -import { useEffect, useMemo, useState } from 'react'; -import { type DataView } from '@kbn/data-views-plugin/public'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { DataView } from '@kbn/data-views-plugin/public'; import { useSelector } from 'react-redux'; +import { type FieldFormatsStartCommon } from '@kbn/field-formats-plugin/common'; import { useKibana } from '../../common/lib/kibana'; import { DataViewManagerScopeName } from '../constants'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { sourcererAdapterSelector } from '../redux/selectors'; import type { SharedDataViewSelectionState } from '../redux/types'; +const INITIAL_DV = new DataView({ + fieldFormats: {} as FieldFormatsStartCommon, +}); + /* * This hook should be used whenever we need the actual DataView and not just the spec for the * selected data view. */ export const useDataView = ( dataViewManagerScope: DataViewManagerScopeName = DataViewManagerScopeName.default -): { dataView: DataView | undefined; status: SharedDataViewSelectionState['status'] } => { +): { dataView: DataView; status: SharedDataViewSelectionState['status'] } => { const { services: { dataViews }, notifications, @@ -31,12 +36,23 @@ export const useDataView = ( sourcererAdapterSelector(dataViewManagerScope) ); const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const [retrievedDataView, setRetrievedDataView] = useState(); + const [localStatus, setLocalStatus] = + useState('pristine'); + const [retrievedDataView, setRetrievedDataView] = useState(INITIAL_DV); + const loadedForTheFirstTimeRef = useRef(false); useEffect(() => { (async () => { + if (!newDataViewPickerEnabled) { + return; + } + if (!dataViewId || internalStatus !== 'ready') { - return setRetrievedDataView(undefined); + return; + } + + if (loadedForTheFirstTimeRef.current) { + setLocalStatus('loading'); } try { @@ -44,23 +60,27 @@ export const useDataView = ( // this is due to the fact that many of our tests mock kibana hook and do not provide proper // double for dataViews service const currDv = await dataViews?.get(dataViewId); + if (!loadedForTheFirstTimeRef.current) { + loadedForTheFirstTimeRef.current = true; + } setRetrievedDataView(currDv); + setLocalStatus('ready'); } catch (error) { - setRetrievedDataView(undefined); // TODO: (remove conditional call when feature flag is on (mocks are broken for some tests)) notifications?.toasts?.danger({ title: 'Error retrieving data view', body: `Error: ${error?.message ?? 'unknown'}`, }); + setLocalStatus('error'); } })(); - }, [dataViews, dataViewId, internalStatus, notifications]); + }, [dataViews, dataViewId, internalStatus, notifications, newDataViewPickerEnabled]); return useMemo(() => { if (!newDataViewPickerEnabled) { - return { dataView: undefined, status: internalStatus }; + return { dataView: retrievedDataView, status: localStatus }; } - return { dataView: retrievedDataView, status: retrievedDataView ? internalStatus : 'loading' }; - }, [newDataViewPickerEnabled, retrievedDataView, internalStatus]); + return { dataView: retrievedDataView, status: localStatus }; + }, [newDataViewPickerEnabled, retrievedDataView, localStatus]); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx index bf983c363a1c1..3a1cc9086bb1a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_dashboard.tsx @@ -29,6 +29,7 @@ import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experime import { useDataViewSpec } from '../../data_view_manager/hooks/use_data_view_spec'; import { useDataView } from '../../data_view_manager/hooks/use_data_view'; import { useStoreEntityTypes } from '../hooks/use_enabled_entity_types'; +import { PageLoader } from '../../common/components/page_loader'; const EntityAnalyticsComponent = () => { const { data: riskScoreEngineStatus } = useRiskEngineStatus(); @@ -60,6 +61,10 @@ const EntityAnalyticsComponent = () => { const isEntityStoreFeatureFlagDisabled = useIsExperimentalFeatureEnabled('entityStoreDisabled'); const entityTypes = useStoreEntityTypes(); + if (newDataViewPickerEnabled && status === 'pristine') { + return ; + } + return ( <> {indicesExist ? ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/pages/details/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/pages/details/index.tsx index 30497c1db300b..2e0575692212b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/pages/details/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/pages/details/index.tsx @@ -86,6 +86,7 @@ import { SourcererScopeName } from '../../../../sourcerer/store/model'; import { useDataView } from '../../../../data_view_manager/hooks/use_data_view'; import { useDataViewSpec } from '../../../../data_view_manager/hooks/use_data_view_spec'; import { useSelectedPatterns } from '../../../../data_view_manager/hooks/use_selected_patterns'; +import { PageLoader } from '../../../../common/components/page_loader'; const ES_HOST_FIELD = 'host.name'; const HostOverviewManage = manageQuery(HostOverview); @@ -142,7 +143,7 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const { dataView } = useDataView(); + const { dataView, status } = useDataView(); const { dataViewSpec } = useDataViewSpec(); const experimentalSelectedPatterns = useSelectedPatterns(); @@ -225,6 +226,10 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta onChange: calculateEntityRiskScore, }); + if (newDataViewPickerEnabled && status === 'pristine') { + return ; + } + return ( <> {indicesExist ? ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/pages/hosts.tsx b/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/pages/hosts.tsx index ef4d436eaa6e6..64821b8b09e0b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/pages/hosts.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/pages/hosts.tsx @@ -58,6 +58,7 @@ import { useLicense } from '../../../common/hooks/use_license'; import { useDataView } from '../../../data_view_manager/hooks/use_data_view'; import { useDataViewSpec } from '../../../data_view_manager/hooks/use_data_view_spec'; import { useSelectedPatterns } from '../../../data_view_manager/hooks/use_selected_patterns'; +import { PageLoader } from '../../../common/components/page_loader'; /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. @@ -117,7 +118,7 @@ const HostsComponent = () => { const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const { dataView } = useDataView(); + const { dataView, status } = useDataView(); const { dataViewSpec } = useDataViewSpec(); const experimentalSelectedPatterns = useSelectedPatterns(); @@ -185,6 +186,10 @@ const HostsComponent = () => { [containerElement, onSkipFocusBeforeEventsTable, onSkipFocusAfterEventsTable] ); + if (newDataViewPickerEnabled && status === 'pristine') { + return ; + } + return ( <> {indicesExist ? ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/network/pages/details/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/explore/network/pages/details/index.tsx index b1ecbd8360c37..1f6d5d4000158 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/network/pages/details/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/network/pages/details/index.tsx @@ -33,6 +33,7 @@ import { FlowTargetSelectConnected } from '../../components/flow_target_select_c import type { IpOverviewProps } from '../../components/details'; import { IpOverview } from '../../components/details'; import { SiemSearchBar } from '../../../../common/components/search_bar'; +import { PageLoader } from '../../../../common/components/page_loader'; import { SecuritySolutionPageWrapper } from '../../../../common/components/page_wrapper'; import { useNetworkDetails, ID } from '../../containers/details'; import { useKibana } from '../../../../common/lib/kibana'; @@ -118,7 +119,7 @@ const NetworkDetailsComponent: React.FC = () => { const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const { dataView } = useDataView(); + const { dataView, status } = useDataView(); const { dataViewSpec } = useDataViewSpec(); const experimentalSelectedPatterns = useSelectedPatterns(); @@ -193,6 +194,10 @@ const NetworkDetailsComponent: React.FC = () => { return dataViewSpecToViewBase(sourcererDataView); }, [sourcererDataView]); + if (newDataViewPickerEnabled && status === 'pristine') { + return ; + } + return (
{indicesExist ? ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/network/pages/network.tsx b/x-pack/solutions/security/plugins/security_solution/public/explore/network/pages/network.tsx index b1d18246f2fe6..d1960cd68891b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/network/pages/network.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/network/pages/network.tsx @@ -52,6 +52,8 @@ import { EmptyPrompt } from '../../../common/components/empty_prompt'; import { useDataView } from '../../../data_view_manager/hooks/use_data_view'; import { useDataViewSpec } from '../../../data_view_manager/hooks/use_data_view_spec'; import { useSelectedPatterns } from '../../../data_view_manager/hooks/use_selected_patterns'; +import { PageLoader } from '../../../common/components/page_loader'; + /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. */ @@ -100,7 +102,7 @@ const NetworkComponent = React.memo( const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const { dataView } = useDataView(); + const { dataView, status } = useDataView(); const { dataViewSpec } = useDataViewSpec(); const experimentalSelectedPatterns = useSelectedPatterns(); @@ -152,6 +154,10 @@ const NetworkComponent = React.memo( useInvalidFilterQuery({ id: ID, filterQuery, kqlError, query, startDate: from, endDate: to }); + if (newDataViewPickerEnabled && status === 'pristine') { + return ; + } + return ( <> {indicesExist ? ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/users/pages/details/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/explore/users/pages/details/index.tsx index 95bd2239657b8..ba50e89ea645d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/users/pages/details/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/users/pages/details/index.tsx @@ -84,6 +84,7 @@ import { useRefetchOverviewPageRiskScore } from '../../../../entity_analytics/ap import { useDataView } from '../../../../data_view_manager/hooks/use_data_view'; import { useDataViewSpec } from '../../../../data_view_manager/hooks/use_data_view_spec'; import { useSelectedPatterns } from '../../../../data_view_manager/hooks/use_selected_patterns'; +import { PageLoader } from '../../../../common/components/page_loader'; const QUERY_ID = 'UsersDetailsQueryId'; const ES_USER_FIELD = 'user.name'; @@ -129,7 +130,7 @@ const UsersDetailsComponent: React.FC = ({ const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const { dataView } = useDataView(); + const { dataView, status } = useDataView(); const { dataViewSpec } = useDataViewSpec(); const experimentalSelectedPatterns = useSelectedPatterns(); @@ -225,6 +226,10 @@ const UsersDetailsComponent: React.FC = ({ onChange: calculateEntityRiskScore, }); + if (newDataViewPickerEnabled && status === 'pristine') { + return ; + } + return ( <> {indicesExist ? ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/users/pages/users.tsx b/x-pack/solutions/security/plugins/security_solution/public/explore/users/pages/users.tsx index a5fd301d1f352..7d0d245d619f0 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/users/pages/users.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/users/pages/users.tsx @@ -54,6 +54,7 @@ import { userNameExistsFilter } from './details/helpers'; import { useDataView } from '../../../data_view_manager/hooks/use_data_view'; import { useDataViewSpec } from '../../../data_view_manager/hooks/use_data_view_spec'; import { useSelectedPatterns } from '../../../data_view_manager/hooks/use_selected_patterns'; +import { PageLoader } from '../../../common/components/page_loader'; const ID = 'UsersQueryId'; @@ -111,7 +112,7 @@ const UsersComponent = () => { const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const { dataView } = useDataView(); + const { dataView, status } = useDataView(); const { dataViewSpec } = useDataViewSpec(); const experimentalSelectedPatterns = useSelectedPatterns(); @@ -180,6 +181,10 @@ const UsersComponent = () => { const capabilities = useMlCapabilities(); const navTabs = useMemo(() => navTabsUsers(hasMlUserPermissions(capabilities)), [capabilities]); + if (newDataViewPickerEnabled && status === 'pristine') { + return ; + } + return ( <> {indicesExist ? ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/network_details/components/network_details.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/network_details/components/network_details.tsx index 3bb68185e9192..9b8814e82d346 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/network_details/components/network_details.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/network_details/components/network_details.tsx @@ -8,6 +8,7 @@ import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { getEsQueryConfig } from '@kbn/data-plugin/common'; +import { PageLoader } from '../../../common/components/page_loader'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { InputsModelId } from '../../../common/store/inputs/constants'; import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; @@ -85,7 +86,7 @@ export const NetworkDetails = ({ ip, flowTarget }: NetworkDetailsProps) => { const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const { dataView } = useDataView(); + const { dataView, status } = useDataView(); const { dataViewSpec } = useDataViewSpec(); const experimentalSelectedPatterns = useSelectedPatterns(); @@ -123,6 +124,10 @@ export const NetworkDetails = ({ ip, flowTarget }: NetworkDetailsProps) => { aggregationInterval: 'auto', }); + if (newDataViewPickerEnabled && status === 'pristine') { + return ; + } + return indicesExist ? ( { return ; } + if (newDataViewPickerEnabled && status === 'pristine') { + return ; + } + return ( <> {indicesExist ? ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/overview/pages/detection_response.tsx b/x-pack/solutions/security/plugins/security_solution/public/overview/pages/detection_response.tsx index 0a09b73cfbf5b..b7c256b57fe4a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/overview/pages/detection_response.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/overview/pages/detection_response.tsx @@ -34,6 +34,7 @@ import { useGlobalFilterQuery } from '../../common/hooks/use_global_filter_query import { useKibana } from '../../common/lib/kibana'; import { useDataView } from '../../data_view_manager/hooks/use_data_view'; import { useDataViewSpec } from '../../data_view_manager/hooks/use_data_view_spec'; +import { PageLoader } from '../../common/components/page_loader'; const DetectionResponseComponent = () => { const { cases } = useKibana().services; @@ -67,6 +68,10 @@ const DetectionResponseComponent = () => { return docLinks.siem.privileges} />; } + if (newDataViewPickerEnabled && status === 'pristine') { + return ; + } + return ( <> {indicesExist ? ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/solutions/security/plugins/security_solution/public/overview/pages/overview.tsx index 95377d152e56d..fa9e88efe27fd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/overview/pages/overview.tsx @@ -43,6 +43,7 @@ import { useSelectedPatterns } from '../../data_view_manager/hooks/use_selected_ import { useDataViewSpec } from '../../data_view_manager/hooks/use_data_view_spec'; import { useDataView } from '../../data_view_manager/hooks/use_data_view'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; +import { PageLoader } from '../../common/components/page_loader'; const OverviewComponent = () => { const getGlobalFiltersQuerySelector = useMemo( @@ -62,7 +63,7 @@ const OverviewComponent = () => { const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const { dataView } = useDataView(); + const { dataView, status } = useDataView(); const { dataViewSpec } = useDataViewSpec(); const experimentalSelectedPatterns = useSelectedPatterns(); @@ -95,6 +96,10 @@ const OverviewComponent = () => { const { hasIndexRead, hasKibanaREAD } = useAlertsPrivileges(); const { tiDataSources: allTiDataSources, isInitiallyLoaded: isTiLoaded } = useAllTiDataSources(); + if (newDataViewPickerEnabled && status === 'pristine') { + return ; + } + return ( <> diff --git a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx index 49c1dc2d1c969..d6f6fe55c5ef8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.tsx @@ -49,19 +49,24 @@ export const useSignalHelpers = (): { ? experimentalSignalIndexName : signalIndexNameSourcerer; - const { dataView: experimentalDefaultDataView } = useDataView(SourcererScopeName.default); + const { dataView: experimentalDefaultDataView, status } = useDataView( + SourcererScopeName.detections + ); const dataViewId = newDataViewPickerEnabled ? experimentalDefaultDataView?.id ?? null : oldDataViewId; const defaultDataViewPattern = newDataViewPickerEnabled - ? experimentalDefaultDataView?.getIndexPattern() ?? '' + ? experimentalDefaultDataView.getIndexPattern() ?? '' : oldDefaultDataView.title; - const signalIndexNeedsInit = useMemo( - () => !defaultDataViewPattern.includes(`${signalIndexName}`), - [defaultDataViewPattern, signalIndexName] - ); + const signalIndexNeedsInit = useMemo(() => { + if (newDataViewPickerEnabled && status === 'pristine') { + return false; + } + + return !defaultDataViewPattern.includes(`${signalIndexName}`); + }, [defaultDataViewPattern, newDataViewPickerEnabled, signalIndexName, status]); const shouldWePollForIndex = useMemo( () => !indicesExist && !signalIndexNeedsInit, [indicesExist, signalIndexNeedsInit] diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx index 01de3e0da474a..d0e5b83a8f0d3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx @@ -26,7 +26,7 @@ jest.mock('../../overview/components/events_by_dataset'); jest.mock('../../sourcerer/containers'); jest.mock('../../common/components/user_privileges'); jest.mock('../../data_view_manager/hooks/use_data_view', () => ({ - useDataView: jest.fn(() => ({ matchedIndices: [] })), + useDataView: jest.fn(() => ({ dataView: { matchedIndices: [] } })), })); jest.mock('../../common/hooks/use_experimental_features'); diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.tsx index a52aa638359e2..93b2435fd883d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/pages/timelines_page.tsx @@ -22,6 +22,7 @@ import { DataViewManagerScopeName } from '../../data_view_manager/constants'; import { useSourcererDataView } from '../../sourcerer/containers'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { useDataView } from '../../data_view_manager/hooks/use_data_view'; +import { PageLoader } from '../../common/components/page_loader'; export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; @@ -31,9 +32,9 @@ export const TimelinesPage = React.memo(() => { const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); const { indicesExist: oldIndicesExist } = useSourcererDataView(); - const { dataView } = useDataView(DataViewManagerScopeName.default); + const { dataView, status } = useDataView(DataViewManagerScopeName.default); // NOTE: there should be a Suspense / some kind of loader here as this value is not settled immediately - const experimentalIndicesExist = !!dataView?.matchedIndices?.length; + const experimentalIndicesExist = !!dataView.matchedIndices.length; const indicesExist = newDataViewPickerEnabled ? experimentalIndicesExist : oldIndicesExist; @@ -49,6 +50,10 @@ export const TimelinesPage = React.memo(() => { const timelineType = tabName === TimelineTypeEnum.default ? TimelineTypeEnum.default : TimelineTypeEnum.template; + if (newDataViewPickerEnabled && status === 'pristine') { + return ; + } + return ( {indicesExist ? (