diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx
index 7734344d193b8..1ff5d770521f3 100644
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.test.tsx
@@ -8,14 +8,27 @@ import { ThemeProvider } from 'styled-components';
import { mount } from 'enzyme';
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
+// we don't have the types for waitFor just yet, so using "as waitFor" until when we do
+import { wait as waitFor } from '@testing-library/react';
import { getField } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks.ts';
-import { AutocompleteFieldListsComponent } from './field_value_lists';
+import { ListSchema } from '../../../lists_plugin_deps';
import { getFoundListSchemaMock } from '../../../../../lists/common/schemas/response/found_list_schema.mock';
+import { getListResponseMock } from '../../../../../lists/common/schemas/response/list_schema.mock';
+import { DATE_NOW } from '../../../../../lists/common/constants.mock';
+
+import { AutocompleteFieldListsComponent } from './field_value_lists';
-const mockStart = jest.fn();
-const mockResult = getFoundListSchemaMock();
jest.mock('../../../common/lib/kibana');
+const mockStart = jest.fn();
+const mockKeywordList: ListSchema = {
+ ...getListResponseMock(),
+ id: 'keyword_list',
+ type: 'keyword',
+ name: 'keyword list',
+};
+const mockResult = { ...getFoundListSchemaMock() };
+mockResult.data = [...mockResult.data, mockKeywordList];
jest.mock('../../../lists_plugin_deps', () => {
const originalModule = jest.requireActual('../../../lists_plugin_deps');
@@ -31,7 +44,7 @@ jest.mock('../../../lists_plugin_deps', () => {
});
describe('AutocompleteFieldListsComponent', () => {
- test('it renders disabled if "isDisabled" is true', () => {
+ test('it renders disabled if "isDisabled" is true', async () => {
const wrapper = mount(
({ eui: euiLightVars, darkMode: false })}>
{
);
- expect(
- wrapper
- .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] input`)
- .prop('disabled')
- ).toBeTruthy();
+ await waitFor(() => {
+ expect(
+ wrapper
+ .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] input`)
+ .prop('disabled')
+ ).toBeTruthy();
+ });
});
- test('it renders loading if "isLoading" is true', () => {
+ test('it renders loading if "isLoading" is true', async () => {
const wrapper = mount(
({ eui: euiLightVars, darkMode: false })}>
{
/>
);
- wrapper
- .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] button`)
- .at(0)
- .simulate('click');
- expect(
+
+ await waitFor(() => {
wrapper
- .find(
- `EuiComboBoxOptionsList[data-test-subj="valuesAutocompleteComboBox listsComboxBox-optionsList"]`
- )
- .prop('isLoading')
- ).toBeTruthy();
+ .find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] button`)
+ .at(0)
+ .simulate('click');
+ expect(
+ wrapper
+ .find(
+ `EuiComboBoxOptionsList[data-test-subj="valuesAutocompleteComboBox listsComboxBox-optionsList"]`
+ )
+ .prop('isLoading')
+ ).toBeTruthy();
+ });
});
- test('it allows user to clear values if "isClearable" is true', () => {
+ test('it allows user to clear values if "isClearable" is true', async () => {
const wrapper = mount(
({ eui: euiLightVars, darkMode: false })}>
{
/>
);
-
expect(
wrapper
.find(`[data-test-subj="comboBoxInput"]`)
@@ -102,7 +119,55 @@ describe('AutocompleteFieldListsComponent', () => {
).toBeTruthy();
});
- test('it correctly displays selected list', () => {
+ test('it correctly displays lists that match the selected "keyword" field esType', () => {
+ const wrapper = mount(
+ ({ eui: euiLightVars, darkMode: false })}>
+
+
+ );
+
+ wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click');
+
+ expect(
+ wrapper
+ .find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]')
+ .prop('options')
+ ).toEqual([{ label: 'keyword list' }]);
+ });
+
+ test('it correctly displays lists that match the selected "ip" field esType', () => {
+ const wrapper = mount(
+ ({ eui: euiLightVars, darkMode: false })}>
+
+
+ );
+
+ wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click');
+
+ expect(
+ wrapper
+ .find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]')
+ .prop('options')
+ ).toEqual([{ label: 'some name' }]);
+ });
+
+ test('it correctly displays selected list', async () => {
const wrapper = mount(
({ eui: euiLightVars, darkMode: false })}>
{
}).onChange([{ label: 'some name' }]);
expect(mockOnChange).toHaveBeenCalledWith({
- created_at: '2020-04-20T15:25:31.830Z',
+ created_at: DATE_NOW,
created_by: 'some user',
description: 'some description',
id: 'some-list-id',
@@ -154,7 +219,7 @@ describe('AutocompleteFieldListsComponent', () => {
name: 'some name',
tie_breaker_id: '6a76b69d-80df-4ab2-8c3e-85f466b06a0e',
type: 'ip',
- updated_at: '2020-04-20T15:25:31.830Z',
+ updated_at: DATE_NOW,
updated_by: 'some user',
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx
index d8ce27e97874d..a9d85452651b5 100644
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_lists.tsx
@@ -36,8 +36,12 @@ export const AutocompleteFieldListsComponent: React.FC name, []);
const optionsMemo = useMemo(() => {
- if (selectedField != null) {
- return lists.filter(({ type }) => type === selectedField.type);
+ if (
+ selectedField != null &&
+ selectedField.esTypes != null &&
+ selectedField.esTypes.length > 0
+ ) {
+ return lists.filter(({ type }) => selectedField.esTypes?.includes(type));
} else {
return [];
}
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx
index 32a82af114bae..a082811920f88 100644
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx
@@ -79,10 +79,10 @@ export const AutocompleteFieldMatchComponent: React.FC validateParams(selectedValue, selectedField ? selectedField.type : ''),
- [selectedField, selectedValue]
- );
+ const isValid = useMemo((): boolean => validateParams(selectedValue, selectedField), [
+ selectedField,
+ selectedValue,
+ ]);
return (
{
const areAnyInvalid = selectedComboOptions.filter(
- ({ label }) => !validateParams(label, selectedField ? selectedField.type : '')
+ ({ label }) => !validateParams(label, selectedField)
);
return areAnyInvalid.length === 0;
}, [selectedComboOptions, selectedField]);
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts
index cfe23b9391ec0..cb07d99913107 100644
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.test.ts
@@ -55,49 +55,25 @@ describe('helpers', () => {
describe('#validateParams', () => {
test('returns true if value is undefined', () => {
- const isValid = validateParams(undefined, 'date');
+ const isValid = validateParams(undefined, getField('@timestamp'));
expect(isValid).toBeTruthy();
});
test('returns true if value is empty string', () => {
- const isValid = validateParams('', 'date');
+ const isValid = validateParams('', getField('@timestamp'));
expect(isValid).toBeTruthy();
});
test('returns true if type is "date" and value is valid', () => {
- const isValid = validateParams('1994-11-05T08:15:30-05:00', 'date');
+ const isValid = validateParams('1994-11-05T08:15:30-05:00', getField('@timestamp'));
expect(isValid).toBeTruthy();
});
test('returns false if type is "date" and value is not valid', () => {
- const isValid = validateParams('1593478826', 'date');
-
- expect(isValid).toBeFalsy();
- });
-
- test('returns true if type is "ip" and value is valid', () => {
- const isValid = validateParams('126.45.211.34', 'ip');
-
- expect(isValid).toBeTruthy();
- });
-
- test('returns false if type is "ip" and value is not valid', () => {
- const isValid = validateParams('hellooo', 'ip');
-
- expect(isValid).toBeFalsy();
- });
-
- test('returns true if type is "number" and value is valid', () => {
- const isValid = validateParams('123', 'number');
-
- expect(isValid).toBeTruthy();
- });
-
- test('returns false if type is "number" and value is not valid', () => {
- const isValid = validateParams('not a number', 'number');
+ const isValid = validateParams('1593478826', getField('@timestamp'));
expect(isValid).toBeFalsy();
});
diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts
index 483ca5d6d332e..16659593784db 100644
--- a/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts
+++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts
@@ -7,7 +7,7 @@
import dateMath from '@elastic/datemath';
import { EuiComboBoxOptionOption } from '@elastic/eui';
-import { IFieldType, Ipv4Address } from '../../../../../../../src/plugins/data/common';
+import { IFieldType } from '../../../../../../../src/plugins/data/common';
import {
EXCEPTION_OPERATORS,
@@ -30,29 +30,27 @@ export const getOperators = (field: IFieldType | undefined): OperatorOption[] =>
}
};
-export function validateParams(params: string | undefined, type: string) {
+export const validateParams = (
+ params: string | undefined,
+ field: IFieldType | undefined
+): boolean => {
// Box would show error state if empty otherwise
if (params == null || params === '') {
return true;
}
- switch (type) {
- case 'date':
- const moment = dateMath.parse(params);
- return Boolean(moment && moment.isValid());
- case 'ip':
- try {
- return Boolean(new Ipv4Address(params));
- } catch (e) {
- return false;
- }
- case 'number':
- const val = parseFloat(params);
- return typeof val === 'number' && !isNaN(val);
- default:
- return true;
- }
-}
+ const types = field != null && field.esTypes != null ? field.esTypes : [];
+
+ return types.reduce((acc, type) => {
+ switch (type) {
+ case 'date':
+ const moment = dateMath.parse(params);
+ return Boolean(moment && moment.isValid());
+ default:
+ return acc;
+ }
+ }, true);
+};
export function getGenericComboBoxProps({
options,
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx
index 53c53f48f076b..e630645ef8c4e 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx
@@ -22,7 +22,6 @@ import {
EuiText,
} from '@elastic/eui';
import { Status } from '../../../../../common/detection_engine/schemas/common/schemas';
-import { alertsIndexPattern } from '../../../../../common/endpoint/constants';
import {
ExceptionListItemSchema,
CreateExceptionListItemSchema,
@@ -48,24 +47,18 @@ import {
} from '../helpers';
import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules';
-export interface AddExceptionOnClick {
+export interface AddExceptionModalBaseProps {
ruleName: string;
ruleId: string;
exceptionListType: ExceptionListType;
+ ruleIndices: string[];
alertData?: {
ecsData: Ecs;
nonEcsData: TimelineNonEcsData[];
};
}
-interface AddExceptionModalProps {
- ruleName: string;
- ruleId: string;
- exceptionListType: ExceptionListType;
- alertData?: {
- ecsData: Ecs;
- nonEcsData: TimelineNonEcsData[];
- };
+export interface AddExceptionModalProps extends AddExceptionModalBaseProps {
onCancel: () => void;
onConfirm: (didCloseAlert: boolean) => void;
alertStatus?: Status;
@@ -78,10 +71,8 @@ const Modal = styled(EuiModal)`
`;
const ModalHeader = styled(EuiModalHeader)`
- ${({ theme }) => css`
- flex-direction: column;
- align-items: flex-start;
- `}
+ flex-direction: column;
+ align-items: flex-start;
`;
const ModalHeaderSubtitle = styled.div`
@@ -103,6 +94,7 @@ const ModalBodySection = styled.section`
export const AddExceptionModal = memo(function AddExceptionModal({
ruleName,
ruleId,
+ ruleIndices,
exceptionListType,
alertData,
onCancel,
@@ -120,10 +112,11 @@ export const AddExceptionModal = memo(function AddExceptionModal({
const [fetchOrCreateListError, setFetchOrCreateListError] = useState(false);
const { addError, addSuccess } = useAppToasts();
const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex();
+ const [
+ { isLoading: isSignalIndexPatternLoading, indexPatterns: signalIndexPatterns },
+ ] = useFetchIndexPatterns(signalIndexName !== null ? [signalIndexName] : []);
- const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns(
- signalIndexName !== null ? [signalIndexName] : []
- );
+ const [{ isLoading: isIndexPatternLoading, indexPatterns }] = useFetchIndexPatterns(ruleIndices);
const onError = useCallback(
(error: Error) => {
@@ -183,19 +176,19 @@ export const AddExceptionModal = memo(function AddExceptionModal({
}, [alertData, exceptionListType, ruleExceptionList, ruleName]);
useEffect(() => {
- if (indexPatternLoading === false && isSignalIndexLoading === false) {
+ if (isSignalIndexPatternLoading === false && isSignalIndexLoading === false) {
setShouldDisableBulkClose(
entryHasListType(exceptionItemsToAdd) ||
- entryHasNonEcsType(exceptionItemsToAdd, indexPatterns) ||
+ entryHasNonEcsType(exceptionItemsToAdd, signalIndexPatterns) ||
exceptionItemsToAdd.length === 0
);
}
}, [
setShouldDisableBulkClose,
exceptionItemsToAdd,
- indexPatternLoading,
+ isSignalIndexPatternLoading,
isSignalIndexLoading,
- indexPatterns,
+ signalIndexPatterns,
]);
useEffect(() => {
@@ -274,15 +267,8 @@ export const AddExceptionModal = memo(function AddExceptionModal({
[fetchOrCreateListError, exceptionItemsToAdd]
);
- const indexPatternConfig = useCallback(() => {
- if (exceptionListType === 'endpoint') {
- return [alertsIndexPattern];
- }
- return signalIndexName ? [signalIndexName] : [];
- }, [exceptionListType, signalIndexName]);
-
return (
-
+
{i18n.ADD_EXCEPTION}
@@ -301,8 +287,9 @@ export const AddExceptionModal = memo(function AddExceptionModal({
)}
{fetchOrCreateListError === false &&
!isSignalIndexLoading &&
- !indexPatternLoading &&
+ !isSignalIndexPatternLoading &&
!isLoadingExceptionList &&
+ !isIndexPatternLoading &&
ruleExceptionList && (
<>
@@ -314,8 +301,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({
listId={ruleExceptionList.list_id}
listNamespaceType={ruleExceptionList.namespace_type}
ruleName={ruleName}
- indexPatternConfig={indexPatternConfig()}
- isLoading={false}
+ indexPatterns={indexPatterns}
isOrDisabled={false}
isAndDisabled={false}
data-test-subj="alert-exception-builder"
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx
index 08e5b49073ecf..f6feca591dc6d 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/builder/index.tsx
@@ -3,12 +3,12 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useState, useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import styled from 'styled-components';
import { ExceptionListItemComponent } from './builder_exception_item';
-import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules/fetch_index_patterns';
+import { IIndexPattern } from '../../../../../../../../src/plugins/data/common';
import {
ExceptionListItemSchema,
NamespaceType,
@@ -22,7 +22,6 @@ import { AndOrBadge } from '../../and_or_badge';
import { BuilderButtonOptions } from './builder_button_options';
import { getNewExceptionItem, filterExceptionItems } from '../helpers';
import { ExceptionsBuilderExceptionItem, CreateExceptionListItemBuilderSchema } from '../types';
-import { Loader } from '../../loader';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import exceptionableFields from '../exceptionable_fields.json';
@@ -51,8 +50,7 @@ interface ExceptionBuilderProps {
listId: string;
listNamespaceType: NamespaceType;
ruleName: string;
- indexPatternConfig: string[];
- isLoading: boolean;
+ indexPatterns: IIndexPattern;
isOrDisabled: boolean;
isAndDisabled: boolean;
onChange: (arg: OnChangeProps) => void;
@@ -64,8 +62,7 @@ export const ExceptionBuilder = ({
listId,
listNamespaceType,
ruleName,
- indexPatternConfig,
- isLoading,
+ indexPatterns,
isOrDisabled,
isAndDisabled,
onChange,
@@ -75,9 +72,6 @@ export const ExceptionBuilder = ({
exceptionListItems
);
const [exceptionsToDelete, setExceptionsToDelete] = useState([]);
- const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns(
- indexPatternConfig ?? []
- );
const handleCheckAndLogic = (items: ExceptionsBuilderExceptionItem[]): void => {
setAndLogicIncluded(items.filter(({ entries }) => entries.length > 1).length > 0);
@@ -154,7 +148,7 @@ export const ExceptionBuilder = ({
}, [setExceptions, listType, listId, listNamespaceType, ruleName]);
// Filters index pattern fields by exceptionable fields if list type is endpoint
- const filterIndexPatterns = useCallback(() => {
+ const filterIndexPatterns = useMemo((): IIndexPattern => {
if (listType === 'endpoint') {
return {
...indexPatterns,
@@ -196,9 +190,6 @@ export const ExceptionBuilder = ({
return (
- {(isLoading || indexPatternLoading) && (
-
- )}
{exceptions.map((exceptionListItem, index) => (
@@ -224,8 +215,8 @@ export const ExceptionBuilder = ({
key={getExceptionListItemId(exceptionListItem, index)}
exceptionItem={exceptionListItem}
exceptionId={getExceptionListItemId(exceptionListItem, index)}
- indexPattern={filterIndexPatterns()}
- isLoading={indexPatternLoading}
+ indexPattern={filterIndexPatterns}
+ isLoading={indexPatterns.fields.length === 0}
exceptionItemIndex={index}
andLogicIncluded={andLogicIncluded}
isOnlyItem={exceptions.length === 1}
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx
index 51cc684a01de6..d07a8b5f0d2f6 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx
@@ -20,7 +20,6 @@ import {
EuiFormRow,
EuiText,
} from '@elastic/eui';
-import { alertsIndexPattern } from '../../../../../common/endpoint/constants';
import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules';
import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index';
import {
@@ -45,6 +44,7 @@ import { Loader } from '../../loader';
interface EditExceptionModalProps {
ruleName: string;
+ ruleIndices: string[];
exceptionItem: ExceptionListItemSchema;
exceptionListType: ExceptionListType;
onCancel: () => void;
@@ -58,10 +58,8 @@ const Modal = styled(EuiModal)`
`;
const ModalHeader = styled(EuiModalHeader)`
- ${({ theme }) => css`
- flex-direction: column;
- align-items: flex-start;
- `}
+ flex-direction: column;
+ align-items: flex-start;
`;
const ModalHeaderSubtitle = styled.div`
@@ -82,6 +80,7 @@ const ModalBodySection = styled.section`
export const EditExceptionModal = memo(function EditExceptionModal({
ruleName,
+ ruleIndices,
exceptionItem,
exceptionListType,
onCancel,
@@ -96,10 +95,11 @@ export const EditExceptionModal = memo(function EditExceptionModal({
>([]);
const { addError, addSuccess } = useAppToasts();
const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex();
+ const [
+ { isLoading: isSignalIndexPatternLoading, indexPatterns: signalIndexPatterns },
+ ] = useFetchIndexPatterns(signalIndexName !== null ? [signalIndexName] : []);
- const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns(
- signalIndexName !== null ? [signalIndexName] : []
- );
+ const [{ isLoading: isIndexPatternLoading, indexPatterns }] = useFetchIndexPatterns(ruleIndices);
const onError = useCallback(
(error) => {
@@ -122,19 +122,19 @@ export const EditExceptionModal = memo(function EditExceptionModal({
);
useEffect(() => {
- if (indexPatternLoading === false && isSignalIndexLoading === false) {
+ if (isSignalIndexPatternLoading === false && isSignalIndexLoading === false) {
setShouldDisableBulkClose(
entryHasListType(exceptionItemsToAdd) ||
- entryHasNonEcsType(exceptionItemsToAdd, indexPatterns) ||
+ entryHasNonEcsType(exceptionItemsToAdd, signalIndexPatterns) ||
exceptionItemsToAdd.length === 0
);
}
}, [
setShouldDisableBulkClose,
exceptionItemsToAdd,
- indexPatternLoading,
+ isSignalIndexPatternLoading,
isSignalIndexLoading,
- indexPatterns,
+ signalIndexPatterns,
]);
useEffect(() => {
@@ -189,15 +189,8 @@ export const EditExceptionModal = memo(function EditExceptionModal({
}
}, [addOrUpdateExceptionItems, enrichExceptionItems, shouldBulkCloseAlert, signalIndexName]);
- const indexPatternConfig = useCallback(() => {
- if (exceptionListType === 'endpoint') {
- return [alertsIndexPattern];
- }
- return signalIndexName ? [signalIndexName] : [];
- }, [exceptionListType, signalIndexName]);
-
return (
-
+
{i18n.EDIT_EXCEPTION_TITLE}
@@ -206,11 +199,11 @@ export const EditExceptionModal = memo(function EditExceptionModal({
- {(addExceptionIsLoading || indexPatternLoading || isSignalIndexLoading) && (
+ {(addExceptionIsLoading || isIndexPatternLoading || isSignalIndexLoading) && (
)}
- {!isSignalIndexLoading && !addExceptionIsLoading && !indexPatternLoading && (
+ {!isSignalIndexLoading && !addExceptionIsLoading && !isIndexPatternLoading && (
<>
{i18n.EXCEPTION_BUILDER_INFO}
@@ -221,13 +214,12 @@ export const EditExceptionModal = memo(function EditExceptionModal({
listId={exceptionItem.list_id}
listNamespaceType={exceptionItem.namespace_type}
ruleName={ruleName}
- isLoading={false}
isOrDisabled={false}
isAndDisabled={false}
data-test-subj="edit-exception-modal-builder"
id-aria="edit-exception-modal-builder"
onChange={handleBuilderOnChange}
- indexPatternConfig={indexPatternConfig()}
+ indexPatterns={indexPatterns}
/>
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.test.tsx
index f72008cbdffe1..986f27f6495ec 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.test.tsx
@@ -67,6 +67,7 @@ describe('ExceptionsViewer', () => {
({ eui: euiLightVars, darkMode: false })}>
{
const wrapper = mount(
({ eui: euiLightVars, darkMode: false })}>
{
const wrapper = mount(
({ eui: euiLightVars, darkMode: false })}>
{
+ }: UseExceptionListSuccess): void => {
dispatch({
type: 'setExceptions',
lists: newLists,
@@ -253,10 +255,11 @@ const ExceptionsViewerComponent = ({
return (
<>
{currentModal === 'editModal' &&
- exceptionToEdit !== null &&
- exceptionListTypeToEdit !== null && (
+ exceptionToEdit != null &&
+ exceptionListTypeToEdit != null && (
0
? {
fields: fields.map((field) =>
- pick(['name', 'searchable', 'type', 'aggregatable'], field)
+ pick(['name', 'searchable', 'type', 'aggregatable', 'esTypes', 'subType'], field)
),
title,
}
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx
index 71cf5c10de764..a4ce6c0200eb3 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx
@@ -12,6 +12,7 @@ import { Dispatch } from 'redux';
import { EuiText } from '@elastic/eui';
import { RowRendererId } from '../../../../common/types/timeline';
+import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants';
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
import { Filter } from '../../../../../../../src/plugins/data/common/es_query';
import {
@@ -38,7 +39,7 @@ import {
UpdateTimelineLoading,
} from './types';
import { Ecs, TimelineNonEcsData } from '../../../graphql/types';
-import { AddExceptionOnClick } from '../../../common/components/exceptions/add_exception_modal';
+import { AddExceptionModalBaseProps } from '../../../common/components/exceptions/add_exception_modal';
import { getMappedNonEcsValue } from '../../../common/components/exceptions/helpers';
export const buildAlertStatusFilter = (status: Status): Filter[] => [
@@ -225,7 +226,7 @@ interface AlertActionArgs {
alertData,
ruleName,
ruleId,
- }: AddExceptionOnClick) => void;
+ }: AddExceptionModalBaseProps) => void;
}
export const getAlertActions = ({
@@ -346,10 +347,12 @@ export const getAlertActions = ({
onClick: ({ ecsData, data }: TimelineRowActionOnClick) => {
const [ruleName] = getMappedNonEcsValue({ data, fieldName: 'signal.rule.name' });
const [ruleId] = getMappedNonEcsValue({ data, fieldName: 'signal.rule.id' });
+ const ruleIndices = getMappedNonEcsValue({ data, fieldName: 'signal.rule.index' });
if (ruleId !== undefined) {
openAddExceptionModal({
ruleName: ruleName ?? '',
ruleId,
+ ruleIndices: ruleIndices.length > 0 ? ruleIndices : DEFAULT_INDEX_PATTERN,
exceptionListType: 'endpoint',
alertData: {
ecsData,
@@ -369,10 +372,12 @@ export const getAlertActions = ({
onClick: ({ ecsData, data }: TimelineRowActionOnClick) => {
const [ruleName] = getMappedNonEcsValue({ data, fieldName: 'signal.rule.name' });
const [ruleId] = getMappedNonEcsValue({ data, fieldName: 'signal.rule.id' });
+ const ruleIndices = getMappedNonEcsValue({ data, fieldName: 'signal.rule.index' });
if (ruleId !== undefined) {
openAddExceptionModal({
ruleName: ruleName ?? '',
ruleId,
+ ruleIndices: ruleIndices.length > 0 ? ruleIndices : DEFAULT_INDEX_PATTERN,
exceptionListType: 'detection',
alertData: {
ecsData,
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
index 1d4c97d85443f..1eda358fe5944 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
@@ -54,7 +54,7 @@ import {
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';
import {
AddExceptionModal,
- AddExceptionOnClick,
+ AddExceptionModalBaseProps,
} from '../../../common/components/exceptions/add_exception_modal';
interface OwnProps {
@@ -73,9 +73,10 @@ interface OwnProps {
type AlertsTableComponentProps = OwnProps & PropsFromRedux;
-const addExceptionModalInitialState: AddExceptionOnClick = {
+const addExceptionModalInitialState: AddExceptionModalBaseProps = {
ruleName: '',
ruleId: '',
+ ruleIndices: [],
exceptionListType: 'detection',
alertData: undefined,
};
@@ -112,7 +113,7 @@ export const AlertsTableComponent: React.FC = ({
const [showClearSelectionAction, setShowClearSelectionAction] = useState(false);
const [filterGroup, setFilterGroup] = useState(FILTER_OPEN);
const [shouldShowAddExceptionModal, setShouldShowAddExceptionModal] = useState(false);
- const [addExceptionModalState, setAddExceptionModalState] = useState(
+ const [addExceptionModalState, setAddExceptionModalState] = useState(
addExceptionModalInitialState
);
const [{ browserFields, indexPatterns }] = useFetchIndexPatterns(
@@ -216,12 +217,19 @@ export const AlertsTableComponent: React.FC = ({
);
const openAddExceptionModalCallback = useCallback(
- ({ ruleName, ruleId, exceptionListType, alertData }: AddExceptionOnClick) => {
+ ({
+ ruleName,
+ ruleIndices,
+ ruleId,
+ exceptionListType,
+ alertData,
+ }: AddExceptionModalBaseProps) => {
if (alertData !== null && alertData !== undefined) {
setShouldShowAddExceptionModal(true);
setAddExceptionModalState({
ruleName,
ruleId,
+ ruleIndices,
exceptionListType,
alertData,
});
@@ -421,12 +429,9 @@ export const AlertsTableComponent: React.FC = ({
closeAddExceptionModal();
}, [closeAddExceptionModal]);
- const onAddExceptionConfirm = useCallback(
- (didCloseAlert: boolean) => {
- closeAddExceptionModal();
- },
- [closeAddExceptionModal]
- );
+ const onAddExceptionConfirm = useCallback(() => closeAddExceptionModal(), [
+ closeAddExceptionModal,
+ ]);
if (loading || isEmpty(signalsIndex)) {
return (
@@ -454,6 +459,7 @@ export const AlertsTableComponent: React.FC = ({
= ({
;
format?: Maybe;
+ /** the elastic type as mapped in the index */
+ esTypes?: Maybe;
+
+ subType?: Maybe;
}
export interface AuthenticationsData {
@@ -2780,6 +2788,10 @@ export namespace SourceQuery {
aggregatable: boolean;
format: Maybe;
+
+ esTypes: Maybe;
+
+ subType: Maybe;
};
}
diff --git a/x-pack/plugins/security_solution/server/graphql/ecs/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/ecs/resolvers.ts
index f30b7d192d05d..414e5b5d95bec 100644
--- a/x-pack/plugins/security_solution/server/graphql/ecs/resolvers.ts
+++ b/x-pack/plugins/security_solution/server/graphql/ecs/resolvers.ts
@@ -47,9 +47,41 @@ export const toStringArrayScalar = new GraphQLScalarType({
return null;
},
});
-
+export const toStringArrayNoNullableScalar = new GraphQLScalarType({
+ name: 'StringArray',
+ description: 'Represents value in detail item from the timeline who wants to more than one type',
+ serialize(value): string[] | undefined {
+ if (value == null) {
+ return undefined;
+ } else if (Array.isArray(value)) {
+ return convertArrayToString(value) as string[];
+ } else if (isBoolean(value) || isNumber(value) || isObject(value)) {
+ return [convertToString(value)];
+ }
+ return [value];
+ },
+ parseValue(value) {
+ return value;
+ },
+ parseLiteral(ast) {
+ switch (ast.kind) {
+ case Kind.INT:
+ return parseInt(ast.value, 10);
+ case Kind.FLOAT:
+ return parseFloat(ast.value);
+ case Kind.STRING:
+ return ast.value;
+ case Kind.LIST:
+ return ast.values;
+ case Kind.OBJECT:
+ return ast.fields;
+ }
+ return undefined;
+ },
+});
export const createScalarToStringArrayValueResolvers = () => ({
ToStringArray: toStringArrayScalar,
+ ToStringArrayNoNullable: toStringArrayNoNullableScalar,
});
const convertToString = (value: object | number | boolean | string): string => {
diff --git a/x-pack/plugins/security_solution/server/graphql/ecs/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/ecs/schema.gql.ts
index 5b093a02b6514..bdc69f85d3542 100644
--- a/x-pack/plugins/security_solution/server/graphql/ecs/schema.gql.ts
+++ b/x-pack/plugins/security_solution/server/graphql/ecs/schema.gql.ts
@@ -8,6 +8,7 @@ import gql from 'graphql-tag';
export const ecsSchema = gql`
scalar ToStringArray
+ scalar ToStringArrayNoNullable
type EventEcsFields {
action: ToStringArray
diff --git a/x-pack/plugins/security_solution/server/graphql/source_status/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/source_status/resolvers.ts
index 24589822f0250..8d55e645d6791 100644
--- a/x-pack/plugins/security_solution/server/graphql/source_status/resolvers.ts
+++ b/x-pack/plugins/security_solution/server/graphql/source_status/resolvers.ts
@@ -4,11 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { GraphQLScalarType, Kind } from 'graphql';
import { SourceStatusResolvers } from '../../graphql/types';
import { AppResolverOf, ChildResolverOf } from '../../lib/framework';
import { IndexFields } from '../../lib/index_fields';
import { SourceStatus } from '../../lib/source_status';
import { QuerySourceResolver } from '../sources/resolvers';
+import { IFieldSubType } from '../../../../../../src/plugins/data/common/index_patterns/types';
export type SourceStatusIndicesExistResolver = ChildResolverOf<
AppResolverOf,
@@ -50,3 +52,40 @@ export const createSourceStatusResolvers = (libs: {
},
},
});
+
+export const toIFieldSubTypeNonNullableScalar = new GraphQLScalarType({
+ name: 'IFieldSubType',
+ description: 'Represents value in index pattern field item',
+ serialize(value): IFieldSubType | undefined {
+ if (value == null) {
+ return undefined;
+ }
+
+ return {
+ multi: value.multi ?? undefined,
+ nested: value.nested ?? undefined,
+ };
+ },
+ parseValue(value) {
+ return value;
+ },
+ parseLiteral(ast) {
+ switch (ast.kind) {
+ case Kind.INT:
+ return undefined;
+ case Kind.FLOAT:
+ return undefined;
+ case Kind.STRING:
+ return undefined;
+ case Kind.LIST:
+ return undefined;
+ case Kind.OBJECT:
+ return ast;
+ }
+ return undefined;
+ },
+});
+
+export const createScalarToIFieldSubTypeNonNullableScalarResolvers = () => ({
+ ToIFieldSubTypeNonNullable: toIFieldSubTypeNonNullableScalar,
+});
diff --git a/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts
index e484b60f8f364..3062113f1b635 100644
--- a/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts
+++ b/x-pack/plugins/security_solution/server/graphql/source_status/schema.gql.ts
@@ -7,6 +7,8 @@
import gql from 'graphql-tag';
export const sourceStatusSchema = gql`
+ scalar ToIFieldSubTypeNonNullable
+
"A descriptor of a field in an index"
type IndexField {
"Where the field belong"
@@ -26,6 +28,9 @@ export const sourceStatusSchema = gql`
"Description of the field"
description: String
format: String
+ "the elastic type as mapped in the index"
+ esTypes: ToStringArrayNoNullable
+ subType: ToIFieldSubTypeNonNullable
}
extend type SourceStatus {
diff --git a/x-pack/plugins/security_solution/server/graphql/types.ts b/x-pack/plugins/security_solution/server/graphql/types.ts
index f8a614e86f28e..1e397a4e6bb6c 100644
--- a/x-pack/plugins/security_solution/server/graphql/types.ts
+++ b/x-pack/plugins/security_solution/server/graphql/types.ts
@@ -430,6 +430,10 @@ export enum FlowDirection {
biDirectional = 'biDirectional',
}
+export type ToStringArrayNoNullable = any;
+
+export type ToIFieldSubTypeNonNullable = any;
+
export type ToStringArray = string[] | string;
export type Date = string;
@@ -629,6 +633,10 @@ export interface IndexField {
description?: Maybe;
format?: Maybe;
+ /** the elastic type as mapped in the index */
+ esTypes?: Maybe;
+
+ subType?: Maybe;
}
export interface AuthenticationsData {
@@ -3579,6 +3587,10 @@ export namespace IndexFieldResolvers {
description?: DescriptionResolver, TypeParent, TContext>;
format?: FormatResolver, TypeParent, TContext>;
+ /** the elastic type as mapped in the index */
+ esTypes?: EsTypesResolver, TypeParent, TContext>;
+
+ subType?: SubTypeResolver, TypeParent, TContext>;
}
export type CategoryResolver = Resolver<
@@ -3626,6 +3638,16 @@ export namespace IndexFieldResolvers {
Parent = IndexField,
TContext = SiemContext
> = Resolver;
+ export type EsTypesResolver<
+ R = Maybe,
+ Parent = IndexField,
+ TContext = SiemContext
+ > = Resolver;
+ export type SubTypeResolver<
+ R = Maybe,
+ Parent = IndexField,
+ TContext = SiemContext
+ > = Resolver;
}
export namespace AuthenticationsDataResolvers {
@@ -9317,6 +9339,14 @@ export interface DeprecatedDirectiveArgs {
reason?: string;
}
+export interface ToStringArrayNoNullableScalarConfig
+ extends GraphQLScalarTypeConfig {
+ name: 'ToStringArrayNoNullable';
+}
+export interface ToIFieldSubTypeNonNullableScalarConfig
+ extends GraphQLScalarTypeConfig {
+ name: 'ToIFieldSubTypeNonNullable';
+}
export interface ToStringArrayScalarConfig extends GraphQLScalarTypeConfig {
name: 'ToStringArray';
}
@@ -9490,6 +9520,8 @@ export type IResolvers = {
EventsTimelineData?: EventsTimelineDataResolvers.Resolvers;
OsFields?: OsFieldsResolvers.Resolvers;
HostFields?: HostFieldsResolvers.Resolvers;
+ ToStringArrayNoNullable?: GraphQLScalarType;
+ ToIFieldSubTypeNonNullable?: GraphQLScalarType;
ToStringArray?: GraphQLScalarType;
Date?: GraphQLScalarType;
ToNumberArray?: GraphQLScalarType;
diff --git a/x-pack/plugins/security_solution/server/lib/index_fields/types.ts b/x-pack/plugins/security_solution/server/lib/index_fields/types.ts
index 0c894c6980a31..67b3c254007e2 100644
--- a/x-pack/plugins/security_solution/server/lib/index_fields/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/index_fields/types.ts
@@ -6,6 +6,7 @@
import { IndexField } from '../../graphql/types';
import { FrameworkRequest } from '../framework';
+import { IFieldSubType } from '../../../../../../src/plugins/data/common';
export interface FieldsAdapter {
getIndexFields(req: FrameworkRequest, indices: string[]): Promise;
@@ -16,4 +17,6 @@ export interface IndexFieldDescriptor {
type: string;
searchable: boolean;
aggregatable: boolean;
+ esTypes?: string[];
+ subType?: IFieldSubType;
}