Skip to content

Commit

Permalink
[8.x] [Security Solution][Notes] - fix createdBy filter for notes man…
Browse files Browse the repository at this point in the history
…agement page (#197706) (#198088)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution][Notes] - fix createdBy filter for notes
management page
(#197706)](#197706)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Philippe
Oberti","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-10-25T20:37:23Z","message":"[Security
Solution][Notes] - fix createdBy filter for notes management page
(#197706)","sha":"1065bbf03ca0583935d9b482939823e6b46c3c52","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["backport","release_note:skip","v9.0.0","Team:Threat
Hunting:Investigations","v8.16.0"],"number":197706,"url":"https://github.com/elastic/kibana/pull/197706","mergeCommit":{"message":"[Security
Solution][Notes] - fix createdBy filter for notes management page
(#197706)","sha":"1065bbf03ca0583935d9b482939823e6b46c3c52"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/197706","number":197706,"mergeCommit":{"message":"[Security
Solution][Notes] - fix createdBy filter for notes management page
(#197706)","sha":"1065bbf03ca0583935d9b482939823e6b46c3c52"}},{"branch":"8.16","label":"v8.16.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/197911","number":197911,"state":"MERGED","mergeCommit":{"sha":"96044028628a6b3e63cb834f1ac8f82fecd7052e","message":"[8.16]
[Security Solution][Notes] - fix createdBy filter for notes management
page (#197706) (#197911)\n\n# Backport\n\nThis will backport the
following commits from `main` to `8.16`:\n- [[Security Solution][Notes]
- fix createdBy filter for notes\nmanagement
page\n(#197706)](https://github.com/elastic/kibana/pull/197706)\n\n<!---
Backport version: 8.9.8 -->\n\n### Questions ?\nPlease refer to the
[Backport
tool\ndocumentation](https://github.com/sqren/backport)\n\n<!--BACKPORT
[{\"author\":{\"name\":\"Philippe\nOberti\",\"email\":\"[email protected]\"},\"sourceCommit\":{\"committedDate\":\"2024-10-25T20:37:23Z\",\"message\":\"[Security\nSolution][Notes]
- fix createdBy filter for notes management
page\n(#197706)\",\"sha\":\"1065bbf03ca0583935d9b482939823e6b46c3c52\",\"branchLabelMapping\":{\"^v9.0.0$\":\"main\",\"^v8.17.0$\":\"8.x\",\"^v(\\\\d+).(\\\\d+).\\\\d+$\":\"$1.$2\"}},\"sourcePullRequest\":{\"labels\":[\"backport\",\"release_note:skip\",\"v9.0.0\",\"Team:Threat\nHunting:Investigations\",\"v8.16.0\"],\"number\":197706,\"url\":\"https://github.com/elastic/kibana/pull/197706\",\"mergeCommit\":{\"message\":\"[Security\nSolution][Notes]
- fix createdBy filter for notes management
page\n(#197706)\",\"sha\":\"1065bbf03ca0583935d9b482939823e6b46c3c52\"}},\"sourceBranch\":\"main\",\"suggestedTargetBranches\":[\"8.16\"],\"targetPullRequestStates\":[{\"branch\":\"main\",\"label\":\"v9.0.0\",\"labelRegex\":\"^v9.0.0$\",\"isSourceBranch\":true,\"state\":\"MERGED\",\"url\":\"https://github.com/elastic/kibana/pull/197706\",\"number\":197706,\"mergeCommit\":{\"message\":\"[Security\nSolution][Notes]
- fix createdBy filter for notes management
page\n(#197706)\",\"sha\":\"1065bbf03ca0583935d9b482939823e6b46c3c52\"}},{\"branch\":\"8.16\",\"label\":\"v8.16.0\",\"labelRegex\":\"^v(\\\\d+).(\\\\d+).\\\\d+$\",\"isSourceBranch\":false,\"state\":\"NOT_CREATED\"}]}]\nBACKPORT-->\n\n---------\n\nCo-authored-by:
kibanamachine <[email protected]>"}}]}]
BACKPORT-->
  • Loading branch information
PhilippeOberti authored Oct 28, 2024
1 parent 5fb32ba commit 68b0489
Show file tree
Hide file tree
Showing 23 changed files with 174 additions and 85 deletions.
2 changes: 1 addition & 1 deletion oas_docs/output/kibana.serverless.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14812,7 +14812,7 @@ paths:
nullable: true
type: string
- in: query
name: userFilter
name: createdByFilter
schema:
nullable: true
type: string
Expand Down
2 changes: 1 addition & 1 deletion oas_docs/output/kibana.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18242,7 +18242,7 @@ paths:
nullable: true
type: string
- in: query
name: userFilter
name: createdByFilter
schema:
nullable: true
type: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export const ALERT_SUPPRESSION_RULE_DETAILS = i18n.translate(
);

export const UPGRADE_NOTES_MANAGEMENT_USER_FILTER = (requiredLicense: string) =>
i18n.translate('securitySolutionPackages.noteManagement.userFilter.upsell', {
defaultMessage: 'Upgrade to {requiredLicense} to make use of user filters',
i18n.translate('securitySolutionPackages.noteManagement.createdByFilter.upsell', {
defaultMessage: 'Upgrade to {requiredLicense} to make use of createdBy filter',
values: {
requiredLicense,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const GetNotesRequestQuery = z.object({
sortField: z.string().nullable().optional(),
sortOrder: z.string().nullable().optional(),
filter: z.string().nullable().optional(),
userFilter: z.string().nullable().optional(),
createdByFilter: z.string().nullable().optional(),
associatedFilter: AssociatedFilterType.optional(),
});
export type GetNotesRequestQueryInput = z.input<typeof GetNotesRequestQuery>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ paths:
type: string
nullable: true
- in: query
name: userFilter
name: createdByFilter
schema:
nullable: true
type: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ paths:
nullable: true
type: string
- in: query
name: userFilter
name: createdByFilter
schema:
nullable: true
type: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ paths:
nullable: true
type: string
- in: query
name: userFilter
name: createdByFilter
schema:
nullable: true
type: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ export const mockGlobalState: State = {
direction: 'desc' as const,
},
filter: '',
userFilter: '',
createdByFilter: '',
associatedFilter: AssociatedFilter.all,
search: '',
selectedIds: [],
Expand Down
6 changes: 3 additions & 3 deletions x-pack/plugins/security_solution/public/notes/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const fetchNotes = async ({
sortField,
sortOrder,
filter,
userFilter,
createdByFilter,
associatedFilter,
search,
}: {
Expand All @@ -52,7 +52,7 @@ export const fetchNotes = async ({
sortField: string;
sortOrder: string;
filter: string;
userFilter: string;
createdByFilter: string;
associatedFilter: AssociatedFilter;
search: string;
}) => {
Expand All @@ -63,7 +63,7 @@ export const fetchNotes = async ({
sortField,
sortOrder,
filter,
userFilter,
createdByFilter,
associatedFilter,
search,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

import { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { UserFilterDropdown } from './user_filter_dropdown';
import { USER_SELECT_TEST_ID } from './test_ids';
import { CreatedByFilterDropdown } from './created_by_filter_dropdown';
import { CREATED_BY_SELECT_TEST_ID } from './test_ids';
import { useSuggestUsers } from '../../common/components/user_profiles/use_suggest_users';
import { useLicense } from '../../common/hooks/use_license';
import { useUpsellingMessage } from '../../common/hooks/use_upselling';
Expand All @@ -32,16 +32,25 @@ describe('UserFilterDropdown', () => {
jest.clearAllMocks();
(useSuggestUsers as jest.Mock).mockReturnValue({
isLoading: false,
data: [{ user: { username: 'test' } }, { user: { username: 'elastic' } }],
data: [
{
uid: '1',
user: { username: 'test' },
},
{
uid: '2',
user: { username: 'elastic' },
},
],
});
(useLicense as jest.Mock).mockReturnValue({ isPlatinumPlus: () => true });
(useUpsellingMessage as jest.Mock).mockReturnValue('upsellingMessage');
});

it('should render the component enabled', () => {
const { getByTestId } = render(<UserFilterDropdown />);
const { getByTestId } = render(<CreatedByFilterDropdown />);

const dropdown = getByTestId(USER_SELECT_TEST_ID);
const dropdown = getByTestId(CREATED_BY_SELECT_TEST_ID);

expect(dropdown).toBeInTheDocument();
expect(dropdown).not.toHaveClass('euiComboBox-isDisabled');
Expand All @@ -50,13 +59,13 @@ describe('UserFilterDropdown', () => {
it('should render the dropdown disabled', async () => {
(useLicense as jest.Mock).mockReturnValue({ isPlatinumPlus: () => false });

const { getByTestId } = render(<UserFilterDropdown />);
const { getByTestId } = render(<CreatedByFilterDropdown />);

expect(getByTestId(USER_SELECT_TEST_ID)).toHaveClass('euiComboBox-isDisabled');
expect(getByTestId(CREATED_BY_SELECT_TEST_ID)).toHaveClass('euiComboBox-isDisabled');
});

it('should call the correct action when select a user', async () => {
const { getByTestId } = render(<UserFilterDropdown />);
const { getByTestId } = render(<CreatedByFilterDropdown />);

const userSelect = getByTestId('comboBoxSearchInput');
userSelect.focus();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,26 @@ import { i18n } from '@kbn/i18n';
import type { EuiComboBoxOptionOption } from '@elastic/eui/src/components/combo_box/types';
import { useLicense } from '../../common/hooks/use_license';
import { useUpsellingMessage } from '../../common/hooks/use_upselling';
import { USER_SELECT_TEST_ID } from './test_ids';
import { CREATED_BY_SELECT_TEST_ID } from './test_ids';
import { useSuggestUsers } from '../../common/components/user_profiles/use_suggest_users';
import { userFilterUsers } from '..';
import { userFilterCreatedBy } from '..';

export const USERS_DROPDOWN = i18n.translate('xpack.securitySolution.notes.usersDropdownLabel', {
defaultMessage: 'Users',
export const CREATED_BY = i18n.translate('xpack.securitySolution.notes.createdByDropdownLabel', {
defaultMessage: 'Created by',
});

export const UserFilterDropdown = React.memo(() => {
interface User {
/**
* uuid of the UserProfile
*/
id: string;
/**
* full_name || email || username of the UserProfile
*/
label: string;
}

export const CreatedByFilterDropdown = React.memo(() => {
const dispatch = useDispatch();
const isPlatinumPlus = useLicense().isPlatinumPlus();
const upsellingMessage = useUpsellingMessage('note_management_user_filter');
Expand All @@ -30,34 +41,36 @@ export const UserFilterDropdown = React.memo(() => {
searchTerm: '',
enabled: isPlatinumPlus,
});
const users = useMemo(

const users: User[] = useMemo(
() =>
(data || []).map((userProfile: UserProfileWithAvatar) => ({
label: userProfile.user.full_name || userProfile.user.username,
id: userProfile.uid,
label: userProfile.user.full_name || userProfile.user.email || userProfile.user.username,
})),
[data]
);

const [selectedUser, setSelectedUser] = useState<Array<EuiComboBoxOptionOption<string>>>();
const [selectedUser, setSelectedUser] = useState<Array<EuiComboBoxOptionOption<User>>>();
const onChange = useCallback(
(user: Array<EuiComboBoxOptionOption<string>>) => {
(user: Array<EuiComboBoxOptionOption<User>>) => {
setSelectedUser(user);
dispatch(userFilterUsers(user.length > 0 ? user[0].label : ''));
dispatch(userFilterCreatedBy(user.length > 0 ? (user[0].id as string) : ''));
},
[dispatch]
);

const dropdown = useMemo(
() => (
<EuiComboBox
prepend={USERS_DROPDOWN}
prepend={CREATED_BY}
singleSelection={{ asPlainText: true }}
options={users}
selectedOptions={selectedUser}
onChange={onChange}
isLoading={isPlatinumPlus && isLoading}
isDisabled={!isPlatinumPlus}
data-test-subj={USER_SELECT_TEST_ID}
data-test-subj={CREATED_BY_SELECT_TEST_ID}
/>
),
[isLoading, isPlatinumPlus, onChange, selectedUser, users]
Expand All @@ -76,4 +89,4 @@ export const UserFilterDropdown = React.memo(() => {
);
});

UserFilterDropdown.displayName = 'UserFilterDropdown';
CreatedByFilterDropdown.displayName = 'CreatedByFilterDropdown';
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { SearchRow } from './search_row';
import { ASSOCIATED_NOT_SELECT_TEST_ID, SEARCH_BAR_TEST_ID, USER_SELECT_TEST_ID } from './test_ids';
import {
ASSOCIATED_NOT_SELECT_TEST_ID,
SEARCH_BAR_TEST_ID,
CREATED_BY_SELECT_TEST_ID,
} from './test_ids';
import { AssociatedFilter } from '../../../common/notes/constants';
import { useSuggestUsers } from '../../common/components/user_profiles/use_suggest_users';
import { TestProviders } from '../../common/mock';
Expand Down Expand Up @@ -43,7 +47,7 @@ describe('SearchRow', () => {
);

expect(getByTestId(SEARCH_BAR_TEST_ID)).toBeInTheDocument();
expect(getByTestId(USER_SELECT_TEST_ID)).toBeInTheDocument();
expect(getByTestId(CREATED_BY_SELECT_TEST_ID)).toBeInTheDocument();
expect(getByTestId(ASSOCIATED_NOT_SELECT_TEST_ID)).toBeInTheDocument();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from '@elastic/eui';
import { useDispatch } from 'react-redux';
import { i18n } from '@kbn/i18n';
import { UserFilterDropdown } from './user_filter_dropdown';
import { CreatedByFilterDropdown } from './created_by_filter_dropdown';
import { ASSOCIATED_NOT_SELECT_TEST_ID, SEARCH_BAR_TEST_ID } from './test_ids';
import { userFilterAssociatedNotes, userSearchedNotes } from '..';
import { AssociatedFilter } from '../../../common/notes/constants';
Expand Down Expand Up @@ -65,7 +65,7 @@ export const SearchRow = React.memo(() => {
<EuiSearchBar box={searchBox} onChange={onQueryChange} defaultQuery="" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<UserFilterDropdown />
<CreatedByFilterDropdown />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSelect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ export const TIMELINE_DESCRIPTION_COMMENT_TEST_ID = `${PREFIX}TimelineDescriptio
export const NOTE_CONTENT_BUTTON_TEST_ID = `${PREFIX}NoteContentButton` as const;
export const NOTE_CONTENT_POPOVER_TEST_ID = `${PREFIX}NoteContentPopover` as const;
export const SEARCH_BAR_TEST_ID = `${PREFIX}SearchBar` as const;
export const USER_SELECT_TEST_ID = `${PREFIX}UserSelect` as const;
export const CREATED_BY_SELECT_TEST_ID = `${PREFIX}CreatedBySelect` as const;
export const ASSOCIATED_NOT_SELECT_TEST_ID = `${PREFIX}AssociatedNoteSelect` as const;
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
selectNotesTableSelectedIds,
selectNotesTableSearch,
userSelectedBulkDelete,
selectNotesTableUserFilters,
selectNotesTableCreatedByFilter,
selectNotesTableAssociatedFilter,
} from '..';

Expand Down Expand Up @@ -53,8 +53,8 @@ export const NotesUtilityBar = React.memo(() => {
const pagination = useSelector(selectNotesPagination);
const sort = useSelector(selectNotesTableSort);
const selectedItems = useSelector(selectNotesTableSelectedIds);
const notesUserFilters = useSelector(selectNotesTableUserFilters);
const notesAssociatedFilters = useSelector(selectNotesTableAssociatedFilter);
const notesCreatedByFilter = useSelector(selectNotesTableCreatedByFilter);
const notesAssociatedFilter = useSelector(selectNotesTableAssociatedFilter);
const resultsCount = useMemo(() => {
const { perPage, page, total } = pagination;
const startOfCurrentPage = perPage * (page - 1) + 1;
Expand Down Expand Up @@ -87,8 +87,8 @@ export const NotesUtilityBar = React.memo(() => {
sortField: sort.field,
sortOrder: sort.direction,
filter: '',
userFilter: notesUserFilters,
associatedFilter: notesAssociatedFilters,
createdByFilter: notesCreatedByFilter,
associatedFilter: notesAssociatedFilter,
search: notesSearch,
})
);
Expand All @@ -98,8 +98,8 @@ export const NotesUtilityBar = React.memo(() => {
pagination.perPage,
sort.field,
sort.direction,
notesUserFilters,
notesAssociatedFilters,
notesCreatedByFilter,
notesAssociatedFilter,
notesSearch,
]);
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
selectNotesTablePendingDeleteIds,
selectFetchNotesError,
ReqStatus,
selectNotesTableUserFilters,
selectNotesTableCreatedByFilter,
selectNotesTableAssociatedFilter,
} from '..';
import type { NotesState } from '..';
Expand Down Expand Up @@ -121,8 +121,8 @@ export const NoteManagementPage = () => {
const pagination = useSelector(selectNotesPagination);
const sort = useSelector(selectNotesTableSort);
const notesSearch = useSelector(selectNotesTableSearch);
const notesUserFilters = useSelector(selectNotesTableUserFilters);
const notesAssociatedFilters = useSelector(selectNotesTableAssociatedFilter);
const notesCreatedByFilter = useSelector(selectNotesTableCreatedByFilter);
const notesAssociatedFilter = useSelector(selectNotesTableAssociatedFilter);
const pendingDeleteIds = useSelector(selectNotesTablePendingDeleteIds);
const isDeleteModalVisible = pendingDeleteIds.length > 0;
const fetchNotesStatus = useSelector(selectFetchNotesStatus);
Expand All @@ -138,8 +138,8 @@ export const NoteManagementPage = () => {
sortField: sort.field,
sortOrder: sort.direction,
filter: '',
userFilter: notesUserFilters,
associatedFilter: notesAssociatedFilters,
createdByFilter: notesCreatedByFilter,
associatedFilter: notesAssociatedFilter,
search: notesSearch,
})
);
Expand All @@ -149,8 +149,8 @@ export const NoteManagementPage = () => {
pagination.perPage,
sort.field,
sort.direction,
notesUserFilters,
notesAssociatedFilters,
notesCreatedByFilter,
notesAssociatedFilter,
notesSearch,
]);

Expand Down
Loading

0 comments on commit 68b0489

Please sign in to comment.