From 53acbab23b29e8aac7d7fdb0dfebac690640339c Mon Sep 17 00:00:00 2001 From: Philippe Oberti Date: Wed, 6 Nov 2024 15:14:14 -0600 Subject: [PATCH] [Security Solution][Notes] - disable note buttons in the right panel header when in preview mode (#199189) ## Summary This PR fixes a issue where the `Add note` button and the `+` icon button are clickable when an alert is viewed in preview mode. Users should not be able to perform actions here, as the action expands the flyouts and opens the left panel Notes tab, but the issue is the left panel now shows a different alert from the right panel. If the user closes the preview panel, they now see a different alerts on the left and right panels but they have no way to know this. #### Add note button disabled https://github.com/user-attachments/assets/20554b60-39a1-4c6d-b215-e502b5b24dbd #### + button disabled https://github.com/user-attachments/assets/df540aed-b583-457d-a9f4-0093a171ddaa Also adding notes should be disabled when in the rule creation page, as we do not want to generate notes for alerts that actually do not exist yet. To be consistent with the other blocks in the flyout header, we show a `-`. https://github.com/user-attachments/assets/b62ecf85-ee0f-4bee-853c-ff1034b5bf25 --- .../right/components/notes.test.tsx | 89 +++++++++++++++++++ .../right/components/notes.tsx | 70 +++++++++------ 2 files changed, 130 insertions(+), 29 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 f70cd2aae3e8d..023b0202ecb63 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 @@ -23,6 +23,7 @@ import type { Note } from '../../../../../common/api/timeline'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { DocumentDetailsLeftPanelKey } from '../../shared/constants/panel_keys'; import { LeftPanelNotesTab } from '../../left'; +import { getEmptyValue } from '../../../../common/components/empty_value'; jest.mock('@kbn/expandable-flyout'); @@ -43,6 +44,10 @@ jest.mock('react-redux', () => { }); describe('', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should render loading spinner', () => { (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: jest.fn() }); @@ -99,6 +104,34 @@ describe('', () => { }); }); + it('should disabled the Add note button if in preview mode', () => { + const contextValue = { + ...mockContextValue, + isPreviewMode: true, + }; + + const mockOpenLeftPanel = jest.fn(); + (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel }); + + const { getByTestId } = render( + + + + + + ); + + expect(mockDispatch).not.toHaveBeenCalled(); + + const button = getByTestId(NOTES_ADD_NOTE_BUTTON_TEST_ID); + expect(button).toBeInTheDocument(); + expect(button).toBeDisabled(); + + button.click(); + + expect(mockOpenLeftPanel).not.toHaveBeenCalled(); + }); + it('should render number of notes and plus button', () => { const mockOpenLeftPanel = jest.fn(); (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel }); @@ -135,6 +168,38 @@ describe('', () => { }); }); + it('should disable the plus button if in preview mode', () => { + const mockOpenLeftPanel = jest.fn(); + (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel }); + + const contextValue = { + ...mockContextValue, + eventId: '1', + isPreviewMode: true, + }; + + const { getByTestId } = render( + + + + + + ); + + expect(getByTestId(NOTES_COUNT_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(NOTES_COUNT_TEST_ID)).toHaveTextContent('1'); + + expect(mockDispatch).not.toHaveBeenCalled(); + + const button = getByTestId(NOTES_ADD_NOTE_ICON_BUTTON_TEST_ID); + + expect(button).toBeInTheDocument(); + button.click(); + expect(button).toBeDisabled(); + + expect(mockOpenLeftPanel).not.toHaveBeenCalled(); + }); + it('should render number of notes in scientific notation for big numbers', () => { const createMockNote = (noteId: string): Note => ({ eventId: '1', // should be a valid id based on mockTimelineData @@ -180,6 +245,30 @@ describe('', () => { expect(getByTestId(NOTES_COUNT_TEST_ID)).toHaveTextContent('1k'); }); + it('should show a - when in rule creation workflow', () => { + const contextValue = { + ...mockContextValue, + isPreview: true, + }; + + (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: jest.fn() }); + + const { getByText, queryByTestId } = render( + + + + + + ); + + expect(mockDispatch).not.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(); + }); + it('should render toast error', () => { const store = createMockStore({ ...mockGlobalState, 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 a981a16117a68..b0e2008c04103 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 @@ -19,6 +19,7 @@ import { } from '@elastic/eui'; import { css } from '@emotion/react'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { DocumentDetailsLeftPanelKey } from '../../shared/constants/panel_keys'; import { FormattedCount } from '../../../../common/components/formatted_number'; import { useDocumentDetailsContext } from '../../shared/context'; @@ -61,7 +62,7 @@ export const ADD_NOTE_BUTTON = i18n.translate( export const Notes = memo(() => { const { euiTheme } = useEuiTheme(); const dispatch = useDispatch(); - const { eventId, indexName, scopeId } = useDocumentDetailsContext(); + const { eventId, indexName, scopeId, isPreview, isPreviewMode } = useDocumentDetailsContext(); const { addError: addErrorToast } = useAppToasts(); const { openLeftPanel } = useExpandableFlyoutApi(); @@ -80,8 +81,11 @@ export const Notes = memo(() => { ); useEffect(() => { - dispatch(fetchNotesByDocumentIds({ documentIds: [eventId] })); - }, [dispatch, eventId]); + // only fetch notes if we are not in a preview panel, or not in a rule preview workflow + if (!isPreviewMode && !isPreview) { + dispatch(fetchNotesByDocumentIds({ documentIds: [eventId] })); + } + }, [dispatch, eventId, isPreview, isPreviewMode]); const fetchStatus = useSelector((state: State) => selectFetchNotesByDocumentIdsStatus(state)); const fetchError = useSelector((state: State) => selectFetchNotesByDocumentIdsError(state)); @@ -107,37 +111,45 @@ export const Notes = memo(() => { } data-test-subj={NOTES_TITLE_TEST_ID} > - {fetchStatus === ReqStatus.Loading ? ( - + {isPreview ? ( + getEmptyTagValue() ) : ( <> - {notes.length === 0 ? ( - - {ADD_NOTE_BUTTON} - + {fetchStatus === ReqStatus.Loading ? ( + ) : ( - - - - - - + {notes.length === 0 ? ( + - - + data-test-subj={NOTES_ADD_NOTE_BUTTON_TEST_ID} + > + {ADD_NOTE_BUTTON} + + ) : ( + + + + + + + + + )} + )} )}