From 95f4cd559c9ce55401b689cff8bed493f0a4167b Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Tue, 10 Jun 2025 23:51:03 -0400 Subject: [PATCH] remove react query --- .../public/app/home/index.tsx | 6 +- .../assistant/send_to_timeline/index.tsx | 2 +- .../common/components/cell_actions/index.tsx | 14 +++- .../markdown_editor/plugins/insight/index.tsx | 4 +- .../containers/source/use_data_view.tsx | 8 ++- .../components/data_view_picker/index.tsx | 6 +- .../hooks/use_browser_fields.ts | 3 +- .../hooks/use_data_view_spec.ts | 24 +++++-- .../hooks/use_init_data_view_manager.ts | 2 + .../utils/data_view_spec_cache.ts | 68 +++++++++++++++++++ .../additional_toolbar_controls.tsx | 1 + .../components/fields_browser/index.tsx | 55 +++++++++++---- .../components/open_timeline/index.tsx | 5 +- .../timeline/body/unified_timeline_body.tsx | 9 ++- .../timelines/components/timeline/index.tsx | 5 +- .../timeline/query_bar/eql/index.tsx | 1 + .../components/timeline/tabs/esql/index.tsx | 1 + .../timelines/hooks/use_create_timeline.tsx | 3 +- 18 files changed, 185 insertions(+), 32 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/data_view_spec_cache.ts diff --git a/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx index a1d7ec1a5af5a..7e21be304dbc3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/app/home/index.tsx @@ -25,7 +25,7 @@ import { useUpgradeSecurityPackages } from '../../detection_engine/rule_manageme import { useSetupDetectionEngineHealthApi } from '../../detection_engine/rule_monitoring'; import { TopValuesPopover } from '../components/top_values_popover/top_values_popover'; import { AssistantOverlay } from '../../assistant/overlay'; -import { useInitSourcerer } from '../../sourcerer/containers/use_init_sourcerer'; +// import { useInitSourcerer } from '../../sourcerer/containers/use_init_sourcerer'; import { useInitDataViewManager } from '../../data_view_manager/hooks/use_init_data_view_manager'; import { useRestoreDataViewManagerStateFromURL } from '../../data_view_manager/hooks/use_sync_url_state'; import { useBrowserFields } from '../../data_view_manager/hooks/use_browser_fields'; @@ -41,7 +41,7 @@ const HomePageComponent: React.FC = ({ children }) => { const { pathname } = useLocation(); const sourcererScope = getScopeFromPath(pathname); - const { browserFields: oldBrowserFields } = useInitSourcerer(sourcererScope); + // const { browserFields: oldBrowserFields } = useInitSourcerer(sourcererScope); const { browserFields: experimentalBrowserFields } = useBrowserFields(sourcererScope); useRestoreDataViewManagerStateFromURL(useInitDataViewManager(), sourcererScope); @@ -51,7 +51,7 @@ const HomePageComponent: React.FC = ({ children }) => { useUpdateExecutionContext(); const browserFields = ( - newDataViewPickerEnabled ? experimentalBrowserFields : oldBrowserFields + newDataViewPickerEnabled ? experimentalBrowserFields : {} ) as BrowserFields; // side effect: this will attempt to upgrade the endpoint package if it is not up to date diff --git a/x-pack/solutions/security/plugins/security_solution/public/assistant/send_to_timeline/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/assistant/send_to_timeline/index.tsx index 43cf7ef997811..7037a45197b16 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/assistant/send_to_timeline/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/assistant/send_to_timeline/index.tsx @@ -67,7 +67,7 @@ export const SendToTimelineButton: FC = ({ children, ...props }) => { + const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); + const { dataViewSpec } = useDataViewSpec(sourcererScopeId); + const experimentalDataViewId = dataViewSpec?.id ?? ''; + const getFieldSpec = useGetFieldSpec(sourcererScopeId); - const dataViewId = useDataViewId(sourcererScopeId); + const oldDataViewId = useDataViewId(sourcererScopeId); + + const dataViewId = newDataViewPickerEnabled ? experimentalDataViewId : oldDataViewId; // Make a dependency key to prevent unnecessary re-renders when data object is defined inline // It is necessary because the data object is an array or an object and useMemo would always re-render const dependencyKey = JSON.stringify(data); @@ -74,12 +82,12 @@ export const SecurityCellActions: React.FC = ({ () => (Array.isArray(data) ? data : [data]) .map(({ field, value }) => ({ - field: getFieldSpec(field), + field: newDataViewPickerEnabled ? dataViewSpec.fields?.[field] : getFieldSpec(field), value, })) .filter((item): item is CellActionsData => !!item.field), // eslint-disable-next-line react-hooks/exhaustive-deps -- Use the dependencyKey to prevent unnecessary re-renders - [dependencyKey, getFieldSpec] + [dependencyKey, dataViewSpec, getFieldSpec, newDataViewPickerEnabled] ); const metadataWithDataView = useMemo(() => ({ ...metadata, dataViewId }), [dataViewId, metadata]); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/index.tsx index 91e790d9d1cf0..00ba145d9c72b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/index.tsx @@ -37,6 +37,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import type { Filter } from '@kbn/es-query'; import { FilterStateStore } from '@kbn/es-query'; import { useForm, FormProvider, useController } from 'react-hook-form'; +import { DataViewManagerScopeName } from '../../../../../data_view_manager/constants'; import { useDataView } from '../../../../../data_view_manager/hooks/use_data_view'; import { useIsExperimentalFeatureEnabled } from '../../../../hooks/use_experimental_features'; import { useUpsellingMessage } from '../../../../hooks/use_upselling'; @@ -291,7 +292,8 @@ const InsightEditorComponent = ({ const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); - const { dataViewSpec } = useDataViewSpec(); + // Only need the name + const { dataViewSpec } = useDataViewSpec(DataViewManagerScopeName.default, false); const sourcererDataView = newDataViewPickerEnabled ? dataViewSpec : oldSourcererDataView; const { diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/containers/source/use_data_view.tsx b/x-pack/solutions/security/plugins/security_solution/public/common/containers/source/use_data_view.tsx index 1dd1bf2877438..f841431a09397 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/containers/source/use_data_view.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/common/containers/source/use_data_view.tsx @@ -9,6 +9,7 @@ import { useCallback, useRef } from 'react'; import type { Subscription } from 'rxjs'; import { useDispatch } from 'react-redux'; import memoizeOne from 'memoize-one'; +import deepEqual from 'fast-deep-equal'; import type { BrowserFields } from '@kbn/timelines-plugin/common'; import type { DataViewSpec } from '@kbn/data-views-plugin/public'; import type { FieldCategory } from '@kbn/timelines-plugin/common/search_strategy'; @@ -66,9 +67,14 @@ export const getDataViewStateFromIndexFields = memoizeOne( return { browserFields: browserFields as DangerCastForBrowserFieldsMutation }; } }, - (newArgs, lastArgs) => newArgs[0] === lastArgs[0] && newArgs[1]?.length === lastArgs[1]?.length + (newArgs, lastArgs) => deepEqual(newArgs, lastArgs) ); +// This is a utility function to get an instance of the getDataViewStateFromIndexFields function +// If the original function is called in a hook called in different places, the memoization becomes potential useless +// as each call overrides the previous one. This hook is used to ensure that the memoization is preserved for each hook instance. +export const getMemoizedGetDataViewStateFromIndexFields = () => getDataViewStateFromIndexFields; + export const useDataView = (): { indexFieldsSearch: IndexFieldSearch; } => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx index fc95b83e940d7..87963913abc57 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/components/data_view_picker/index.tsx @@ -23,6 +23,7 @@ import { useManagedDataViews } from '../../hooks/use_managed_data_views'; import { useSavedDataViews } from '../../hooks/use_saved_data_views'; import { DEFAULT_SECURITY_DATA_VIEW, LOADING } from './translations'; import { DATA_VIEW_PICKER_TEST_ID } from './constants'; +import { dataViewSpecCache } from '../../utils/data_view_spec_cache'; interface DataViewPickerProps { /** @@ -49,7 +50,7 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie const closeDataViewEditor = useRef<() => void | undefined>(); const closeFieldEditor = useRef<() => void | undefined>(); - const { dataViewSpec, status } = useDataViewSpec(scope); + const { dataViewSpec, status } = useDataViewSpec(scope, false); const { adhocDataViews: adhocDataViewSpecs, defaultDataViewId } = useSelector(sharedStateSelector); @@ -69,6 +70,8 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie // hence - it is the only place where we should update the url param for the data view selection. const handleChangeDataView = useCallback( (id: string, indexPattern: string = '') => { + // Update the dataViewSpec cache when re-selecting it. + dataViewSpecCache.delete(id); selectDataView({ id, scope }); if (isDefaultSourcerer) { @@ -131,6 +134,7 @@ export const DataViewPicker = memo(({ scope, onClosePopover, disabled }: DataVie if (!updatedDataView.id) { return; } + dataViewSpecCache.delete(updatedDataView.id); handleChangeDataView(updatedDataView.id, updatedDataView.getIndexPattern()); }, [handleChangeDataView] diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_browser_fields.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_browser_fields.ts index f498e841a5791..1e32cdbfa7cd7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_browser_fields.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_browser_fields.ts @@ -9,7 +9,7 @@ import { useMemo } from 'react'; import type { BrowserFields } from '@kbn/timelines-plugin/common'; import { DataViewManagerScopeName } from '../constants'; import { useDataViewSpec } from './use_data_view_spec'; -import { getDataViewStateFromIndexFields } from '../../common/containers/source/use_data_view'; +import { getMemoizedGetDataViewStateFromIndexFields } from '../../common/containers/source/use_data_view'; export const useBrowserFields = ( scope: DataViewManagerScopeName = DataViewManagerScopeName.default @@ -21,6 +21,7 @@ export const useBrowserFields = ( return {}; } + const getDataViewStateFromIndexFields = getMemoizedGetDataViewStateFromIndexFields(); const { browserFields } = getDataViewStateFromIndexFields( dataViewSpec?.title ?? '', dataViewSpec.fields diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_spec.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_spec.ts index a89524559c25d..a0505b5ff439d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_spec.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_data_view_spec.ts @@ -9,6 +9,7 @@ import { useMemo } from 'react'; import type { DataViewSpec, SharedDataViewSelectionState } from '../redux/types'; import { DataViewManagerScopeName } from '../constants'; import { useDataView } from './use_data_view'; +import { dataViewSpecCache } from '../utils/data_view_spec_cache'; export interface UseDataViewSpecResult { /** @@ -25,14 +26,25 @@ export interface UseDataViewSpecResult { * Returns an object with the dataViewSpec and status values for the given scopeName. */ export const useDataViewSpec = ( - scopeName: DataViewManagerScopeName = DataViewManagerScopeName.default + scopeName: DataViewManagerScopeName = DataViewManagerScopeName.default, + // This can be prohibitively expensive with sufficient enough fields and called in enough components + includeFields: boolean = true ): UseDataViewSpecResult => { const { dataView, status } = useDataView(scopeName); + const cachedSpec = dataViewSpecCache.get(dataView?.id ?? ''); + const shouldUpdateCacheWithFieldsInformation = cachedSpec && !cachedSpec.fields && includeFields; + + if (dataView?.id && (!cachedSpec || shouldUpdateCacheWithFieldsInformation)) { + // Cache the DataViewSpec to avoid recalculating it every time the hook is called + dataViewSpecCache.set(dataView?.id, dataView.toSpec?.(includeFields)); + } + return useMemo(() => { // NOTE: remove this after we are ready for undefined (lazy) data view everywhere in the app // https://github.com/elastic/security-team/issues/11959 - if (!dataView) { + // every dataView should have the saved object id + if (!dataView || !dataView.id) { return { dataViewSpec: { id: '', @@ -41,7 +53,9 @@ export const useDataViewSpec = ( status, }; } - - return { dataViewSpec: dataView?.toSpec?.(), status }; - }, [dataView, status]); + const dataViewSpec = cachedSpec ?? dataView.toSpec?.(includeFields); + // TODO: (DV_PICKER) Remove this in the cleanup phase, just here for testing purposes + dataViewSpecCache.log(); + return { dataViewSpec, status }; + }, [cachedSpec, dataView, includeFields, status]); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_init_data_view_manager.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_init_data_view_manager.ts index 36e27d58cae57..e5f711f7c7164 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_init_data_view_manager.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/hooks/use_init_data_view_manager.ts @@ -21,6 +21,7 @@ import { sharedDataViewManagerSlice } from '../redux/slices'; import { useUserInfo } from '../../detections/components/user_info'; import { type SelectDataViewAsyncPayload } from '../redux/actions'; import { DataViewManagerScopeName } from '../constants'; +import { dataViewSpecCache } from '../utils/data_view_spec_cache'; type OriginalListener = Parameters[0]; @@ -105,6 +106,7 @@ export const useInitDataViewManager = () => { listeners.forEach((dataViewSelectedListener) => { dispatch(removeListener(dataViewSelectedListener)); }); + dataViewSpecCache.clear(); }; }, [ dispatch, diff --git a/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/data_view_spec_cache.ts b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/data_view_spec_cache.ts new file mode 100644 index 0000000000000..e9c91824f4fe5 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/data_view_manager/utils/data_view_spec_cache.ts @@ -0,0 +1,68 @@ +/* + * 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 type { DataViewSpec } from '@kbn/data-views-plugin/common'; + +/** + * @description We use a cache here rather than redux because the dataViewSpec is not + * serializable and we don't want to store it in the redux store. + * An alternative could be to store it in context of some sort at the top level of the application, + * but I would rather not mix context and redux for this use case. + */ + +export class DataViewSpecCache { + // Ensure one cache is only ever created + static #instance: DataViewSpecCache | null = null; + private cache: Map = new Map(); + + constructor() { + if (!DataViewSpecCache.#instance) { + DataViewSpecCache.#instance = this; + } + return DataViewSpecCache.#instance; + } + + get(dataViewId: string): DataViewSpec | undefined { + return this.cache.get(dataViewId); + } + + set(dataViewId: string, dataViewSpec: DataViewSpec): void { + this.cache.set(dataViewId, dataViewSpec); + } + + clear(): void { + this.cache.clear(); + } + /** + * Deletes the DataViewSpec from the cache by its ID. + * @param dataViewId - The ID of the DataViewSpec to delete. + */ + delete(dataViewId: string): void { + this.cache.delete(dataViewId); + } + + has(dataViewId: string): boolean { + return this.cache.has(dataViewId); + } + + size(): number { + return this.cache.size; + } + + // TODO: (DV_PICKER) Remove this in the cleanup phase, just here for testing purposes + log(): void { + console.debug('!!DataViewSpecCache contents:', Array.from(this.cache.entries())); + } +} + +/** + * @description This cache is used to store the DataViewSpec objects to avoid recalculating them + * every time the useDataViewSpec hook is called. This is particularly useful + * when the dataView has a large number of fields, as the toSpec() method can be + * expensive to compute. + */ +export const dataViewSpecCache = new DataViewSpecCache(); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/additional_toolbar_controls.tsx b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/additional_toolbar_controls.tsx index 02c55cae8785b..150e0f641b667 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/additional_toolbar_controls.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_table/additional_toolbar_controls.tsx @@ -80,6 +80,7 @@ const AdditionalToolbarControlsComponent = ({ [dispatch, tableType, trackGroupChange] ); + // TODO: (DV_PICKER) This can be generalized to just return the fields for the current data view. const fields = useMemo(() => { return Object.values(sourcererDataView.fields || {}); }, [sourcererDataView.fields]); diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/fields_browser/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/fields_browser/index.tsx index c9acc48051911..d76336820e3bd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/fields_browser/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/fields_browser/index.tsx @@ -13,8 +13,11 @@ import type { CreateFieldComponent, GetFieldTableColumns, } from '@kbn/response-ops-alerts-fields-browser/types'; +import { dataViewSpecCache } from '../../../data_view_manager/utils/data_view_spec_cache'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; +import { useDataView } from '../../../data_view_manager/hooks/use_data_view'; import type { ColumnHeaderOptions } from '../../../../common/types'; -import { useDataView } from '../../../common/containers/source/use_data_view'; +import { useDataView as useDataViewOld } from '../../../common/containers/source/use_data_view'; import { useKibana } from '../../../common/lib/kibana'; import { sourcererSelectors } from '../../../common/store'; import type { State } from '../../../common/store'; @@ -50,10 +53,12 @@ export const useFieldBrowserOptions: UseFieldBrowserOptions = ({ removeColumn, upsertColumn, }) => { + const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); const [dataView, setDataView] = useState(null); + const { dataView: experimentalDataView } = useDataView(sourcererScope); const { startTransaction } = useStartTransaction(); - const { indexFieldsSearch } = useDataView(); + const { indexFieldsSearch } = useDataViewOld(); const { dataViewFieldEditor, data: { dataViews }, @@ -61,12 +66,18 @@ export const useFieldBrowserOptions: UseFieldBrowserOptions = ({ const missingPatterns = useSelector((state: State) => { return sourcererSelectors.sourcererScopeMissingPatterns(state, sourcererScope); }); - const selectedDataViewId = useSelector((state: State) => { - return sourcererSelectors.sourcererScopeSelectedDataViewId(state, sourcererScope); - }); + const selectedDataViewId = useMemo( + () => (newDataViewPickerEnabled ? experimentalDataView?.id : dataView?.id), + [dataView?.id, experimentalDataView?.id, newDataViewPickerEnabled] + ); + useEffect(() => { let ignore = false; const fetchAndSetDataView = async (dataViewId: string) => { + if (newDataViewPickerEnabled) { + if (experimentalDataView) setDataView(experimentalDataView); + return; + } const aDatView = await dataViews.get(dataViewId); if (ignore) return; setDataView(aDatView); @@ -78,7 +89,13 @@ export const useFieldBrowserOptions: UseFieldBrowserOptions = ({ return () => { ignore = true; }; - }, [selectedDataViewId, missingPatterns, dataViews]); + }, [ + selectedDataViewId, + missingPatterns, + dataViews, + newDataViewPickerEnabled, + experimentalDataView, + ]); const openFieldEditor = useCallback( async (fieldName) => { @@ -90,7 +107,12 @@ export const useFieldBrowserOptions: UseFieldBrowserOptions = ({ startTransaction({ name: FIELD_BROWSER_ACTIONS.FIELD_SAVED }); // Fetch the updated list of fields // Using cleanCache since the number of fields might have not changed, but we need to update the state anyway - await indexFieldsSearch({ dataViewId: selectedDataViewId, cleanCache: true }); + if (newDataViewPickerEnabled) { + dataViewSpecCache.delete(selectedDataViewId); + await dataViews.clearInstanceCache(selectedDataViewId); + } else { + await indexFieldsSearch({ dataViewId: selectedDataViewId, cleanCache: true }); + } for (const savedField of savedFields) { if (fieldName && fieldName !== savedField.name) { @@ -129,10 +151,12 @@ export const useFieldBrowserOptions: UseFieldBrowserOptions = ({ selectedDataViewId, dataViewFieldEditor, editorActionsRef, + startTransaction, + newDataViewPickerEnabled, + dataViews, indexFieldsSearch, - removeColumn, upsertColumn, - startTransaction, + removeColumn, ] ); @@ -146,7 +170,12 @@ export const useFieldBrowserOptions: UseFieldBrowserOptions = ({ startTransaction({ name: FIELD_BROWSER_ACTIONS.FIELD_DELETED }); // Fetch the updated list of fields - await indexFieldsSearch({ dataViewId: selectedDataViewId }); + if (newDataViewPickerEnabled) { + dataViewSpecCache.delete(selectedDataViewId); + await dataViews.clearInstanceCache(selectedDataViewId); + } else { + await indexFieldsSearch({ dataViewId: selectedDataViewId, cleanCache: true }); + } removeColumn(fieldName); }, @@ -157,9 +186,11 @@ export const useFieldBrowserOptions: UseFieldBrowserOptions = ({ dataView, selectedDataViewId, dataViewFieldEditor, - indexFieldsSearch, - removeColumn, startTransaction, + newDataViewPickerEnabled, + removeColumn, + dataViews, + indexFieldsSearch, ] ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/index.tsx index 0fdbb03484b54..254df7fc933d0 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/open_timeline/index.tsx @@ -164,7 +164,10 @@ export const StatefulOpenTimelineComponent = React.memo( useSourcererDataView(SourcererScopeName.timeline); const { newDataViewPickerEnabled } = useEnableExperimental(); - const { dataViewSpec: experimentalDataViewSpec } = useDataViewSpec(SourcererScopeName.timeline); + const { dataViewSpec: experimentalDataViewSpec } = useDataViewSpec( + SourcererScopeName.timeline, + false + ); const experimentalSelectedPatterns = useSelectedPatterns(SourcererScopeName.timeline); const dataViewId = useMemo( diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.tsx b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.tsx index fe037890f126b..bae42380ded73 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.tsx @@ -8,6 +8,7 @@ import type { ComponentProps, ReactElement } from 'react'; import React, { useMemo } from 'react'; import { RootDragDropProvider } from '@kbn/dom-drag-drop'; +import { EuiSkeletonText } from '@elastic/eui'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { useDataView } from '../../../../data_view_manager/hooks/use_data_view'; import { useGetScopedSourcererDataView } from '../../../../sourcerer/components/use_get_sourcerer_data_view'; @@ -48,11 +49,17 @@ export const UnifiedTimelineBody = (props: UnifiedTimelineBodyProps) => { }); const columnsHeader = useMemo(() => columns ?? defaultUdtHeaders, [columns]); - const { dataView: experimentalDataView } = useDataView(SourcererScopeName.timeline); + const { dataView: experimentalDataView, status: dataViewStatus } = useDataView( + SourcererScopeName.timeline + ); const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); const dataView = newDataViewPickerEnabled ? experimentalDataView : oldDataView; + if (dataViewStatus === 'loading') { + return ; + } + return ( {header} diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/index.tsx index e1c24552e48ab..aac678121f78e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/index.tsx @@ -104,7 +104,10 @@ const StatefulTimelineComponent: React.FC = ({ const newDataViewPickerEnabled = useIsExperimentalFeatureEnabled('newDataViewPickerEnabled'); const experimentalSelectedPatterns = useSelectedPatterns(SourcererScopeName.timeline); - const { dataViewSpec: experimentalDataViewSpec } = useDataViewSpec(SourcererScopeName.timeline); + const { dataViewSpec: experimentalDataViewSpec } = useDataViewSpec( + SourcererScopeName.timeline, + false + ); const selectedDataViewId = useMemo( () => diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx index 6345568d21a41..849b82a1e5f8d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx @@ -182,6 +182,7 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) () => ({ ...dataViewSpec, title: dataViewSpec.title ?? '', + // TODO: (DV_PICKER) We can cache the dataView spec fields in redux and just pull them from there fields: Object.values(dataViewSpec.fields || {}), }), [dataViewSpec] diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/tabs/esql/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/tabs/esql/index.tsx index 2447bb787ce46..d9bb932a6ebad 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/tabs/esql/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/tabs/esql/index.tsx @@ -84,6 +84,7 @@ export const DiscoverTabContent: FC = ({ timelineId }) // TODO: (DV_PICKER) should not be here, used to make discover container work I suppose useEffect(() => { if (!dataViewId) return; + // TODO: (DV_PICKER) we have the spec with the new data, we should not need to fetch it again dataViewService.get(dataViewId).then((dv) => setDataViewSpec(dv?.toSpec?.())); }, [dataViewId, dataViewService]); diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/hooks/use_create_timeline.tsx b/x-pack/solutions/security/plugins/security_solution/public/timelines/hooks/use_create_timeline.tsx index 653c3943efd85..90f8c43b99a26 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/hooks/use_create_timeline.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/hooks/use_create_timeline.tsx @@ -60,7 +60,8 @@ export const useCreateTimeline = ({ const { newDataViewPickerEnabled } = useEnableExperimental(); const { dataViewSpec: experimentalDataViewSpec } = useDataViewSpec( - DataViewManagerScopeName.default + DataViewManagerScopeName.default, + false ); const experimentalSelectedPatterns = useSelectedPatterns(DataViewManagerScopeName.default);