Skip to content

Commit

Permalink
[Security Solution][Endpoint] Delete Event Filter from the List (#99686)
Browse files Browse the repository at this point in the history
* modal for delete of event filter
* Add total count of items to the List view
  • Loading branch information
paul-tavares authored May 13, 2021
1 parent 91136c9 commit 6319544
Show file tree
Hide file tree
Showing 14 changed files with 505 additions and 17 deletions.
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)
// @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

0 comments on commit 6319544

Please sign in to comment.