Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution][Endpoint] Delete Event Filter from the List #99686

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,3 @@ export const EventFiltersContainer = () => {
</Switch>
);
};
export { EventFiltersService } from './types';
export { EventFiltersListPageState } from './types';
export { EventFiltersListPageData } from './types';
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,13 @@ export class EventFiltersHttpService implements EventFiltersService {
body: JSON.stringify(exception),
});
}

async deleteOne(id: string): Promise<ExceptionListItemSchema> {
return (await this.httpWrapper()).delete<ExceptionListItemSchema>(EXCEPTION_LIST_ITEM_URL, {
query: {
id,
namespace_type: 'agnostic',
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -65,4 +77,8 @@ export type EventFiltersPageAction =
| EventFiltersCreateStart
| EventFiltersCreateSuccess
| EventFiltersCreateError
| EventFiltersFormStateChanged;
| EventFiltersFormStateChanged
| EventFilterForDeletion
| EventFilterDeletionReset
| EventFilterDeleteSubmit
| EventFilterDeleteStatusChanged;
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
Expand All @@ -16,7 +17,7 @@ export const initialEventFiltersPageState = (): EventFiltersListPageState => ({
hasItemsError: false,
hasOSError: false,
newComment: '',
submissionResourceState: { type: 'UninitialisedResourceState' },
submissionResourceState: createUninitialisedResourceState(),
},
location: {
page_index: MANAGEMENT_DEFAULT_PAGE,
Expand All @@ -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(),
},
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const createEventFiltersServiceMock = (): jest.Mocked<EventFiltersService> => ({
getList: jest.fn(),
getOne: jest.fn(),
updateOne: jest.fn(),
deleteOne: jest.fn(),
});

const createStoreSetup = (eventFiltersService: EventFiltersService) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import {

import { EventFiltersHttpService } from '../service';

import { getLastLoadedResourceState } from '../../../state/async_resource_state';

import {
CreateExceptionListItemSchema,
ExceptionListItemSchema,
Expand All @@ -33,6 +31,9 @@ import {
getFormEntry,
getSubmissionResource,
getNewComment,
isDeletionInProgress,
getItemToDelete,
getDeletionState,
} from './selector';

import { parseQueryFilterToKQL } from '../../../common/utils';
Expand All @@ -47,7 +48,9 @@ import {
createFailedResourceState,
createLoadedResourceState,
createLoadingResourceState,
getLastLoadedResourceState,
} from '../../../state';
import { ServerApiError } from '../../../../common/types';

const addNewComments = (
entry: UpdateExceptionListItemSchema | CreateExceptionListItemSchema,
Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// @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<ExceptionListItemSchema, ServerApiError>(e.body ?? e),
});
}
};

export const createEventFiltersPageMiddleware = (
eventFiltersService: EventFiltersService
): ImmutableMiddleware<EventFiltersListPageState, AppAction> => {
Expand All @@ -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);
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -24,6 +27,9 @@ import {
EventFiltersUpdateSuccess,
EventFiltersListPageDataChanged,
EventFiltersListPageDataExistsChanged,
EventFilterForDeletion,
EventFilterDeletionReset,
EventFilterDeleteStatusChanged,
} from './action';

import { initialEventFiltersPageState } from './builders';
Expand Down Expand Up @@ -173,6 +179,46 @@ const userChangedUrl: CaseReducer<UserChangedUrl> = (state, action) => {
}
};

const handleEventFilterForDeletion: CaseReducer<EventFilterForDeletion> = (state, action) => {
return {
...state,
listPage: {
...state.listPage,
deletion: {
...state.listPage.deletion,
item: action.payload,
},
},
};
};

const handleEventFilterDeletionReset: CaseReducer<EventFilterDeletionReset> = (state) => {
return {
...state,
listPage: {
...state.listPage,
deletion: initialEventFiltersPageState().listPage.deletion,
},
};
};

const handleEventFilterDeleteStatusChanges: CaseReducer<EventFilterDeleteStatusChanged> = (
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
Expand All @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,42 @@ export const listDataNeedsRefresh: EventFiltersSelector<boolean> = createSelecto
);
}
);

export const getDeletionState = createSelector(
getCurrentListPageState,
(listState) => listState.deletion
);

export const showDeleteModal: EventFiltersSelector<boolean> = createSelector(
getDeletionState,
({ item }) => {
return Boolean(item);
}
);

export const getItemToDelete: EventFiltersSelector<
StoreState['listPage']['deletion']['item']
> = createSelector(getDeletionState, ({ item }) => item);

export const isDeletionInProgress: EventFiltersSelector<boolean> = createSelector(
getDeletionState,
({ status }) => {
return isLoadingResourceState(status);
}
);

export const wasDeletionSuccessful: EventFiltersSelector<boolean> = createSelector(
getDeletionState,
({ status }) => {
return isLoadedResourceState(status);
}
);

export const getDeleteError: EventFiltersSelector<ServerApiError | undefined> = createSelector(
getDeletionState,
({ status }) => {
if (isFailedResourceState(status)) {
return status.error;
}
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface EventFiltersService {
getList(options?: EventFiltersServiceGetListOptions): Promise<FoundExceptionListItemSchema>;
getOne(id: string): Promise<ExceptionListItemSchema>;
updateOne(exception: Immutable<UpdateExceptionListItemSchema>): Promise<ExceptionListItemSchema>;
deleteOne(id: string): Promise<ExceptionListItemSchema>;
}

export interface EventFiltersListPageData {
Expand All @@ -68,5 +69,10 @@ export interface EventFiltersListPageState {
data: AsyncResourceState<EventFiltersListPageData>;
/** tracks if the overall list (not filtered or with invalid page numbers) contains data */
dataExist: AsyncResourceState<boolean>;
/** state for deletion of items from the list */
deletion: {
item: ExceptionListItemSchema | undefined;
status: AsyncResourceState<ExceptionListItemSchema>;
};
};
}
Loading