From c7b5b35b199611741993e124811352472e8323f2 Mon Sep 17 00:00:00 2001 From: Philippe Oberti Date: Tue, 26 Nov 2024 08:36:47 -0600 Subject: [PATCH] [Security Solution][Notes] - disable add note button in flyout is user lacks privileges (#201707) ## Summary This PR fixes a small issue where users could click on the add button in the alert details flyout even if they did not have the correct privileges. When the user has the correct privileges, the UI does not change. In the flyout: - if no notes have previously been created for the document, we show a `Add note` button - if some notes have previously been created for the document, we show the number of notes and a plus button icon https://github.com/user-attachments/assets/d9a27b70-99b1-4562-8224-4f5c2f25b001 When the user does not have the correct privileges, the flyout UI now shows the following: - if no notes have previously been created for the document, we show a `-` - if one or more notes have been created for the document, we show the number of notes followed by a `View note(s)` button, that - when clicked - opens the left panel for the user to view the notes https://github.com/user-attachments/assets/8ebe8bf5-16ab-4652-b4d3-47507c2d3673 https://github.com/elastic/kibana/issues/201702 ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../right/components/notes.test.tsx | 71 +++++++++++++++ .../right/components/notes.tsx | 88 ++++++++++++++----- .../right/components/test_ids.ts | 2 + 3 files changed, 140 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.test.tsx index 023b0202ecb63..7ccc3477a9d5d 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.test.tsx @@ -14,6 +14,7 @@ import { NOTES_COUNT_TEST_ID, NOTES_LOADING_TEST_ID, NOTES_TITLE_TEST_ID, + NOTES_VIEW_NOTES_BUTTON_TEST_ID, } from './test_ids'; import { FETCH_NOTES_ERROR, Notes } from './notes'; import { mockContextValue } from '../../shared/mocks/mock_context'; @@ -24,8 +25,10 @@ import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { DocumentDetailsLeftPanelKey } from '../../shared/constants/panel_keys'; import { LeftPanelNotesTab } from '../../left'; import { getEmptyValue } from '../../../../common/components/empty_value'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; jest.mock('@kbn/expandable-flyout'); +jest.mock('../../../../common/components/user_privileges'); const mockAddError = jest.fn(); jest.mock('../../../../common/hooks/use_app_toasts', () => ({ @@ -45,6 +48,9 @@ jest.mock('react-redux', () => { describe('', () => { beforeEach(() => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + kibanaSecuritySolutionsPrivileges: { crud: true }, + }); jest.clearAllMocks(); }); @@ -297,4 +303,69 @@ describe('', () => { title: FETCH_NOTES_ERROR, }); }); + + it('should show View note button if user does not have the correct privileges but notes have already been created', () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + kibanaSecuritySolutionsPrivileges: { crud: false }, + }); + + const mockOpenLeftPanel = jest.fn(); + (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel }); + + const contextValue = { + ...mockContextValue, + eventId: '1', + }; + + const { getByTestId, queryByTestId } = render( + + + + + + ); + + expect(mockDispatch).toHaveBeenCalled(); + + expect(getByTestId(NOTES_COUNT_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(NOTES_COUNT_TEST_ID)).toHaveTextContent('1'); + + expect(queryByTestId(NOTES_ADD_NOTE_ICON_BUTTON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(NOTES_ADD_NOTE_BUTTON_TEST_ID)).not.toBeInTheDocument(); + const button = getByTestId(NOTES_VIEW_NOTES_BUTTON_TEST_ID); + expect(button).toBeInTheDocument(); + + button.click(); + + expect(mockOpenLeftPanel).toHaveBeenCalledWith({ + id: DocumentDetailsLeftPanelKey, + path: { tab: LeftPanelNotesTab }, + params: { + id: contextValue.eventId, + indexName: mockContextValue.indexName, + scopeId: mockContextValue.scopeId, + }, + }); + }); + + it('should show a - if user does not have the correct privileges and no notes have been created', () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + kibanaSecuritySolutionsPrivileges: { crud: false }, + }); + + const { getByText, queryByTestId } = render( + + + + + + ); + + expect(mockDispatch).toHaveBeenCalled(); + + expect(queryByTestId(NOTES_ADD_NOTE_ICON_BUTTON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(NOTES_ADD_NOTE_BUTTON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(NOTES_COUNT_TEST_ID)).not.toBeInTheDocument(); + expect(getByText(getEmptyValue())).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.tsx index b0e2008c04103..e10a1ff23919f 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useCallback, useEffect } from 'react'; +import React, { memo, useCallback, useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -19,6 +19,7 @@ import { } from '@elastic/eui'; import { css } from '@emotion/react'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { DocumentDetailsLeftPanelKey } from '../../shared/constants/panel_keys'; import { FormattedCount } from '../../../../common/components/formatted_number'; @@ -29,6 +30,7 @@ import { NOTES_COUNT_TEST_ID, NOTES_LOADING_TEST_ID, NOTES_TITLE_TEST_ID, + NOTES_VIEW_NOTES_BUTTON_TEST_ID, } from './test_ids'; import type { State } from '../../../../common/store'; import type { Note } from '../../../../../common/api/timeline'; @@ -55,6 +57,12 @@ export const ADD_NOTE_BUTTON = i18n.translate( defaultMessage: 'Add note', } ); +export const VIEW_NOTES_BUTTON_ARIA_LABEL = i18n.translate( + 'xpack.securitySolution.flyout.right.notes.viewNoteButtonAriaLabel', + { + defaultMessage: 'View notes', + } +); /** * Renders a block with the number of notes for the event @@ -64,6 +72,7 @@ export const Notes = memo(() => { const dispatch = useDispatch(); const { eventId, indexName, scopeId, isPreview, isPreviewMode } = useDocumentDetailsContext(); const { addError: addErrorToast } = useAppToasts(); + const { kibanaSecuritySolutionsPrivileges } = useUserPrivileges(); const { openLeftPanel } = useExpandableFlyoutApi(); const openExpandedFlyoutNotesTab = useCallback( @@ -101,6 +110,61 @@ export const Notes = memo(() => { } }, [addErrorToast, fetchError, fetchStatus]); + const viewNotesButton = useMemo( + () => ( + + + + ), + [isPreview, isPreviewMode, notes.length, openExpandedFlyoutNotesTab] + ); + const addNoteButton = useMemo( + () => ( + + {ADD_NOTE_BUTTON} + + ), + [isPreview, isPreviewMode, openExpandedFlyoutNotesTab] + ); + const addNoteButtonIcon = useMemo( + () => ( + + ), + [ + euiTheme.size.xs, + isPreview, + isPreviewMode, + kibanaSecuritySolutionsPrivileges.crud, + openExpandedFlyoutNotesTab, + ] + ); + return ( { ) : ( <> {notes.length === 0 ? ( - - {ADD_NOTE_BUTTON} - + <>{kibanaSecuritySolutionsPrivileges.crud ? addNoteButton : getEmptyTagValue()} ) : ( - + {kibanaSecuritySolutionsPrivileges.crud ? addNoteButtonIcon : viewNotesButton} )} diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts index 959f8f106bb08..78afeb19e0b80 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts @@ -35,6 +35,8 @@ export const CHAT_BUTTON_TEST_ID = 'newChatByTitle' as const; export const NOTES_TITLE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}NotesTitle` as const; export const NOTES_ADD_NOTE_BUTTON_TEST_ID = `${FLYOUT_HEADER_TEST_ID}NotesAddNoteButton` as const; +export const NOTES_VIEW_NOTES_BUTTON_TEST_ID = + `${FLYOUT_HEADER_TEST_ID}NotesViewNotesButton` as const; export const NOTES_ADD_NOTE_ICON_BUTTON_TEST_ID = `${FLYOUT_HEADER_TEST_ID}NotesAddNoteIconButton` as const; export const NOTES_COUNT_TEST_ID = `${FLYOUT_HEADER_TEST_ID}NotesCount` as const;