mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[8.8] [Security Solution] Improve rules exception flyout opening for the indices with huge amount of fields (#159216) (#159801)
# Backport This will backport the following commits from `main` to `8.8`: - [[Security Solution] Improve rules exception flyout opening for the indices with huge amount of fields (#159216)](https://github.com/elastic/kibana/pull/159216) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Ievgen Sorokopud","email":"ievgen.sorokopud@elastic.co"},"sourceCommit":{"committedDate":"2023-06-15T12:57:15Z","message":"[Security Solution] Improve rules exception flyout opening for the indices with huge amount of fields (#159216)\n\n## Summary\r\n\r\nOriginal ticket:\r\n[#158751](https://github.com/elastic/kibana/issues/158751)\r\n\r\nThese changes improve the rule's exceptions flyout opening experience.\r\nWe had a few complaints that it is very slow to open it and sometimes it\r\nthrows an exception about the limited response size.\r\n\r\nTo fix this, we decided to load extended field's data (conflicts and\r\nunmapped info) only when user selects some field instead of fetching\r\nthis data for all fields on flyout opening.\r\n\r\n## NOTES:\r\n\r\nAfter these changes we gonna do next steps related to fields loading\r\nwhen user creates/edits rule exceptions:\r\n1. We will call `_fields_for_wildcard` **WITHOUT**\r\n`include_unmapped=true` parameter to fetch all fields specs on exception\r\nflyout loading\r\n2. We will call `_fields_for_wildcard` **WITH** `include_unmapped=true`\r\nfor only one field when user selects it from the dropdown menu\r\n\r\nWith these changes we will improve slow exception flyout opening when\r\nuser has lots of fields which are unmapped in different indices. If for\r\nsome reason user has a lot of (thousands) conflicting fields around\r\nindices then the loading is still might be slow as the\r\n`_fields_for_wildcard` call will return conflicts information even\r\nwithout `include_unmapped=true` parameter.\r\n\r\n---------\r\n\r\nCo-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>","sha":"31b34771c5e6f710858a7f617bbca04537cf5c1b","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team: SecuritySolution","ci:cloud-deploy","v8.9.0","Team:Detection Engine","v8.8.2"],"number":159216,"url":"https://github.com/elastic/kibana/pull/159216","mergeCommit":{"message":"[Security Solution] Improve rules exception flyout opening for the indices with huge amount of fields (#159216)\n\n## Summary\r\n\r\nOriginal ticket:\r\n[#158751](https://github.com/elastic/kibana/issues/158751)\r\n\r\nThese changes improve the rule's exceptions flyout opening experience.\r\nWe had a few complaints that it is very slow to open it and sometimes it\r\nthrows an exception about the limited response size.\r\n\r\nTo fix this, we decided to load extended field's data (conflicts and\r\nunmapped info) only when user selects some field instead of fetching\r\nthis data for all fields on flyout opening.\r\n\r\n## NOTES:\r\n\r\nAfter these changes we gonna do next steps related to fields loading\r\nwhen user creates/edits rule exceptions:\r\n1. We will call `_fields_for_wildcard` **WITHOUT**\r\n`include_unmapped=true` parameter to fetch all fields specs on exception\r\nflyout loading\r\n2. We will call `_fields_for_wildcard` **WITH** `include_unmapped=true`\r\nfor only one field when user selects it from the dropdown menu\r\n\r\nWith these changes we will improve slow exception flyout opening when\r\nuser has lots of fields which are unmapped in different indices. If for\r\nsome reason user has a lot of (thousands) conflicting fields around\r\nindices then the loading is still might be slow as the\r\n`_fields_for_wildcard` call will return conflicts information even\r\nwithout `include_unmapped=true` parameter.\r\n\r\n---------\r\n\r\nCo-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>","sha":"31b34771c5e6f710858a7f617bbca04537cf5c1b"}},"sourceBranch":"main","suggestedTargetBranches":["8.8"],"targetPullRequestStates":[{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/159216","number":159216,"mergeCommit":{"message":"[Security Solution] Improve rules exception flyout opening for the indices with huge amount of fields (#159216)\n\n## Summary\r\n\r\nOriginal ticket:\r\n[#158751](https://github.com/elastic/kibana/issues/158751)\r\n\r\nThese changes improve the rule's exceptions flyout opening experience.\r\nWe had a few complaints that it is very slow to open it and sometimes it\r\nthrows an exception about the limited response size.\r\n\r\nTo fix this, we decided to load extended field's data (conflicts and\r\nunmapped info) only when user selects some field instead of fetching\r\nthis data for all fields on flyout opening.\r\n\r\n## NOTES:\r\n\r\nAfter these changes we gonna do next steps related to fields loading\r\nwhen user creates/edits rule exceptions:\r\n1. We will call `_fields_for_wildcard` **WITHOUT**\r\n`include_unmapped=true` parameter to fetch all fields specs on exception\r\nflyout loading\r\n2. We will call `_fields_for_wildcard` **WITH** `include_unmapped=true`\r\nfor only one field when user selects it from the dropdown menu\r\n\r\nWith these changes we will improve slow exception flyout opening when\r\nuser has lots of fields which are unmapped in different indices. If for\r\nsome reason user has a lot of (thousands) conflicting fields around\r\nindices then the loading is still might be slow as the\r\n`_fields_for_wildcard` call will return conflicts information even\r\nwithout `include_unmapped=true` parameter.\r\n\r\n---------\r\n\r\nCo-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>","sha":"31b34771c5e6f710858a7f617bbca04537cf5c1b"}},{"branch":"8.8","label":"v8.8.2","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Ievgen Sorokopud <ievgen.sorokopud@elastic.co>
This commit is contained in:
parent
f3f2d31cc0
commit
608652a0a7
13 changed files with 107 additions and 66 deletions
|
@ -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(
|
||||
<BuilderEntryItem
|
||||
autocompleteService={autocompleteStartMock}
|
||||
entry={{
|
||||
correspondingKeywordField: undefined,
|
||||
entryIndex: 0,
|
||||
field: getField('mapping issues'),
|
||||
field,
|
||||
id: '123',
|
||||
nested: undefined,
|
||||
operator: isOperator,
|
||||
|
@ -223,12 +224,16 @@ describe('BuilderEntryItem', () => {
|
|||
setWarningsExist={jest.fn()}
|
||||
showLabel
|
||||
allowCustomOptions
|
||||
getExtendedFields={(): Promise<FieldSpec[]> => 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"', () => {
|
||||
|
|
|
@ -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<DataViewField[]>;
|
||||
}
|
||||
|
||||
export const BuilderEntryItem: React.FC<EntryItemProps> = ({
|
||||
|
@ -106,6 +108,7 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
|
|||
isDisabled = false,
|
||||
operatorsList,
|
||||
allowCustomOptions = false,
|
||||
getExtendedFields,
|
||||
}): JSX.Element => {
|
||||
const sPaddingSize = useEuiPaddingSize('s');
|
||||
|
||||
|
@ -175,6 +178,22 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
|
|||
[onChange, entry]
|
||||
);
|
||||
|
||||
const [extendedField, setExtendedField] = useState<DataViewField | null>(null);
|
||||
useEffect(() => {
|
||||
if (!entry.field?.name) {
|
||||
setExtendedField(null);
|
||||
}
|
||||
const fetchExtendedField = async (): Promise<void> => {
|
||||
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<EntryItemProps> = ({
|
|||
);
|
||||
|
||||
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<EntryItemProps> = ({
|
|||
data-test-subj="mappingConflictsAccordion"
|
||||
>
|
||||
<div data-test-subj="mappingConflictsDescription">
|
||||
{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 (
|
||||
<>
|
||||
<EuiFlexItem key={`${idx}`}>
|
||||
<EuiSpacer size="s" />
|
||||
{`${
|
||||
info.totalIndexCount > 1
|
||||
|
@ -254,7 +276,7 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
|
|||
)
|
||||
: info.type
|
||||
}: ${groupDetails.join(', ')}`}
|
||||
</>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
<EuiSpacer size="s" />
|
||||
|
@ -268,12 +290,12 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
|
|||
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<EntryItemProps> = ({
|
|||
osTypes,
|
||||
isDisabled,
|
||||
handleFieldChange,
|
||||
sPaddingSize,
|
||||
allowCustomOptions,
|
||||
sPaddingSize,
|
||||
extendedField,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -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<DataViewField[]>;
|
||||
}
|
||||
|
||||
export const BuilderExceptionListItemComponent = React.memo<BuilderExceptionListItemProps>(
|
||||
|
@ -88,6 +90,7 @@ export const BuilderExceptionListItemComponent = React.memo<BuilderExceptionList
|
|||
isDisabled = false,
|
||||
operatorsList,
|
||||
allowCustomOptions = false,
|
||||
getExtendedFields,
|
||||
}) => {
|
||||
const handleEntryChange = useCallback(
|
||||
(entry: BuilderEntry, entryIndex: number): void => {
|
||||
|
@ -161,6 +164,7 @@ export const BuilderExceptionListItemComponent = React.memo<BuilderExceptionList
|
|||
}
|
||||
operatorsList={operatorsList}
|
||||
allowCustomOptions={allowCustomOptions}
|
||||
getExtendedFields={getExtendedFields}
|
||||
/>
|
||||
</MyOverflowContainer>
|
||||
<BuilderEntryDeleteButtonComponent
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import {
|
||||
CreateExceptionListItemBuilderSchema,
|
||||
DataViewField,
|
||||
ExceptionsBuilderExceptionItem,
|
||||
ExceptionsBuilderReturnExceptionItem,
|
||||
OperatorOption,
|
||||
|
@ -98,6 +99,7 @@ export interface ExceptionBuilderProps {
|
|||
operatorsList?: OperatorOption[];
|
||||
exceptionItemName?: string;
|
||||
allowCustomFieldOptions?: boolean;
|
||||
getExtendedFields?: (fields: string[]) => Promise<DataViewField[]>;
|
||||
}
|
||||
|
||||
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}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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<string, {}>;
|
||||
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 = (): {
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 && (
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -109,7 +109,7 @@ const EditExceptionFlyoutComponent: React.FC<EditExceptionFlyoutProps> = ({
|
|||
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<EditExceptionFlyoutProps> = ({
|
|||
onExceptionItemAdd={setExceptionItemsToAdd}
|
||||
onSetErrorExists={setConditionsValidationError}
|
||||
onFilterIndexPatterns={filterIndexPatterns}
|
||||
getExtendedFields={getExtendedFields}
|
||||
/>
|
||||
{!openedFromListDetailPage && listType === ExceptionListTypeEnum.DETECTION && (
|
||||
<>
|
||||
|
|
|
@ -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<DataViewField[]>;
|
||||
}
|
||||
|
||||
const ExceptionsConditionsComponent: React.FC<ExceptionsFlyoutConditionsComponentProps> = ({
|
||||
|
@ -105,6 +108,7 @@ const ExceptionsConditionsComponent: React.FC<ExceptionsFlyoutConditionsComponen
|
|||
onExceptionItemAdd,
|
||||
onSetErrorExists,
|
||||
onFilterIndexPatterns,
|
||||
getExtendedFields,
|
||||
}): JSX.Element => {
|
||||
const { http, unifiedSearch } = useKibana().services;
|
||||
const isEndpointException = useMemo(
|
||||
|
@ -267,6 +271,7 @@ const ExceptionsConditionsComponent: React.FC<ExceptionsFlyoutConditionsComponen
|
|||
onChange: handleBuilderOnChange,
|
||||
isDisabled: isExceptionBuilderFormDisabled,
|
||||
allowCustomFieldOptions: !isEndpointException,
|
||||
getExtendedFields,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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<FieldSpec[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<DataViewBase | null>(null);
|
||||
const [dataViewSpec, setDataViewSpec] = useState<DataViewSpec | null>(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,
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue