Skip to content

Commit

Permalink
[Security Solution][Notes] - allow filtering by note association (#19…
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilippeOberti authored Oct 16, 2024
1 parent 002b757 commit 66708b2
Show file tree
Hide file tree
Showing 20 changed files with 409 additions and 40 deletions.
12 changes: 12 additions & 0 deletions oas_docs/output/kibana.serverless.staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35266,6 +35266,10 @@ paths:
schema:
nullable: true
type: string
- in: query
name: associatedFilter
schema:
$ref: '#/components/schemas/Security_Timeline_API_AssociatedFilterType'
responses:
'200':
content:
Expand Down Expand Up @@ -49419,6 +49423,14 @@ components:
Security_Osquery_API_VersionOrUndefined:
$ref: '#/components/schemas/Security_Osquery_API_Version'
nullable: true
Security_Timeline_API_AssociatedFilterType:
description: Filter notes based on their association with a document or saved object.
enum:
- document_only
- saved_object_only
- document_and_saved_object
- orphan
type: string
Security_Timeline_API_BareNote:
type: object
properties:
Expand Down
12 changes: 12 additions & 0 deletions oas_docs/output/kibana.serverless.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35266,6 +35266,10 @@ paths:
schema:
nullable: true
type: string
- in: query
name: associatedFilter
schema:
$ref: '#/components/schemas/Security_Timeline_API_AssociatedFilterType'
responses:
'200':
content:
Expand Down Expand Up @@ -49419,6 +49423,14 @@ components:
Security_Osquery_API_VersionOrUndefined:
$ref: '#/components/schemas/Security_Osquery_API_Version'
nullable: true
Security_Timeline_API_AssociatedFilterType:
description: Filter notes based on their association with a document or saved object.
enum:
- document_only
- saved_object_only
- document_and_saved_object
- orphan
type: string
Security_Timeline_API_BareNote:
type: object
properties:
Expand Down
12 changes: 12 additions & 0 deletions oas_docs/output/kibana.staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38697,6 +38697,10 @@ paths:
schema:
nullable: true
type: string
- in: query
name: associatedFilter
schema:
$ref: '#/components/schemas/Security_Timeline_API_AssociatedFilterType'
responses:
'200':
content:
Expand Down Expand Up @@ -58185,6 +58189,14 @@ components:
Security_Osquery_API_VersionOrUndefined:
$ref: '#/components/schemas/Security_Osquery_API_Version'
nullable: true
Security_Timeline_API_AssociatedFilterType:
description: Filter notes based on their association with a document or saved object.
enum:
- document_only
- saved_object_only
- document_and_saved_object
- orphan
type: string
Security_Timeline_API_BareNote:
type: object
properties:
Expand Down
12 changes: 12 additions & 0 deletions oas_docs/output/kibana.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38697,6 +38697,10 @@ paths:
schema:
nullable: true
type: string
- in: query
name: associatedFilter
schema:
$ref: '#/components/schemas/Security_Timeline_API_AssociatedFilterType'
responses:
'200':
content:
Expand Down Expand Up @@ -58185,6 +58189,14 @@ components:
Security_Osquery_API_VersionOrUndefined:
$ref: '#/components/schemas/Security_Osquery_API_Version'
nullable: true
Security_Timeline_API_AssociatedFilterType:
description: Filter notes based on their association with a document or saved object.
enum:
- document_only
- saved_object_only
- document_and_saved_object
- orphan
type: string
Security_Timeline_API_BareNote:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ import { z } from '@kbn/zod';

import { Note } from '../model/components.gen';

/**
* Filter notes based on their association with a document or saved object.
*/
export type AssociatedFilterType = z.infer<typeof AssociatedFilterType>;
export const AssociatedFilterType = z.enum([
'document_only',
'saved_object_only',
'document_and_saved_object',
'orphan',
]);
export type AssociatedFilterTypeEnum = typeof AssociatedFilterType.enum;
export const AssociatedFilterTypeEnum = AssociatedFilterType.enum;

export type DocumentIds = z.infer<typeof DocumentIds>;
export const DocumentIds = z.union([z.array(z.string()), z.string()]);

Expand All @@ -41,6 +54,7 @@ export const GetNotesRequestQuery = z.object({
sortOrder: z.string().nullable().optional(),
filter: z.string().nullable().optional(),
userFilter: 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 @@ -56,6 +56,10 @@ paths:
schema:
nullable: true
type: string
- name: associatedFilter
in: query
schema:
$ref: '#/components/schemas/AssociatedFilterType'
responses:
'200':
description: Indicates the requested notes were returned.
Expand All @@ -68,6 +72,14 @@ paths:

components:
schemas:
AssociatedFilterType:
type: string
enum:
- document_only
- saved_object_only
- document_and_saved_object
- orphan
description: Filter notes based on their association with a document or saved object.
DocumentIds:
oneOf:
- type: array
Expand Down
14 changes: 14 additions & 0 deletions x-pack/plugins/security_solution/common/notes/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 enum AssociatedFilter {
all = 'all',
documentOnly = 'document_only',
savedObjectOnly = 'saved_object_only',
documentAndSavedObject = 'document_and_saved_object',
orphan = 'orphan',
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ paths:
schema:
nullable: true
type: string
- in: query
name: associatedFilter
schema:
$ref: '#/components/schemas/AssociatedFilterType'
responses:
'200':
content:
Expand Down Expand Up @@ -921,6 +925,14 @@ paths:
- access:securitySolution
components:
schemas:
AssociatedFilterType:
description: Filter notes based on their association with a document or saved object.
enum:
- document_only
- saved_object_only
- document_and_saved_object
- orphan
type: string
BareNote:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ paths:
schema:
nullable: true
type: string
- in: query
name: associatedFilter
schema:
$ref: '#/components/schemas/AssociatedFilterType'
responses:
'200':
content:
Expand Down Expand Up @@ -921,6 +925,14 @@ paths:
- access:securitySolution
components:
schemas:
AssociatedFilterType:
description: Filter notes based on their association with a document or saved object.
enum:
- document_only
- saved_object_only
- document_and_saved_object
- orphan
type: string
BareNote:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { TableId } from '@kbn/securitysolution-data-table';
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
import { AssociatedFilter } from '../../../common/notes/constants';
import { ReqStatus } from '../../notes/store/notes.slice';
import { HostsFields } from '../../../common/api/search_strategy/hosts/model/sort';
import { InputsModelId } from '../store/inputs/constants';
Expand Down Expand Up @@ -550,6 +551,7 @@ export const mockGlobalState: State = {
},
filter: '',
userFilter: '',
associatedFilter: AssociatedFilter.all,
search: '',
selectedIds: [],
pendingDeleteIds: [],
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/security_solution/public/notes/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
GetNotesResponse,
PersistNoteRouteResponse,
} from '../../../common/api/timeline';
import type { AssociatedFilter } from '../../../common/notes/constants';
import { KibanaServices } from '../../common/lib/kibana';
import { NOTE_URL } from '../../../common/constants';

Expand Down Expand Up @@ -43,6 +44,7 @@ export const fetchNotes = async ({
sortOrder,
filter,
userFilter,
associatedFilter,
search,
}: {
page: number;
Expand All @@ -51,6 +53,7 @@ export const fetchNotes = async ({
sortOrder: string;
filter: string;
userFilter: string;
associatedFilter: AssociatedFilter;
search: string;
}) => {
const response = await KibanaServices.get().http.get<GetNotesResponse>(NOTE_URL, {
Expand All @@ -61,6 +64,7 @@ export const fetchNotes = async ({
sortOrder,
filter,
userFilter,
associatedFilter,
search,
},
version: '2023-10-31',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { SearchRow } from './search_row';
import { SEARCH_BAR_TEST_ID, USER_SELECT_TEST_ID } from './test_ids';
import { ASSOCIATED_NOT_SELECT_TEST_ID, SEARCH_BAR_TEST_ID, USER_SELECT_TEST_ID } from './test_ids';
import { AssociatedFilter } from '../../../common/notes/constants';
import { useSuggestUsers } from '../../common/components/user_profiles/use_suggest_users';

jest.mock('../../common/components/user_profiles/use_suggest_users');
Expand Down Expand Up @@ -38,6 +39,7 @@ describe('SearchRow', () => {

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

it('should call the correct action when entering a value in the search bar', async () => {
Expand All @@ -62,4 +64,13 @@ describe('SearchRow', () => {

expect(mockDispatch).toHaveBeenCalled();
});

it('should call the correct action when select a value in the associated note dropdown', async () => {
const { getByTestId } = render(<SearchRow />);

const associatedNoteSelect = getByTestId(ASSOCIATED_NOT_SELECT_TEST_ID);
await userEvent.selectOptions(associatedNoteSelect, [AssociatedFilter.documentOnly]);

expect(mockDispatch).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,48 @@
* 2.0.
*/

import { EuiComboBox, EuiFlexGroup, EuiFlexItem, EuiSearchBar } from '@elastic/eui';
import React, { useMemo, useCallback, useState } from 'react';
import type { EuiSelectOption } from '@elastic/eui';
import {
EuiComboBox,
EuiFlexGroup,
EuiFlexItem,
EuiSearchBar,
EuiSelect,
useGeneratedHtmlId,
} from '@elastic/eui';
import { useDispatch } from 'react-redux';
import type { UserProfileWithAvatar } from '@kbn/user-profile-components';
import { i18n } from '@kbn/i18n';
import type { EuiComboBoxOptionOption } from '@elastic/eui/src/components/combo_box/types';
import { SEARCH_BAR_TEST_ID, USER_SELECT_TEST_ID } from './test_ids';
import { ASSOCIATED_NOT_SELECT_TEST_ID, SEARCH_BAR_TEST_ID, USER_SELECT_TEST_ID } from './test_ids';
import { useSuggestUsers } from '../../common/components/user_profiles/use_suggest_users';
import { userFilterUsers, userSearchedNotes } from '..';
import { userFilterAssociatedNotes, userFilterUsers, userSearchedNotes } from '..';
import { AssociatedFilter } from '../../../common/notes/constants';

export const USERS_DROPDOWN = i18n.translate('xpack.securitySolution.notes.usersDropdownLabel', {
defaultMessage: 'Users',
});
const FILTER_SELECT = i18n.translate('xpack.securitySolution.notes.management.filterSelect', {
defaultMessage: 'Select filter',
});

const searchBox = {
placeholder: 'Search note contents',
incremental: false,
'data-test-subj': SEARCH_BAR_TEST_ID,
};
const associatedNoteSelectOptions: EuiSelectOption[] = [
{ value: AssociatedFilter.all, text: 'All' },
{ value: AssociatedFilter.documentOnly, text: 'Attached to document only' },
{ value: AssociatedFilter.savedObjectOnly, text: 'Attached to timeline only' },
{ value: AssociatedFilter.documentAndSavedObject, text: 'Attached to document and timeline' },
{ value: AssociatedFilter.orphan, text: 'Orphan' },
];

export const SearchRow = React.memo(() => {
const dispatch = useDispatch();
const searchBox = useMemo(
() => ({
placeholder: 'Search note contents',
incremental: false,
'data-test-subj': SEARCH_BAR_TEST_ID,
}),
[]
);
const associatedSelectId = useGeneratedHtmlId({ prefix: 'associatedSelectId' });

const onQueryChange = useCallback(
({ queryText }: { queryText: string }) => {
Expand Down Expand Up @@ -57,6 +75,13 @@ export const SearchRow = React.memo(() => {
[dispatch]
);

const onAssociatedNoteSelectChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
dispatch(userFilterAssociatedNotes(e.target.value as AssociatedFilter));
},
[dispatch]
);

return (
<EuiFlexGroup gutterSize="m">
<EuiFlexItem>
Expand All @@ -73,6 +98,16 @@ export const SearchRow = React.memo(() => {
data-test-subj={USER_SELECT_TEST_ID}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSelect
id={associatedSelectId}
options={associatedNoteSelectOptions}
onChange={onAssociatedNoteSelectChange}
prepend={FILTER_SELECT}
aria-label={FILTER_SELECT}
data-test-subj={ASSOCIATED_NOT_SELECT_TEST_ID}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
});
Expand Down
Loading

0 comments on commit 66708b2

Please sign in to comment.