From 9edb58f7b690380c9b9f1ab2545fde76da082871 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Thu, 15 Jun 2023 14:57:15 +0200 Subject: [PATCH] [Security Solution] Improve rules exception flyout opening for the indices with huge amount of fields (#159216) ## Summary Original ticket: [#158751](https://github.com/elastic/kibana/issues/158751) These changes improve the rule's exceptions flyout opening experience. We had a few complaints that it is very slow to open it and sometimes it throws an exception about the limited response size. To fix this, we decided to load extended field's data (conflicts and unmapped info) only when user selects some field instead of fetching this data for all fields on flyout opening. ## NOTES: After these changes we gonna do next steps related to fields loading when user creates/edits rule exceptions: 1. We will call `_fields_for_wildcard` **WITHOUT** `include_unmapped=true` parameter to fetch all fields specs on exception flyout loading 2. We will call `_fields_for_wildcard` **WITH** `include_unmapped=true` for only one field when user selects it from the dropdown menu With these changes we will improve slow exception flyout opening when user has lots of fields which are unmapped in different indices. If for some reason user has a lot of (thousands) conflicting fields around indices then the loading is still might be slow as the `_fields_for_wildcard` call will return conflicts information even without `include_unmapped=true` parameter. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit 31b34771c5e6f710858a7f617bbca04537cf5c1b) --- .../builder/entry_renderer.test.tsx | 15 +++-- .../components/builder/entry_renderer.tsx | 41 +++++++++++--- .../builder/exception_item_renderer.tsx | 4 ++ .../builder/exception_items_renderer.tsx | 4 ++ .../public/common/containers/source/index.tsx | 24 ++------ .../containers/source/use_data_view.tsx | 11 +--- .../common/containers/sourcerer/index.tsx | 3 +- .../add_exception_flyout/index.test.tsx | 2 + .../components/add_exception_flyout/index.tsx | 3 +- .../edit_exception_flyout/index.test.tsx | 2 + .../edit_exception_flyout/index.tsx | 3 +- .../item_conditions/index.tsx | 5 ++ .../logic/use_exception_flyout_data.tsx | 56 ++++++++++++------- 13 files changed, 107 insertions(+), 66 deletions(-) diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx index f2b820fe717693..b73379acd1518c 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx @@ -197,14 +197,15 @@ describe('BuilderEntryItem', () => { ); }); - test('it render mapping issues warning text when field has mapping conflicts', () => { + test('it render mapping issues warning text when field has mapping conflicts', async () => { + const field = getField('mapping issues'); wrapper = mount( { setWarningsExist={jest.fn()} showLabel allowCustomOptions + getExtendedFields={(): Promise => Promise.resolve([field])} /> ); - expect(wrapper.find('.euiFormHelpText.euiFormRow__text').text()).toMatch( - /This field is defined as different types across the following indices or is unmapped. This can cause unexpected query results./ - ); + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('.euiFormHelpText.euiFormRow__text').text()).toMatch( + /This field is defined as different types across the following indices or is unmapped. This can cause unexpected query results./ + ); + }); }); test('it renders field values correctly when operator is "isOperator"', () => { diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx index e5a5cf4fd14ba0..1837e958581587 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiAccordion, @@ -26,6 +26,7 @@ import { } from '@kbn/securitysolution-io-ts-list-types'; import { BuilderEntry, + DataViewField, EXCEPTION_OPERATORS_ONLY_LISTS, FormattedBuilderEntry, OperatorOption, @@ -87,6 +88,7 @@ export interface EntryItemProps { isDisabled?: boolean; operatorsList?: OperatorOption[]; allowCustomOptions?: boolean; + getExtendedFields?: (fields: string[]) => Promise; } export const BuilderEntryItem: React.FC = ({ @@ -106,6 +108,7 @@ export const BuilderEntryItem: React.FC = ({ isDisabled = false, operatorsList, allowCustomOptions = false, + getExtendedFields, }): JSX.Element => { const sPaddingSize = useEuiPaddingSize('s'); @@ -175,6 +178,22 @@ export const BuilderEntryItem: React.FC = ({ [onChange, entry] ); + const [extendedField, setExtendedField] = useState(null); + useEffect(() => { + if (!entry.field?.name) { + setExtendedField(null); + } + const fetchExtendedField = async (): Promise => { + const fieldName = entry.field?.name; + if (getExtendedFields && fieldName) { + const extendedFields = await getExtendedFields([fieldName]); + const field = extendedFields.find((f) => f.name === fieldName) ?? null; + setExtendedField(field); + } + }; + fetchExtendedField(); + }, [entry.field?.name, getExtendedFields]); + const isFieldComponentDisabled = useMemo( (): boolean => isDisabled || @@ -212,8 +231,11 @@ export const BuilderEntryItem: React.FC = ({ ); const warningIconCss = { marginRight: `${sPaddingSize}` }; - const getMappingConflictsWarning = (field: DataViewFieldBase): React.ReactNode | null => { - const conflictsInfo = getMappingConflictsInfo(field); + const getMappingConflictsWarning = (): React.ReactNode | null => { + if (!extendedField) { + return null; + } + const conflictsInfo = getMappingConflictsInfo(extendedField); if (!conflictsInfo) { return null; } @@ -238,13 +260,13 @@ export const BuilderEntryItem: React.FC = ({ data-test-subj="mappingConflictsAccordion" >
- {conflictsInfo.map((info) => { + {conflictsInfo.map((info, idx) => { const groupDetails = info.groupedIndices.map( ({ name, count }) => `${count > 1 ? i18n.CONFLICT_MULTIPLE_INDEX_DESCRIPTION(name, count) : name}` ); return ( - <> + {`${ info.totalIndexCount > 1 @@ -254,7 +276,7 @@ export const BuilderEntryItem: React.FC = ({ ) : info.type }: ${groupDetails.join(', ')}`} - + ); })} @@ -268,12 +290,12 @@ export const BuilderEntryItem: React.FC = ({ entry.nested == null && allowCustomOptions ? i18n.CUSTOM_COMBOBOX_OPTION_TEXT : undefined; const helpText = - entry.field?.conflictDescriptions == null ? ( + extendedField?.conflictDescriptions == null ? ( customOptionText ) : ( <> {customOptionText} - {getMappingConflictsWarning(entry.field)} + {getMappingConflictsWarning()} ); return ( @@ -295,8 +317,9 @@ export const BuilderEntryItem: React.FC = ({ osTypes, isDisabled, handleFieldChange, - sPaddingSize, allowCustomOptions, + sPaddingSize, + extendedField, ] ); diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.tsx index 8d872cd23cbbfb..ea3444155955eb 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.tsx @@ -13,6 +13,7 @@ import { HttpStart } from '@kbn/core/public'; import { ExceptionListType, OsTypeArray } from '@kbn/securitysolution-io-ts-list-types'; import { BuilderEntry, + DataViewField, ExceptionsBuilderExceptionItem, FormattedBuilderEntry, OperatorOption, @@ -65,6 +66,7 @@ interface BuilderExceptionListItemProps { isDisabled?: boolean; operatorsList?: OperatorOption[]; allowCustomOptions?: boolean; + getExtendedFields?: (fields: string[]) => Promise; } export const BuilderExceptionListItemComponent = React.memo( @@ -88,6 +90,7 @@ export const BuilderExceptionListItemComponent = React.memo { const handleEntryChange = useCallback( (entry: BuilderEntry, entryIndex: number): void => { @@ -161,6 +164,7 @@ export const BuilderExceptionListItemComponent = React.memo Promise; } export const ExceptionBuilderComponent = ({ @@ -121,6 +123,7 @@ export const ExceptionBuilderComponent = ({ osTypes, operatorsList, allowCustomFieldOptions = false, + getExtendedFields, }: ExceptionBuilderProps): JSX.Element => { const [state, dispatch] = useReducer(exceptionsBuilderReducer(), { ...initialState, @@ -442,6 +445,7 @@ export const ExceptionBuilderComponent = ({ isDisabled={isDisabled} operatorsList={operatorsList} allowCustomOptions={allowCustomFieldOptions} + getExtendedFields={getExtendedFields} /> diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index 7f259cdf403e3b..5e02ca2e11c589 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -42,11 +42,7 @@ export const getAllFieldsByName = ( keyBy('name', getAllBrowserFields(browserFields)); export const getIndexFields = memoizeOne( - ( - title: string, - fields: IIndexPatternFieldList, - _includeUnmapped: boolean = false - ): DataViewBase => + (title: string, fields: IIndexPatternFieldList): DataViewBase => fields && fields.length > 0 ? { fields: fields.map((field) => @@ -66,10 +62,7 @@ export const getIndexFields = memoizeOne( title, } : { fields: [], title }, - (newArgs, lastArgs) => - newArgs[0] === lastArgs[0] && - newArgs[1].length === lastArgs[1].length && - newArgs[2] === lastArgs[2] + (newArgs, lastArgs) => newArgs[0] === lastArgs[0] && newArgs[1].length === lastArgs[1].length ); const DEFAULT_BROWSER_FIELDS = {}; @@ -96,8 +89,7 @@ interface FetchIndexReturn { export const useFetchIndex = ( indexNames: string[], onlyCheckIfIndicesExist: boolean = false, - strategy: 'indexFields' | 'dataView' | typeof ENDPOINT_FIELDS_SEARCH_STRATEGY = 'indexFields', - includeUnmapped: boolean = false + strategy: 'indexFields' | 'dataView' | typeof ENDPOINT_FIELDS_SEARCH_STRATEGY = 'indexFields' ): [boolean, FetchIndexReturn] => { const { data } = useKibana().services; const abortCtrl = useRef(new AbortController()); @@ -121,11 +113,7 @@ export const useFetchIndex = ( abortCtrl.current = new AbortController(); const dv = await data.dataViews.create({ title: iNames.join(','), allowNoIndex: true }); const dataView = dv.toSpec(); - const { browserFields } = getDataViewStateFromIndexFields( - iNames, - dataView.fields, - includeUnmapped - ); + const { browserFields } = getDataViewStateFromIndexFields(iNames, dataView.fields); previousIndexesName.current = dv.getIndexPattern().split(','); @@ -135,7 +123,7 @@ export const useFetchIndex = ( browserFields, indexes: dv.getIndexPattern().split(','), indexExists: dv.getIndexPattern().split(',').length > 0, - indexPatterns: getIndexFields(dv.getIndexPattern(), dv.fields, includeUnmapped), + indexPatterns: getIndexFields(dv.getIndexPattern(), dv.fields), }); } catch (exc) { setState({ @@ -152,7 +140,7 @@ export const useFetchIndex = ( asyncSearch(); }, - [addError, data.dataViews, includeUnmapped, indexNames, state] + [addError, data.dataViews, indexNames, state] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx b/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx index 46bbd92f21c79e..c36b5a2b2981e1 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx @@ -48,11 +48,7 @@ interface DataViewInfo { * VERY mutatious on purpose to improve the performance of the transform. */ export const getDataViewStateFromIndexFields = memoizeOne( - ( - _title: string, - fields: DataViewSpec['fields'], - _includeUnmapped: boolean = false - ): DataViewInfo => { + (_title: string, fields: DataViewSpec['fields']): DataViewInfo => { // Adds two dangerous casts to allow for mutations within this function type DangerCastForMutation = Record; if (fields == null) { @@ -72,10 +68,7 @@ export const getDataViewStateFromIndexFields = memoizeOne( return { browserFields: browserFields as DangerCastForBrowserFieldsMutation }; } }, - (newArgs, lastArgs) => - newArgs[0] === lastArgs[0] && - newArgs[1]?.length === lastArgs[1]?.length && - newArgs[2] === lastArgs[2] + (newArgs, lastArgs) => newArgs[0] === lastArgs[0] && newArgs[1]?.length === lastArgs[1]?.length ); export const useDataView = (): { diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx index f17fccf7f29ed3..fdbd6d9d530e1d 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx @@ -438,8 +438,7 @@ export const useSourcererDataView = ( const browserFields = useCallback(() => { const { browserFields: dataViewBrowserFields } = getDataViewStateFromIndexFields( sourcererDataView.patternList.join(','), - sourcererDataView.fields, - false + sourcererDataView.fields ); return dataViewBrowserFields; }, [sourcererDataView.fields, sourcererDataView.patternList]); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx index e750b0ddc75bbb..3d47ae518e48b2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx @@ -85,6 +85,7 @@ describe('When the add exception modal is opened', () => { mockFetchIndexPatterns.mockImplementation(() => ({ isLoading: false, indexPatterns: stubIndexPattern, + getExtendedFields: () => Promise.resolve([]), })); mockUseSignalIndex.mockImplementation(() => ({ @@ -153,6 +154,7 @@ describe('When the add exception modal is opened', () => { mockFetchIndexPatterns.mockImplementation(() => ({ isLoading: true, indexPatterns: { fields: [], title: 'foo' }, + getExtendedFields: () => Promise.resolve([]), })); wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx index e0979730b7d7c2..11b2df8271d665 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx @@ -114,7 +114,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ onCancel, onConfirm, }: AddExceptionFlyoutProps) { - const { isLoading, indexPatterns } = useFetchIndexPatterns(rules); + const { isLoading, indexPatterns, getExtendedFields } = useFetchIndexPatterns(rules); const [isSubmitting, submitNewExceptionItems] = useAddNewExceptionItems(); const [isClosingAlerts, closeAlerts] = useCloseAlertsFromExceptions(); const invalidateFetchRuleByIdQuery = useInvalidateFetchRuleByIdQuery(); @@ -502,6 +502,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ onExceptionItemAdd={setExceptionItemsToAdd} onSetErrorExists={setConditionsValidationError} onFilterIndexPatterns={filterIndexPatterns} + getExtendedFields={getExtendedFields} /> {listType !== ExceptionListTypeEnum.ENDPOINT && !sharedListToAddTo?.length && ( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx index 09ed03130ef08c..19a4aa0ac8f755 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx @@ -124,6 +124,7 @@ describe('When the edit exception modal is opened', () => { mockFetchIndexPatterns.mockImplementation(() => ({ isLoading: false, indexPatterns: stubIndexPattern, + getExtendedFields: () => Promise.resolve([]), })); mockUseFindExceptionListReferences.mockImplementation(() => [ false, @@ -168,6 +169,7 @@ describe('When the edit exception modal is opened', () => { mockFetchIndexPatterns.mockImplementation(() => ({ isLoading: true, indexPatterns: { fields: [], title: 'foo' }, + getExtendedFields: () => Promise.resolve([]), })); const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx index 1dd03d8bc9f9d6..e030ae045397fd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx @@ -109,7 +109,7 @@ const EditExceptionFlyoutComponent: React.FC = ({ const rules = useMemo(() => (rule != null ? [rule] : null), [rule]); const listType = useMemo((): ExceptionListTypeEnum => list.type as ExceptionListTypeEnum, [list]); - const { isLoading, indexPatterns } = useFetchIndexPatterns(rules); + const { isLoading, indexPatterns, getExtendedFields } = useFetchIndexPatterns(rules); const [isSubmitting, submitEditExceptionItems] = useEditExceptionItems(); const [isClosingAlerts, closeAlerts] = useCloseAlertsFromExceptions(); @@ -370,6 +370,7 @@ const EditExceptionFlyoutComponent: React.FC = ({ onExceptionItemAdd={setExceptionItemsToAdd} onSetErrorExists={setConditionsValidationError} onFilterIndexPatterns={filterIndexPatterns} + getExtendedFields={getExtendedFields} /> {!openedFromListDetailPage && listType === ExceptionListTypeEnum.DETECTION && ( <> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/item_conditions/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/item_conditions/index.tsx index c1f11b17cbbd97..98983a708a815d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/item_conditions/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/item_conditions/index.tsx @@ -20,6 +20,7 @@ import type { } from '@kbn/securitysolution-io-ts-list-types'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import type { + DataViewField, ExceptionsBuilderExceptionItem, ExceptionsBuilderReturnExceptionItem, } from '@kbn/securitysolution-list-utils'; @@ -89,6 +90,8 @@ interface ExceptionsFlyoutConditionsComponentProps { type: ExceptionListType, osTypes?: Array<'linux' | 'macos' | 'windows'> | undefined ) => DataViewBase; + + getExtendedFields?: (fields: string[]) => Promise; } const ExceptionsConditionsComponent: React.FC = ({ @@ -105,6 +108,7 @@ const ExceptionsConditionsComponent: React.FC { const { http, unifiedSearch } = useKibana().services; const isEndpointException = useMemo( @@ -267,6 +271,7 @@ const ExceptionsConditionsComponent: React.FC ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_exception_flyout_data.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_exception_flyout_data.tsx index d13ad7c2ed174b..4c126311b6d18d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_exception_flyout_data.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_exception_flyout_data.tsx @@ -5,9 +5,11 @@ * 2.0. */ -import { useEffect, useState, useMemo } from 'react'; +import { useEffect, useState, useMemo, useCallback } from 'react'; import type { DataViewBase } from '@kbn/es-query'; +import type { FieldSpec, DataViewSpec } from '@kbn/data-views-plugin/common'; + import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import type { Rule } from '../../rule_management/logic/types'; import { useGetInstalledJob } from '../../../common/components/ml/hooks/use_get_jobs'; @@ -19,6 +21,7 @@ import * as i18n from '../../../common/containers/source/translations'; export interface ReturnUseFetchExceptionFlyoutData { isLoading: boolean; indexPatterns: DataViewBase; + getExtendedFields: (fields: string[]) => Promise; } /** @@ -84,15 +87,14 @@ export const useFetchIndexPatterns = (rules: Rule[] | null): ReturnUseFetchExcep } }, [jobs, isMLRule, memoDataViewId, memoNonDataViewIndexPatterns]); - const [isIndexPatternLoading, { indexPatterns: indexIndexPatterns }] = useFetchIndex( - memoRuleIndices, - false, - 'indexFields', - true - ); + const [ + isIndexPatternLoading, + { indexPatterns: indexIndexPatterns, dataView: indexDataViewSpec }, + ] = useFetchIndex(memoRuleIndices, false, 'indexFields'); // Data view logic const [dataViewIndexPatterns, setDataViewIndexPatterns] = useState(null); + const [dataViewSpec, setDataViewSpec] = useState(null); useEffect(() => { const fetchSingleDataView = async () => { // ensure the memoized data view includes a space id, otherwise @@ -101,25 +103,36 @@ export const useFetchIndexPatterns = (rules: Rule[] | null): ReturnUseFetchExcep if (activeSpaceId !== '' && memoDataViewId) { setDataViewLoading(true); const dv = await data.dataViews.get(memoDataViewId); - let fieldsWithUnmappedInfo = null; - try { - fieldsWithUnmappedInfo = await data.dataViews.getFieldsForIndexPattern(dv, { - pattern: '', - includeUnmapped: true, - }); - } catch (error) { - addWarning(error, { title: i18n.FETCH_FIELDS_WITH_UNMAPPED_DATA_ERROR }); - } setDataViewLoading(false); - setDataViewIndexPatterns({ - ...dv, - ...(fieldsWithUnmappedInfo ? { fields: fieldsWithUnmappedInfo } : {}), - }); + setDataViewIndexPatterns(dv); + setDataViewSpec(dv.toSpec()); } }; fetchSingleDataView(); - }, [memoDataViewId, data.dataViews, setDataViewIndexPatterns, activeSpaceId, addWarning]); + }, [memoDataViewId, data.dataViews, setDataViewIndexPatterns, activeSpaceId]); + + // Fetch extended fields information + const getExtendedFields = useCallback( + async (fields: string[]) => { + let extendedFields: FieldSpec[] = []; + const dv = dataViewSpec ?? indexDataViewSpec; + if (!dv) { + return extendedFields; + } + try { + extendedFields = await data.dataViews.getFieldsForIndexPattern(dv, { + pattern: '', + includeUnmapped: true, + fields, + }); + } catch (error) { + addWarning(error, { title: i18n.FETCH_FIELDS_WITH_UNMAPPED_DATA_ERROR }); + } + return extendedFields; + }, + [addWarning, data.dataViews, dataViewSpec, indexDataViewSpec] + ); // Determine whether to use index patterns or data views const indexPatternsToUse = useMemo( @@ -131,5 +144,6 @@ export const useFetchIndexPatterns = (rules: Rule[] | null): ReturnUseFetchExcep return { isLoading: isIndexPatternLoading || mlJobLoading || dataViewLoading, indexPatterns: indexPatternsToUse, + getExtendedFields, }; };