diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/index.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/index.tsx index 3457010181b981..86c2f2364961d5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/index.tsx @@ -19,6 +19,3 @@ export const EventFiltersContainer = () => { ); }; -export { EventFiltersService } from './types'; -export { EventFiltersListPageState } from './types'; -export { EventFiltersListPageData } from './types'; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts index b8fe7c142dbb1d..6a95ac5c15e836 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts @@ -92,4 +92,13 @@ export class EventFiltersHttpService implements EventFiltersService { body: JSON.stringify(exception), }); } + + async deleteOne(id: string): Promise { + return (await this.httpWrapper()).delete(EXCEPTION_LIST_ITEM_URL, { + query: { + id, + namespace_type: 'agnostic', + }, + }); + } } diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts index ece1e7385b67b6..4ae90e7abba906 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts @@ -22,6 +22,18 @@ export type EventFiltersListPageDataExistsChanged = Action<'eventFiltersListPage payload: EventFiltersListPageState['listPage']['dataExist']; }; +export type EventFilterForDeletion = Action<'eventFilterForDeletion'> & { + payload: ExceptionListItemSchema; +}; + +export type EventFilterDeletionReset = Action<'eventFilterDeletionReset'>; + +export type EventFilterDeleteSubmit = Action<'eventFilterDeleteSubmit'>; + +export type EventFilterDeleteStatusChanged = Action<'eventFilterDeleteStatusChanged'> & { + payload: EventFiltersListPageState['listPage']['deletion']['status']; +}; + export type EventFiltersInitForm = Action<'eventFiltersInitForm'> & { payload: { entry: UpdateExceptionListItemSchema | CreateExceptionListItemSchema; @@ -65,4 +77,8 @@ export type EventFiltersPageAction = | EventFiltersCreateStart | EventFiltersCreateSuccess | EventFiltersCreateError - | EventFiltersFormStateChanged; + | EventFiltersFormStateChanged + | EventFilterForDeletion + | EventFilterDeletionReset + | EventFilterDeleteSubmit + | EventFilterDeleteStatusChanged; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/builders.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/builders.ts index 5a8cb4834c4b5f..30722a33270ee5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/builders.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/builders.ts @@ -7,6 +7,7 @@ import { MANAGEMENT_DEFAULT_PAGE, MANAGEMENT_DEFAULT_PAGE_SIZE } from '../../../common/constants'; import { EventFiltersListPageState } from '../types'; +import { createLoadedResourceState, createUninitialisedResourceState } from '../../../state'; export const initialEventFiltersPageState = (): EventFiltersListPageState => ({ entries: [], @@ -16,7 +17,7 @@ export const initialEventFiltersPageState = (): EventFiltersListPageState => ({ hasItemsError: false, hasOSError: false, newComment: '', - submissionResourceState: { type: 'UninitialisedResourceState' }, + submissionResourceState: createUninitialisedResourceState(), }, location: { page_index: MANAGEMENT_DEFAULT_PAGE, @@ -26,8 +27,12 @@ export const initialEventFiltersPageState = (): EventFiltersListPageState => ({ listPage: { active: false, forceRefresh: false, - data: { type: 'UninitialisedResourceState' }, - /** We started off assuming data exists, until we can confirm othewise */ - dataExist: { type: 'LoadedResourceState', data: true }, + data: createUninitialisedResourceState(), + /** We started off assuming data exists, until we can confirm otherwise */ + dataExist: createLoadedResourceState(true), + deletion: { + item: undefined, + status: createUninitialisedResourceState(), + }, }, }); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts index 787447797158d8..b55a32a937c458 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts @@ -28,6 +28,7 @@ const createEventFiltersServiceMock = (): jest.Mocked => ({ getList: jest.fn(), getOne: jest.fn(), updateOne: jest.fn(), + deleteOne: jest.fn(), }); const createStoreSetup = (eventFiltersService: EventFiltersService) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts index 1bd1340326d09a..b32b39fb9293c5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts @@ -14,8 +14,6 @@ import { import { EventFiltersHttpService } from '../service'; -import { getLastLoadedResourceState } from '../../../state/async_resource_state'; - import { CreateExceptionListItemSchema, ExceptionListItemSchema, @@ -33,6 +31,9 @@ import { getFormEntry, getSubmissionResource, getNewComment, + isDeletionInProgress, + getItemToDelete, + getDeletionState, } from './selector'; import { parseQueryFilterToKQL } from '../../../common/utils'; @@ -47,7 +48,9 @@ import { createFailedResourceState, createLoadedResourceState, createLoadingResourceState, + getLastLoadedResourceState, } from '../../../state'; +import { ServerApiError } from '../../../../common/types'; const addNewComments = ( entry: UpdateExceptionListItemSchema | CreateExceptionListItemSchema, @@ -279,6 +282,43 @@ const refreshListDataIfNeeded: MiddlewareActionHandler = async (store, eventFilt } }; +const eventFilterDeleteEntry: MiddlewareActionHandler = async ( + { getState, dispatch }, + eventFiltersService +) => { + const state = getState(); + + if (isDeletionInProgress(state)) { + return; + } + + const itemId = getItemToDelete(state)?.id; + + if (!itemId) { + return; + } + + dispatch({ + type: 'eventFilterDeleteStatusChanged', + // Ignore will be fixed with when AsyncResourceState is refactored (#830) + // @ts-ignore + payload: createLoadingResourceState(getDeletionState(state).status), + }); + + try { + const response = await eventFiltersService.deleteOne(itemId); + dispatch({ + type: 'eventFilterDeleteStatusChanged', + payload: createLoadedResourceState(response), + }); + } catch (e) { + dispatch({ + type: 'eventFilterDeleteStatusChanged', + payload: createFailedResourceState(e.body ?? e), + }); + } +}; + export const createEventFiltersPageMiddleware = ( eventFiltersService: EventFiltersService ): ImmutableMiddleware => { @@ -298,9 +338,12 @@ export const createEventFiltersPageMiddleware = ( if ( action.type === 'userChangedUrl' || action.type === 'eventFiltersCreateSuccess' || - action.type === 'eventFiltersUpdateSuccess' + action.type === 'eventFiltersUpdateSuccess' || + action.type === 'eventFilterDeleteStatusChanged' ) { refreshListDataIfNeeded(store, eventFiltersService); + } else if (action.type === 'eventFilterDeleteSubmit') { + eventFilterDeleteEntry(store, eventFiltersService); } } }; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts index 323d063f31b877..d69efb689c8779 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts @@ -14,7 +14,10 @@ import { AppLocation, Immutable } from '../../../../../common/endpoint/types'; import { UserChangedUrl } from '../../../../common/store/routing/action'; import { MANAGEMENT_ROUTING_EVENT_FILTERS_PATH } from '../../../common/constants'; import { extractEventFiltetrsPageLocation } from '../../../common/routing'; -import { isUninitialisedResourceState } from '../../../state/async_resource_state'; +import { + isLoadedResourceState, + isUninitialisedResourceState, +} from '../../../state/async_resource_state'; import { EventFiltersInitForm, @@ -24,6 +27,9 @@ import { EventFiltersUpdateSuccess, EventFiltersListPageDataChanged, EventFiltersListPageDataExistsChanged, + EventFilterForDeletion, + EventFilterDeletionReset, + EventFilterDeleteStatusChanged, } from './action'; import { initialEventFiltersPageState } from './builders'; @@ -173,6 +179,46 @@ const userChangedUrl: CaseReducer = (state, action) => { } }; +const handleEventFilterForDeletion: CaseReducer = (state, action) => { + return { + ...state, + listPage: { + ...state.listPage, + deletion: { + ...state.listPage.deletion, + item: action.payload, + }, + }, + }; +}; + +const handleEventFilterDeletionReset: CaseReducer = (state) => { + return { + ...state, + listPage: { + ...state.listPage, + deletion: initialEventFiltersPageState().listPage.deletion, + }, + }; +}; + +const handleEventFilterDeleteStatusChanges: CaseReducer = ( + state, + action +) => { + return { + ...state, + listPage: { + ...state.listPage, + forceRefresh: isLoadedResourceState(action.payload) ? true : state.listPage.forceRefresh, + deletion: { + ...state.listPage.deletion, + status: action.payload, + }, + }, + }; +}; + export const eventFiltersPageReducer: StateReducer = ( state = initialEventFiltersPageState(), action @@ -199,6 +245,12 @@ export const eventFiltersPageReducer: StateReducer = ( return handleEventFiltersListPageDataChanges(state, action); case 'eventFiltersListPageDataExistsChanged': return handleEventFiltersListPageDataExistChanges(state, action); + case 'eventFilterForDeletion': + return handleEventFilterForDeletion(state, action); + case 'eventFilterDeletionReset': + return handleEventFilterDeletionReset(state, action); + case 'eventFilterDeleteStatusChanged': + return handleEventFilterDeleteStatusChanges(state, action); } } diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts index c24d300a5584d5..e6f67f7329e6a0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts @@ -183,3 +183,42 @@ export const listDataNeedsRefresh: EventFiltersSelector = createSelecto ); } ); + +export const getDeletionState = createSelector( + getCurrentListPageState, + (listState) => listState.deletion +); + +export const showDeleteModal: EventFiltersSelector = createSelector( + getDeletionState, + ({ item }) => { + return Boolean(item); + } +); + +export const getItemToDelete: EventFiltersSelector< + StoreState['listPage']['deletion']['item'] +> = createSelector(getDeletionState, ({ item }) => item); + +export const isDeletionInProgress: EventFiltersSelector = createSelector( + getDeletionState, + ({ status }) => { + return isLoadingResourceState(status); + } +); + +export const wasDeletionSuccessful: EventFiltersSelector = createSelector( + getDeletionState, + ({ status }) => { + return isLoadedResourceState(status); + } +); + +export const getDeleteError: EventFiltersSelector = createSelector( + getDeletionState, + ({ status }) => { + if (isFailedResourceState(status)) { + return status.error; + } + } +); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/types.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/types.ts index 80abe225bf289a..cc70a2037a5afa 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/types.ts @@ -48,6 +48,7 @@ export interface EventFiltersService { getList(options?: EventFiltersServiceGetListOptions): Promise; getOne(id: string): Promise; updateOne(exception: Immutable): Promise; + deleteOne(id: string): Promise; } export interface EventFiltersListPageData { @@ -68,5 +69,10 @@ export interface EventFiltersListPageState { data: AsyncResourceState; /** tracks if the overall list (not filtered or with invalid page numbers) contains data */ dataExist: AsyncResourceState; + /** state for deletion of items from the list */ + deletion: { + item: ExceptionListItemSchema | undefined; + status: AsyncResourceState; + }; }; } diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.test.tsx new file mode 100644 index 00000000000000..cec3e34d9c98fd --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.test.tsx @@ -0,0 +1,152 @@ +/* + * 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 { + AppContextTestRender, + createAppRootMockRenderer, +} from '../../../../../common/mock/endpoint'; +import { act } from '@testing-library/react'; +import React from 'react'; +import { EventFilterDeleteModal } from './event_filter_delete_modal'; +import { fireEvent } from '@testing-library/dom'; +import { showDeleteModal } from '../../store/selector'; +import { isFailedResourceState, isLoadedResourceState } from '../../../../state'; + +describe('When event filters delete modal is shown', () => { + let renderAndSetup: () => Promise>; + let renderResult: ReturnType; + let coreStart: AppContextTestRender['coreStart']; + let history: AppContextTestRender['history']; + let waitForAction: AppContextTestRender['middlewareSpy']['waitForAction']; + let store: AppContextTestRender['store']; + + const getBody = () => + renderResult.baseElement.querySelector('[data-test-subj="eventFilterDeleteModalBody"]')!; + + const getConfirmButton = () => + renderResult.baseElement.querySelector( + '[data-test-subj="eventFilterDeleteModalConfirmButton"]' + )! as HTMLButtonElement; + + const getCancelButton = () => + renderResult.baseElement.querySelector( + '[data-test-subj="eventFilterDeleteModalCancelButton"]' + )! as HTMLButtonElement; + + const getCurrentState = () => store.getState().management.eventFilters; + + beforeEach(() => { + const mockedContext = createAppRootMockRenderer(); + + ({ history, store, coreStart } = mockedContext); + renderAndSetup = async () => { + renderResult = mockedContext.render(); + + await act(async () => { + history.push('/event_filters'); + + await waitForAction('userChangedUrl'); + + mockedContext.store.dispatch({ + type: 'eventFilterForDeletion', + payload: { + id: '123', + name: 'tic-tac-toe', + }, + }); + }); + + return renderResult; + }; + + waitForAction = mockedContext.middlewareSpy.waitForAction; + mockedContext.setExperimentalFlag({ eventFilteringEnabled: true }); + }); + + it('should display name of event filter in body message', async () => { + await renderAndSetup(); + expect(getBody().textContent).toMatch(/You are removing event filter "tic-tac-toe"/); + }); + + it('should close dialog if cancel button is clicked', async () => { + await renderAndSetup(); + act(() => { + fireEvent.click(getCancelButton()); + }); + + expect(showDeleteModal(getCurrentState())).toBe(false); + }); + + it('should close dialog if the close X button is clicked', async () => { + await renderAndSetup(); + const dialogCloseButton = renderResult.baseElement.querySelector( + '[aria-label="Closes this modal window"]' + )!; + act(() => { + fireEvent.click(dialogCloseButton); + }); + + expect(showDeleteModal(getCurrentState())).toBe(false); + }); + + it('should disable action buttons when confirmed', async () => { + await renderAndSetup(); + act(() => { + fireEvent.click(getConfirmButton()); + }); + + expect(getCancelButton().disabled).toBe(true); + expect(getConfirmButton().disabled).toBe(true); + }); + + it('should set confirm button to loading', async () => { + await renderAndSetup(); + act(() => { + fireEvent.click(getConfirmButton()); + }); + + expect(getConfirmButton().querySelector('.euiLoadingSpinner')).not.toBeNull(); + }); + + it('should show success toast', async () => { + await renderAndSetup(); + const updateCompleted = waitForAction('eventFilterDeleteStatusChanged', { + validate(action) { + return isLoadedResourceState(action.payload); + }, + }); + + await act(async () => { + fireEvent.click(getConfirmButton()); + await updateCompleted; + }); + + expect(coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith( + '"tic-tac-toe" has been removed from the Event Filters list.' + ); + }); + + it('should show error toast if error is countered', async () => { + coreStart.http.delete.mockRejectedValue(new Error('oh oh')); + await renderAndSetup(); + const updateFailure = waitForAction('eventFilterDeleteStatusChanged', { + validate(action) { + return isFailedResourceState(action.payload); + }, + }); + + await act(async () => { + fireEvent.click(getConfirmButton()); + await updateFailure; + }); + + expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledWith( + 'Unable to remove "tic-tac-toe" from the Event Filters list. Reason: oh oh' + ); + expect(showDeleteModal(getCurrentState())).toBe(true); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.tsx new file mode 100644 index 00000000000000..74a023965a57d5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.tsx @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, useCallback, useEffect } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiText, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; +import { i18n } from '@kbn/i18n'; +import { useEventFiltersSelector } from '../hooks'; +import { + getDeleteError, + getItemToDelete, + isDeletionInProgress, + wasDeletionSuccessful, +} from '../../store/selector'; +import { AppAction } from '../../../../../common/store/actions'; +import { useToasts } from '../../../../../common/lib/kibana'; + +export const EventFilterDeleteModal = memo<{}>(() => { + const dispatch = useDispatch>(); + const toasts = useToasts(); + + const isDeleting = useEventFiltersSelector(isDeletionInProgress); + const eventFilter = useEventFiltersSelector(getItemToDelete); + const wasDeleted = useEventFiltersSelector(wasDeletionSuccessful); + const deleteError = useEventFiltersSelector(getDeleteError); + + const onCancel = useCallback(() => { + dispatch({ type: 'eventFilterDeletionReset' }); + }, [dispatch]); + + const onConfirm = useCallback(() => { + dispatch({ type: 'eventFilterDeleteSubmit' }); + }, [dispatch]); + + // Show toast for success + useEffect(() => { + if (wasDeleted) { + toasts.addSuccess( + i18n.translate('xpack.securitySolution.eventFilters.deletionDialog.deleteSuccess', { + defaultMessage: '"{name}" has been removed from the Event Filters list.', + values: { name: eventFilter?.name }, + }) + ); + + dispatch({ type: 'eventFilterDeletionReset' }); + } + }, [dispatch, eventFilter?.name, toasts, wasDeleted]); + + // show toast for failures + useEffect(() => { + if (deleteError) { + toasts.addDanger( + i18n.translate('xpack.securitySolution.eventFilters.deletionDialog.deleteFailure', { + defaultMessage: + 'Unable to remove "{name}" from the Event Filters list. Reason: {message}', + values: { name: eventFilter?.name, message: deleteError.message }, + }) + ); + } + }, [deleteError, eventFilter?.name, toasts]); + + return ( + + + + + + + + + +

+ {eventFilter?.name} }} + /> +

+

+ +

+
+
+ + + + + + + + + + +
+ ); +}); + +EventFilterDeleteModal.displayName = 'EventFilterDeleteModal'; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx index edd919351a151d..2fbabad746cad7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx @@ -140,6 +140,18 @@ describe('When on the Event Filters List Page', () => { expect(renderResult.getByTestId('eventFiltersContent-error').textContent).toEqual(' oh no'); }); + + it('should show modal when delete is clicked on a card', async () => { + render(); + await dataReceived(); + act(() => { + fireEvent.click(renderResult.getByTestId('exceptionsViewerDeleteBtn')); + }); + + expect( + renderResult.baseElement.querySelector('[data-test-subj="eventFilterDeleteModalHeader"]') + ).not.toBeNull(); + }); }); describe('And search is dispatched', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx index 0e78f67efb433f..0898f93cf6f9b4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx @@ -11,7 +11,7 @@ import { Dispatch } from 'redux'; import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton, EuiSpacer } from '@elastic/eui'; +import { EuiButton, EuiSpacer, EuiHorizontalRule, EuiText } from '@elastic/eui'; import styled from 'styled-components'; import { AppAction } from '../../../../common/store/actions'; @@ -30,6 +30,7 @@ import { getListPageDoesDataExist, getActionError, getFormEntry, + showDeleteModal, } from '../store/selector'; import { PaginatedContent, PaginatedContentProps } from '../../../components/paginated_content'; import { ExceptionListItemSchema } from '../../../../../../lists/common'; @@ -38,6 +39,7 @@ import { ExceptionItem, ExceptionItemProps, } from '../../../../common/components/exceptions/viewer/exception_item'; +import { EventFilterDeleteModal } from './components/event_filter_delete_modal'; import { SearchBar } from '../../../components/search_bar'; @@ -67,6 +69,7 @@ export const EventFiltersListPage = memo(() => { const fetchError = useEventFiltersSelector(getListFetchError); const location = useEventFiltersSelector(getCurrentLocation); const doesDataExist = useEventFiltersSelector(getListPageDoesDataExist); + const showDelete = useEventFiltersSelector(showDeleteModal); const navigateCallback = useEventFiltersNavigateCallback(); const showFlyout = !!location.show; @@ -128,9 +131,16 @@ export const EventFiltersListPage = memo(() => { [navigateCallback] ); - const handleItemDelete: ExceptionItemProps['onDeleteException'] = useCallback((args) => { - // TODO: implement delete item - }, []); + const handleItemDelete: ExceptionItemProps['onDeleteException'] = useCallback( + ({ id }) => { + dispatch({ + type: 'eventFilterForDeletion', + // Casting below needed due to error around the comments array needing to be mutable + payload: listItems.find((item) => item.id === id)! as ExceptionListItemSchema, + }); + }, + [dispatch, listItems] + ); const handleItemComponentProps: EventListPaginatedContent['itemComponentProps'] = useCallback( (exceptionItem) => ({ @@ -199,12 +209,23 @@ export const EventFiltersListPage = memo(() => { /> )} + {showDelete && } + {doesDataExist && ( <> + + + + )} + , typeof ExceptionItem> items={listItems} ItemComponent={ExceptionItem} diff --git a/x-pack/plugins/security_solution/public/management/types.ts b/x-pack/plugins/security_solution/public/management/types.ts index 7d345b084aca5a..cadb3b91f66a60 100644 --- a/x-pack/plugins/security_solution/public/management/types.ts +++ b/x-pack/plugins/security_solution/public/management/types.ts @@ -10,7 +10,7 @@ import { SecurityPageName } from '../app/types'; import { PolicyDetailsState } from './pages/policy/types'; import { EndpointState } from './pages/endpoint_hosts/types'; import { TrustedAppsListPageState } from './pages/trusted_apps/state'; -import { EventFiltersListPageState } from './pages/event_filters'; +import { EventFiltersListPageState } from './pages/event_filters/types'; /** * The type for the management store global namespace. Used mostly internally to reference