From 7b3325d1a857e5223c2a4fbaee1af2f8aeac95aa Mon Sep 17 00:00:00 2001 From: lgestc Date: Tue, 24 Jun 2025 17:49:22 +0200 Subject: [PATCH 1/7] [Security Solution] Loading fallback poc for data views --- .../containers/SafeDataViewProvider.tsx | 42 +++++++++ .../hooks/use_data_view_safe.ts | 40 +++++++++ .../public/timelines/pages/timelines_page.tsx | 87 ++++++++++--------- 3 files changed, 130 insertions(+), 39 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx new file mode 100644 index 0000000000000..8548ee0c14f73 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx @@ -0,0 +1,42 @@ +/* + * 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, { createContext, type ReactNode, type FC, type PropsWithChildren } from 'react'; + +import { type DataViewManagerScopeName } from '../constants'; +import { useDataView } from '../hooks/use_data_view'; + +export const DataViewContext = createContext< + | { results: Array>; scopes: readonly DataViewManagerScopeName[] } + | undefined +>(undefined); + +export interface SafeDataViewProviderProps { + scopes: readonly DataViewManagerScopeName[]; + fallback?: ReactNode; +} + +const fallbackElement =
{`WIP Loading`}
; + +/** + * Data view provider. We call it safe, because obtaining data view instance (for specified scopes) inside of it + * does not require addtional checks for nullish value or loading state. + */ +export const SafeDataViewProvider: FC> = ({ + children, + scopes, + fallback = fallbackElement, +}) => { + const results = scopes.map(useDataView); + const allReady = results.every((result) => result.status === 'ready'); + + return ( + + {allReady ? children : fallback} + + ); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts new file mode 100644 index 0000000000000..c542be50c7954 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts @@ -0,0 +1,40 @@ +/* + * 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 { useContext } from 'react'; +import { DataViewContext } from '../containers/SafeDataViewProvider'; +import { type DataViewManagerScopeName } from '../constants'; + +/** + * Returns data view that is guaranteed to be set, + * allowing you to skip the status check (if its loading or not). + * The only catch is that it can only be used inside the DataViewProvider (you can have many on the page). + */ +export const useDataViewSafe = (scope: DataViewManagerScopeName) => { + const scopes = useContext(DataViewContext); + + if (!scopes) { + throw new Error('You can only use useDataViewSafe inside DataViewProvider'); + } + + if (!scopes.scopes.includes(scope)) { + throw new Error( + 'No safeguards exist for requested scope, make sure it is configured where DataViewProvider is called' + ); + } + + const dataViewIndex = scopes.scopes.indexOf(scope); + const dataView = scopes.results[dataViewIndex].dataView; + + if (!dataView) { + throw new Error( + 'Missing data view. This error should not occur (earlier conditions should fire or the fallback should be still rendered instead)' + ); + } + + return dataView; +}; 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..3d9455cb86d13 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 @@ -21,19 +21,19 @@ import { SecurityRoutePageWrapper } from '../../common/components/security_route 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 { SafeDataViewProvider } from '../../data_view_manager/containers/SafeDataViewProvider'; +import { useDataViewSafe } from '../../data_view_manager/hooks/use_data_view_safe'; export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; -export const TimelinesPage = React.memo(() => { +const TimelinesPageContent = () => { const { tabName } = useParams<{ pageName: SecurityPageName; tabName: string }>(); const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); const { indicesExist: oldIndicesExist } = useSourcererDataView(); - const { dataView } = 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 dataView = useDataViewSafe(DataViewManagerScopeName.default); + const experimentalIndicesExist = !!dataView.matchedIndices.length; const indicesExist = newDataViewPickerEnabled ? experimentalIndicesExist : oldIndicesExist; @@ -49,42 +49,51 @@ export const TimelinesPage = React.memo(() => { const timelineType = tabName === TimelineTypeEnum.default ? TimelineTypeEnum.default : TimelineTypeEnum.template; - return ( - - {indicesExist ? ( - - - - {canWriteTimeline && ( - - - {i18n.ALL_TIMELINES_IMPORT_TIMELINE_TITLE} - - - )} + return indicesExist ? ( + + + + {canWriteTimeline && ( + + + {i18n.ALL_TIMELINES_IMPORT_TIMELINE_TITLE} + + + )} + + + + + + - - - - - + + + ) : ( + + ); +}; - - - ) : ( - - )} +export const TimelinesPage = React.memo(() => { + return ( + + + + ); }); From f7c3af11badcdf6e449844fe5eb77e6f95af57f6 Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 25 Jun 2025 10:31:05 +0200 Subject: [PATCH 2/7] changes to context value type --- .../containers/SafeDataViewProvider.tsx | 24 +++++++++++++------ .../hooks/use_data_view_safe.ts | 16 +++++++------ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx index 8548ee0c14f73..d3cda3becfbac 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx @@ -10,10 +10,11 @@ import React, { createContext, type ReactNode, type FC, type PropsWithChildren } import { type DataViewManagerScopeName } from '../constants'; import { useDataView } from '../hooks/use_data_view'; -export const DataViewContext = createContext< - | { results: Array>; scopes: readonly DataViewManagerScopeName[] } - | undefined ->(undefined); +export interface DataViewContextValue { + readonly results: Record>; +} + +export const DataViewContext = createContext(undefined); export interface SafeDataViewProviderProps { scopes: readonly DataViewManagerScopeName[]; @@ -31,11 +32,20 @@ export const SafeDataViewProvider: FC { - const results = scopes.map(useDataView); - const allReady = results.every((result) => result.status === 'ready'); + const value: DataViewContextValue = { + results: {}, + }; + + scopes.forEach((scope) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const result = useDataView(scope); + value.results[scope] = result; + }); + + const allReady = Object.values(value.results).every((result) => result.status === 'ready'); return ( - + {allReady ? children : fallback} ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts index c542be50c7954..ce7320e20b79b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts @@ -5,30 +5,32 @@ * 2.0. */ -import { useContext } from 'react'; +import { useContext, useMemo } from 'react'; import { DataViewContext } from '../containers/SafeDataViewProvider'; import { type DataViewManagerScopeName } from '../constants'; /** * Returns data view that is guaranteed to be set, * allowing you to skip the status check (if its loading or not). - * The only catch is that it can only be used inside the DataViewProvider (you can have many on the page). + * The catch is that it can only be used inside the DataViewProvider (you can have many on the page). */ export const useDataViewSafe = (scope: DataViewManagerScopeName) => { - const scopes = useContext(DataViewContext); + const dataViewsPerScope = useContext(DataViewContext); - if (!scopes) { + if (!dataViewsPerScope) { throw new Error('You can only use useDataViewSafe inside DataViewProvider'); } - if (!scopes.scopes.includes(scope)) { + if (!(scope in dataViewsPerScope.results)) { throw new Error( 'No safeguards exist for requested scope, make sure it is configured where DataViewProvider is called' ); } - const dataViewIndex = scopes.scopes.indexOf(scope); - const dataView = scopes.results[dataViewIndex].dataView; + const dataView = useMemo( + () => dataViewsPerScope.results[scope].dataView, + [dataViewsPerScope.results, scope] + ); if (!dataView) { throw new Error( From 6dd041560c8fc4ed53f175043072f04d43d335eb Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 25 Jun 2025 10:45:58 +0200 Subject: [PATCH 3/7] add cache --- .../containers/SafeDataViewProvider.tsx | 34 ++++++++++++------- .../data_view_manager/hooks/use_data_view.ts | 16 ++++++--- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx index d3cda3becfbac..1c4d42af836cc 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx @@ -5,7 +5,13 @@ * 2.0. */ -import React, { createContext, type ReactNode, type FC, type PropsWithChildren } from 'react'; +import React, { + createContext, + type ReactNode, + type FC, + type PropsWithChildren, + useMemo, +} from 'react'; import { type DataViewManagerScopeName } from '../constants'; import { useDataView } from '../hooks/use_data_view'; @@ -32,17 +38,21 @@ export const SafeDataViewProvider: FC { - const value: DataViewContextValue = { - results: {}, - }; - - scopes.forEach((scope) => { - // eslint-disable-next-line react-hooks/rules-of-hooks - const result = useDataView(scope); - value.results[scope] = result; - }); - - const allReady = Object.values(value.results).every((result) => result.status === 'ready'); + const results = scopes.map(useDataView); + const allReady = Object.values(results).every((result) => result.status === 'ready'); + const dataViews = Object.values(results).map((result) => result.dataView); + + const value = useMemo(() => { + return results.reduce( + (acc, result) => { + acc.results[result.scope] = result; + return acc; + }, + { results: {} } as DataViewContextValue + ); + // NOTE: below is done on purpose to cache based on the data view instance + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [...dataViews]); return ( 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..c3bbcee6c58ff 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 @@ -21,7 +21,11 @@ import type { SharedDataViewSelectionState } from '../redux/types'; */ export const useDataView = ( dataViewManagerScope: DataViewManagerScopeName = DataViewManagerScopeName.default -): { dataView: DataView | undefined; status: SharedDataViewSelectionState['status'] } => { +): { + dataView: DataView | undefined; + status: SharedDataViewSelectionState['status']; + scope: DataViewManagerScopeName; +} => { const { services: { dataViews }, notifications, @@ -58,9 +62,13 @@ export const useDataView = ( return useMemo(() => { if (!newDataViewPickerEnabled) { - return { dataView: undefined, status: internalStatus }; + return { dataView: undefined, status: internalStatus, scope: dataViewManagerScope }; } - return { dataView: retrievedDataView, status: retrievedDataView ? internalStatus : 'loading' }; - }, [newDataViewPickerEnabled, retrievedDataView, internalStatus]); + return { + dataView: retrievedDataView, + status: retrievedDataView ? internalStatus : 'loading', + scope: dataViewManagerScope, + }; + }, [newDataViewPickerEnabled, retrievedDataView, internalStatus, dataViewManagerScope]); }; From ca456bbf1c145554fa461ba1b76c13d80137c882 Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 25 Jun 2025 10:52:08 +0200 Subject: [PATCH 4/7] update jsdoc --- .../data_view_manager/containers/SafeDataViewProvider.tsx | 7 +++++++ .../public/data_view_manager/hooks/use_data_view_safe.ts | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx index 1c4d42af836cc..7e60cb09af00c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx @@ -23,7 +23,14 @@ export interface DataViewContextValue { export const DataViewContext = createContext(undefined); export interface SafeDataViewProviderProps { + /** + * Specify scopes that are required by the wrapped component + * Only these scopes will be available through useDataViewSafe. + */ scopes: readonly DataViewManagerScopeName[]; + /** + * Optional fallback component + */ fallback?: ReactNode; } diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts index ce7320e20b79b..08b637b2d1a7a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts @@ -10,7 +10,7 @@ import { DataViewContext } from '../containers/SafeDataViewProvider'; import { type DataViewManagerScopeName } from '../constants'; /** - * Returns data view that is guaranteed to be set, + * Returns data view instance that is guaranteed to be set (for the provided scope), * allowing you to skip the status check (if its loading or not). * The catch is that it can only be used inside the DataViewProvider (you can have many on the page). */ @@ -23,7 +23,7 @@ export const useDataViewSafe = (scope: DataViewManagerScopeName) => { if (!(scope in dataViewsPerScope.results)) { throw new Error( - 'No safeguards exist for requested scope, make sure it is configured where DataViewProvider is called' + 'No safeguards exist for requested scope, make sure it is included in `scopes` property of the wrapping DataViewProvider' ); } From a165083bb9211cda0958af5ac0f77c39a9fc170c Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 25 Jun 2025 14:35:19 +0200 Subject: [PATCH 5/7] remove use data view safe hook --- .../containers/SafeDataViewProvider.tsx | 7 +- .../data_view_manager/hooks/use_data_view.ts | 91 +++++++++++++++---- .../hooks/use_data_view_safe.ts | 42 --------- .../public/timelines/pages/timelines_page.tsx | 4 +- 4 files changed, 81 insertions(+), 63 deletions(-) delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx index 7e60cb09af00c..272665572792c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx @@ -14,10 +14,10 @@ import React, { } from 'react'; import { type DataViewManagerScopeName } from '../constants'; -import { useDataView } from '../hooks/use_data_view'; +import { useDataView, type UseDataViewAsyncReturnValue } from '../hooks/use_data_view'; export interface DataViewContextValue { - readonly results: Record>; + readonly results: Record; } export const DataViewContext = createContext(undefined); @@ -45,7 +45,8 @@ export const SafeDataViewProvider: FC { - const results = scopes.map(useDataView); + // eslint-disable-next-line react-hooks/rules-of-hooks + const results = scopes.map((scope) => useDataView(scope, false)); const allReady = Object.values(results).every((result) => result.status === 'ready'); const dataViews = Object.values(results).map((result) => result.dataView); 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 c3bbcee6c58ff..eb908886f57fe 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,7 +5,7 @@ * 2.0. */ -import { useEffect, useMemo, useState } from 'react'; +import { useContext, useEffect, useMemo, useState } from 'react'; import { type DataView } from '@kbn/data-views-plugin/public'; import { useSelector } from 'react-redux'; @@ -14,18 +14,32 @@ import { DataViewManagerScopeName } from '../constants'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { sourcererAdapterSelector } from '../redux/selectors'; import type { SharedDataViewSelectionState } from '../redux/types'; +import { DataViewContext } from '../containers/SafeDataViewProvider'; + +export interface UseDataViewAsyncReturnValue { + dataView: DataView | undefined; + status: SharedDataViewSelectionState['status']; + scope: DataViewManagerScopeName; +} + +type ConditionalReturn = IsSync extends true + ? DataView + : UseDataViewAsyncReturnValue; + +export { type DataView }; /* * 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']; - scope: DataViewManagerScopeName; -} => { +export const useDataView = ( + dataViewManagerScope: DataViewManagerScopeName = DataViewManagerScopeName.default, + /** + * If isSync is set to true, return value is guaranteed to be a data view instance, + * given there is a SafeDataViewProvider on top level for the current component tree + */ + isSync?: IsSync +): ConditionalReturn => { const { services: { dataViews }, notifications, @@ -39,6 +53,11 @@ export const useDataView = ( useEffect(() => { (async () => { + // NOTE: in sync mode, we are not firing useEffect again and reuse the top level data view + if (isSync) { + return; + } + if (!dataViewId || internalStatus !== 'ready') { return setRetrievedDataView(undefined); } @@ -58,17 +77,57 @@ export const useDataView = ( }); } })(); - }, [dataViews, dataViewId, internalStatus, notifications]); + }, [dataViews, dataViewId, internalStatus, notifications, isSync]); + + // TODO: naming, maybe extract this into separate function or hook + const dataViewsPerScope = useContext(DataViewContext); return useMemo(() => { + if (!isSync) { + if (!newDataViewPickerEnabled) { + return { + dataView: undefined, + status: internalStatus, + scope: dataViewManagerScope, + } as ConditionalReturn; + } + + return { + dataView: retrievedDataView, + status: retrievedDataView ? internalStatus : 'loading', + scope: dataViewManagerScope, + } as ConditionalReturn; + } + if (!newDataViewPickerEnabled) { - return { dataView: undefined, status: internalStatus, scope: dataViewManagerScope }; + return {} as ConditionalReturn; + } + + if (!dataViewsPerScope) { + throw new Error('You can only use useDataViewSafe inside DataViewProvider'); + } + + if (!(dataViewManagerScope in dataViewsPerScope.results)) { + throw new Error( + 'No safeguards exist for requested scope, make sure it is included in `scopes` property of the wrapping DataViewProvider' + ); + } + + const dataView = dataViewsPerScope.results[dataViewManagerScope].dataView; + + if (!dataView) { + throw new Error( + 'Missing data view. This error should not occur (earlier conditions should fire or the fallback should be still rendered instead)' + ); } - return { - dataView: retrievedDataView, - status: retrievedDataView ? internalStatus : 'loading', - scope: dataViewManagerScope, - }; - }, [newDataViewPickerEnabled, retrievedDataView, internalStatus, dataViewManagerScope]); + return dataView as ConditionalReturn; + }, [ + isSync, + newDataViewPickerEnabled, + dataViewsPerScope, + dataViewManagerScope, + retrievedDataView, + internalStatus, + ]); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts deleted file mode 100644 index 08b637b2d1a7a..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_safe.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 { useContext, useMemo } from 'react'; -import { DataViewContext } from '../containers/SafeDataViewProvider'; -import { type DataViewManagerScopeName } from '../constants'; - -/** - * Returns data view instance that is guaranteed to be set (for the provided scope), - * allowing you to skip the status check (if its loading or not). - * The catch is that it can only be used inside the DataViewProvider (you can have many on the page). - */ -export const useDataViewSafe = (scope: DataViewManagerScopeName) => { - const dataViewsPerScope = useContext(DataViewContext); - - if (!dataViewsPerScope) { - throw new Error('You can only use useDataViewSafe inside DataViewProvider'); - } - - if (!(scope in dataViewsPerScope.results)) { - throw new Error( - 'No safeguards exist for requested scope, make sure it is included in `scopes` property of the wrapping DataViewProvider' - ); - } - - const dataView = useMemo( - () => dataViewsPerScope.results[scope].dataView, - [dataViewsPerScope.results, scope] - ); - - if (!dataView) { - throw new Error( - 'Missing data view. This error should not occur (earlier conditions should fire or the fallback should be still rendered instead)' - ); - } - - return dataView; -}; 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 3d9455cb86d13..78acac3b74e0e 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,7 +22,7 @@ import { DataViewManagerScopeName } from '../../data_view_manager/constants'; import { useSourcererDataView } from '../../sourcerer/containers'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { SafeDataViewProvider } from '../../data_view_manager/containers/SafeDataViewProvider'; -import { useDataViewSafe } from '../../data_view_manager/hooks/use_data_view_safe'; +import { useDataView } from '../../data_view_manager/hooks/use_data_view'; export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; @@ -32,7 +32,7 @@ const TimelinesPageContent = () => { const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); const { indicesExist: oldIndicesExist } = useSourcererDataView(); - const dataView = useDataViewSafe(DataViewManagerScopeName.default); + const dataView = useDataView(DataViewManagerScopeName.default, true); const experimentalIndicesExist = !!dataView.matchedIndices.length; const indicesExist = newDataViewPickerEnabled ? experimentalIndicesExist : oldIndicesExist; From 47fba6eca421563f7b4696d7e0517ce087c09402 Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 25 Jun 2025 16:33:52 +0200 Subject: [PATCH 6/7] docs --- .../containers/SafeDataViewProvider.tsx | 4 ++-- .../public/data_view_manager/hooks/use_data_view.ts | 13 +++++++------ .../public/timelines/pages/timelines_page.tsx | 6 +++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx index 272665572792c..a1a7a37b6a62f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/containers/SafeDataViewProvider.tsx @@ -22,7 +22,7 @@ export interface DataViewContextValue { export const DataViewContext = createContext(undefined); -export interface SafeDataViewProviderProps { +export interface DataViewProviderProps { /** * Specify scopes that are required by the wrapped component * Only these scopes will be available through useDataViewSafe. @@ -40,7 +40,7 @@ const fallbackElement =
{`WIP Loading`}
; * Data view provider. We call it safe, because obtaining data view instance (for specified scopes) inside of it * does not require addtional checks for nullish value or loading state. */ -export const SafeDataViewProvider: FC> = ({ +export const DataViewProvider: FC> = ({ children, scopes, fallback = fallbackElement, 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 eb908886f57fe..e39e64d169e92 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 @@ -80,7 +80,7 @@ export const useDataView = ( }, [dataViews, dataViewId, internalStatus, notifications, isSync]); // TODO: naming, maybe extract this into separate function or hook - const dataViewsPerScope = useContext(DataViewContext); + const syncDataViewsContext = useContext(DataViewContext); return useMemo(() => { if (!isSync) { @@ -100,24 +100,25 @@ export const useDataView = ( } if (!newDataViewPickerEnabled) { + // TODO: remove this with when compatibility flag is no longer needed. This is for compatibility reasons only return {} as ConditionalReturn; } - if (!dataViewsPerScope) { + if (!syncDataViewsContext) { throw new Error('You can only use useDataViewSafe inside DataViewProvider'); } - if (!(dataViewManagerScope in dataViewsPerScope.results)) { + if (!(dataViewManagerScope in syncDataViewsContext.results)) { throw new Error( 'No safeguards exist for requested scope, make sure it is included in `scopes` property of the wrapping DataViewProvider' ); } - const dataView = dataViewsPerScope.results[dataViewManagerScope].dataView; + const dataView = syncDataViewsContext.results[dataViewManagerScope].dataView; if (!dataView) { throw new Error( - 'Missing data view. This error should not occur (earlier conditions should fire or the fallback should be still rendered instead)' + 'Missing data view. This error should not occur (earlier conditions should fire or the fallback should be rendered instead in the provider)' ); } @@ -125,7 +126,7 @@ export const useDataView = ( }, [ isSync, newDataViewPickerEnabled, - dataViewsPerScope, + syncDataViewsContext, dataViewManagerScope, retrievedDataView, internalStatus, 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 78acac3b74e0e..33937765f3cd8 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 @@ -21,7 +21,7 @@ import { SecurityRoutePageWrapper } from '../../common/components/security_route import { DataViewManagerScopeName } from '../../data_view_manager/constants'; import { useSourcererDataView } from '../../sourcerer/containers'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; -import { SafeDataViewProvider } from '../../data_view_manager/containers/SafeDataViewProvider'; +import { DataViewProvider } from '../../data_view_manager/containers/SafeDataViewProvider'; import { useDataView } from '../../data_view_manager/hooks/use_data_view'; export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; @@ -88,12 +88,12 @@ const TimelinesPageContent = () => { export const TimelinesPage = React.memo(() => { return ( - - + ); }); From e00693407e101105bd134eb54db7710fb4e28254 Mon Sep 17 00:00:00 2001 From: lgestc Date: Wed, 25 Jun 2025 16:35:08 +0200 Subject: [PATCH 7/7] docs --- .../public/data_view_manager/hooks/use_data_view.ts | 2 ++ 1 file changed, 2 insertions(+) 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 e39e64d169e92..95fcd15dd3ee1 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 @@ -84,6 +84,7 @@ export const useDataView = ( return useMemo(() => { if (!isSync) { + // TODO: remove this with when compatibility flag is no longer needed. This is for compatibility reasons only if (!newDataViewPickerEnabled) { return { dataView: undefined, @@ -108,6 +109,7 @@ export const useDataView = ( throw new Error('You can only use useDataViewSafe inside DataViewProvider'); } + // NOTE: check if requested scope is present in the preloaded data views if (!(dataViewManagerScope in syncDataViewsContext.results)) { throw new Error( 'No safeguards exist for requested scope, make sure it is included in `scopes` property of the wrapping DataViewProvider'