-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Security Solution][Detections] Add assignees UI into alerts table (#…
…7661) (#167079) ## Summary Closes elastic/security-team#7661 This PR adds Alert user assignment UI within alerts table. https://github.com/elastic/kibana/assets/2700761/df81928a-b0c7-46ba-98b3-3803774ed239
- Loading branch information
Showing
49 changed files
with
2,037 additions
and
8 deletions.
There are no files selected for viewing
8 changes: 8 additions & 0 deletions
8
x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
export * from './set_alert_assignees/set_alert_assignees_route'; |
8 changes: 8 additions & 0 deletions
8
x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/mocks.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
export * from './set_alert_assignees/set_alert_assignees_route.mock'; |
17 changes: 17 additions & 0 deletions
17
...pi/detection_engine/alert_assignees/set_alert_assignees/set_alert_assignees_route.mock.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
* 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 type { SetAlertAssigneesRequestBody } from './set_alert_assignees_route'; | ||
|
||
export const getSetAlertAssigneesRequestMock = ( | ||
assigneesToAdd: string[] = [], | ||
assigneesToRemove: string[] = [], | ||
ids: string[] = [] | ||
): SetAlertAssigneesRequestBody => ({ | ||
assignees: { assignees_to_add: assigneesToAdd, assignees_to_remove: assigneesToRemove }, | ||
ids, | ||
}); |
20 changes: 20 additions & 0 deletions
20
...mon/api/detection_engine/alert_assignees/set_alert_assignees/set_alert_assignees_route.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/* | ||
* 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 * as t from 'io-ts'; | ||
|
||
import { alert_assignee_ids, alert_assignees } from '../../model'; | ||
|
||
export const setAlertAssigneesRequestBody = t.exact( | ||
t.type({ | ||
assignees: alert_assignees, | ||
ids: alert_assignee_ids, | ||
}) | ||
); | ||
|
||
export type SetAlertAssigneesRequestBody = t.TypeOf<typeof setAlertAssigneesRequestBody>; | ||
export type SetAlertAssigneesRequestBodyDecoded = SetAlertAssigneesRequestBody; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
x-pack/plugins/security_solution/common/api/detection_engine/users/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
export * from './suggest_user_profiles/suggest_user_profiles_route'; |
19 changes: 19 additions & 0 deletions
19
...on/common/api/detection_engine/users/suggest_user_profiles/suggest_user_profiles_route.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/* | ||
* 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 * as t from 'io-ts'; | ||
|
||
import { user_search_term } from '../../model'; | ||
|
||
export const suggestUserProfilesRequestQuery = t.exact( | ||
t.partial({ | ||
searchTerm: user_search_term, | ||
}) | ||
); | ||
|
||
export type SuggestUserProfilesRequestQuery = t.TypeOf<typeof suggestUserProfilesRequestQuery>; | ||
export type SuggestUserProfilesRequestQueryDecoded = SuggestUserProfilesRequestQuery; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
189 changes: 189 additions & 0 deletions
189
...rity_solution/public/common/components/toolbar/bulk_actions/alert_bulk_assignees.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
/* | ||
* 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 type { TimelineItem } from '@kbn/timelines-plugin/common'; | ||
import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; | ||
import { act, fireEvent, render } from '@testing-library/react'; | ||
import React from 'react'; | ||
import { TestProviders } from '../../../mock'; | ||
import { useSuggestUsers } from '../../../../detections/containers/detection_engine/alerts/use_suggest_users'; | ||
|
||
import { BulkAlertAssigneesPanel } from './alert_bulk_assignees'; | ||
import { ALERT_WORKFLOW_ASSIGNEE_IDS } from '@kbn/rule-data-utils'; | ||
|
||
jest.mock('../../../../detections/containers/detection_engine/alerts/use_suggest_users'); | ||
|
||
const mockUserProfiles: UserProfileWithAvatar[] = [ | ||
{ uid: 'default-test-assignee-id-1', enabled: true, user: { username: 'user1' }, data: {} }, | ||
{ uid: 'default-test-assignee-id-2', enabled: true, user: { username: 'user2' }, data: {} }, | ||
]; | ||
|
||
const mockAssigneeItems = [ | ||
{ | ||
_id: 'test-id', | ||
data: [{ field: ALERT_WORKFLOW_ASSIGNEE_IDS, value: ['assignee-id-1', 'assignee-id-2'] }], | ||
ecs: { _id: 'test-id' }, | ||
}, | ||
]; | ||
|
||
(useSuggestUsers as jest.Mock).mockReturnValue({ loading: false, userProfiles: mockUserProfiles }); | ||
|
||
const renderAssigneesMenu = ( | ||
items: TimelineItem[], | ||
closePopover: () => void = jest.fn(), | ||
onSubmit: () => Promise<void> = jest.fn(), | ||
setIsLoading: () => void = jest.fn() | ||
) => { | ||
return render( | ||
<TestProviders> | ||
<BulkAlertAssigneesPanel | ||
alertItems={items} | ||
setIsLoading={setIsLoading} | ||
closePopoverMenu={closePopover} | ||
onSubmit={onSubmit} | ||
/> | ||
</TestProviders> | ||
); | ||
}; | ||
|
||
describe('BulkAlertAssigneesPanel', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
test('it renders', () => { | ||
const wrapper = renderAssigneesMenu(mockAssigneeItems); | ||
|
||
expect(wrapper.getByTestId('alert-assignees-update-button')).toBeInTheDocument(); | ||
expect(useSuggestUsers).toHaveBeenCalled(); | ||
}); | ||
|
||
test('it calls expected functions on submit when nothing has changed', () => { | ||
const mockedClosePopover = jest.fn(); | ||
const mockedOnSubmit = jest.fn(); | ||
const mockedSetIsLoading = jest.fn(); | ||
|
||
const mockAssignees = [ | ||
{ | ||
_id: 'test-id', | ||
data: [{ field: ALERT_WORKFLOW_ASSIGNEE_IDS, value: ['default-test-assignee-id-1'] }], | ||
ecs: { _id: 'test-id' }, | ||
}, | ||
{ | ||
_id: 'test-id', | ||
data: [ | ||
{ | ||
field: ALERT_WORKFLOW_ASSIGNEE_IDS, | ||
value: ['default-test-assignee-id-1', 'default-test-assignee-id-2'], | ||
}, | ||
], | ||
ecs: { _id: 'test-id' }, | ||
}, | ||
]; | ||
const wrapper = renderAssigneesMenu( | ||
mockAssignees, | ||
mockedClosePopover, | ||
mockedOnSubmit, | ||
mockedSetIsLoading | ||
); | ||
|
||
act(() => { | ||
fireEvent.click(wrapper.getByTestId('alert-assignees-update-button')); | ||
}); | ||
expect(mockedClosePopover).toHaveBeenCalled(); | ||
expect(mockedOnSubmit).not.toHaveBeenCalled(); | ||
expect(mockedSetIsLoading).not.toHaveBeenCalled(); | ||
}); | ||
|
||
test('it updates state correctly', () => { | ||
const mockAssignees = [ | ||
{ | ||
_id: 'test-id', | ||
data: [{ field: ALERT_WORKFLOW_ASSIGNEE_IDS, value: ['default-test-assignee-id-1'] }], | ||
ecs: { _id: 'test-id' }, | ||
}, | ||
{ | ||
_id: 'test-id', | ||
data: [ | ||
{ | ||
field: ALERT_WORKFLOW_ASSIGNEE_IDS, | ||
value: ['default-test-assignee-id-1', 'default-test-assignee-id-2'], | ||
}, | ||
], | ||
ecs: { _id: 'test-id' }, | ||
}, | ||
]; | ||
const wrapper = renderAssigneesMenu(mockAssignees); | ||
|
||
expect(wrapper.getAllByRole('option')[0]).toHaveAttribute('title', 'user1'); | ||
expect(wrapper.getAllByRole('option')[0]).toBeChecked(); | ||
act(() => { | ||
fireEvent.click(wrapper.getByText('user1')); | ||
}); | ||
expect(wrapper.getAllByRole('option')[0]).toHaveAttribute('title', 'user1'); | ||
expect(wrapper.getAllByRole('option')[0]).not.toBeChecked(); | ||
|
||
expect(wrapper.getAllByRole('option')[1]).toHaveAttribute('title', 'user2'); | ||
expect(wrapper.getAllByRole('option')[1]).not.toBeChecked(); | ||
act(() => { | ||
fireEvent.click(wrapper.getByText('user2')); | ||
}); | ||
expect(wrapper.getAllByRole('option')[1]).toHaveAttribute('title', 'user2'); | ||
expect(wrapper.getAllByRole('option')[1]).toBeChecked(); | ||
}); | ||
|
||
test('it calls expected functions on submit when alerts have changed', () => { | ||
const mockedClosePopover = jest.fn(); | ||
const mockedOnSubmit = jest.fn(); | ||
const mockedSetIsLoading = jest.fn(); | ||
|
||
const mockAssignees = [ | ||
{ | ||
_id: 'test-id', | ||
data: [{ field: ALERT_WORKFLOW_ASSIGNEE_IDS, value: ['default-test-assignee-id-1'] }], | ||
ecs: { _id: 'test-id' }, | ||
}, | ||
{ | ||
_id: 'test-id', | ||
data: [ | ||
{ | ||
field: ALERT_WORKFLOW_ASSIGNEE_IDS, | ||
value: ['default-test-assignee-id-1', 'default-test-assignee-id-2'], | ||
}, | ||
], | ||
ecs: { _id: 'test-id' }, | ||
}, | ||
]; | ||
const wrapper = renderAssigneesMenu( | ||
mockAssignees, | ||
mockedClosePopover, | ||
mockedOnSubmit, | ||
mockedSetIsLoading | ||
); | ||
act(() => { | ||
fireEvent.click(wrapper.getByText('user1')); | ||
}); | ||
act(() => { | ||
fireEvent.click(wrapper.getByText('user2')); | ||
}); | ||
|
||
act(() => { | ||
fireEvent.click(wrapper.getByTestId('alert-assignees-update-button')); | ||
}); | ||
expect(mockedClosePopover).toHaveBeenCalled(); | ||
expect(mockedOnSubmit).toHaveBeenCalled(); | ||
expect(mockedOnSubmit).toHaveBeenCalledWith( | ||
{ | ||
assignees_to_add: ['default-test-assignee-id-2'], | ||
assignees_to_remove: ['default-test-assignee-id-1'], | ||
}, | ||
['test-id', 'test-id'], | ||
expect.anything(), // An anonymous callback defined in the onSubmit function | ||
mockedSetIsLoading | ||
); | ||
}); | ||
}); |
Oops, something went wrong.