From e8ad8cb6169e0c3c16941ad29494d94294396d51 Mon Sep 17 00:00:00 2001 From: christineweng <18648970+christineweng@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:14:43 -0600 Subject: [PATCH] [Security Solution] Alert Flyout - update header summary panel (#175075) ## Summary Follow up PR of https://github.com/elastic/kibana/pull/170279 to update the flyout header with the new UI design. #### Before ![image](https://github.com/elastic/kibana/assets/18648970/248358ac-75a0-49d8-8b5b-a2515aa24409) #### After - Opening from alerts table shows updated design ![image](https://github.com/elastic/kibana/assets/18648970/ec32ce95-22f3-44d6-82fa-8cc204d88510) - Opening from events table does not have the summary panel (as it is specific to alerts) ![image](https://github.com/elastic/kibana/assets/18648970/5dcb95b6-d833-4b77-949b-0c3f717fcd5e) - Opening from alert preview shows status and assignees as empty tags, as they are not applicable in preview ![image](https://github.com/elastic/kibana/assets/18648970/97b58012-41a6-4101-a839-faac5f3c7b42) ### Checklist Delete any items that are not applicable to this PR. - [x] 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) - [x] [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/assignees.test.tsx | 23 +++++- .../right/components/assignees.tsx | 72 +++++++++++-------- .../right/components/header_title.test.tsx | 22 +++--- .../right/components/header_title.tsx | 67 +++++++++++------ .../right/components/risk_score.tsx | 4 +- .../right/components/status.test.tsx | 18 ++++- .../right/components/status.tsx | 65 +++++++++++------ .../right/components/test_ids.ts | 2 + .../alert_details_right_panel.cy.ts | 5 +- 9 files changed, 185 insertions(+), 93 deletions(-) diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.test.tsx index 0753c6f613cb81..161fc4db117de4 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.test.tsx @@ -8,7 +8,11 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { ASSIGNEES_ADD_BUTTON_TEST_ID, ASSIGNEES_TITLE_TEST_ID } from './test_ids'; +import { + ASSIGNEES_ADD_BUTTON_TEST_ID, + ASSIGNEES_TITLE_TEST_ID, + ASSIGNEES_HEADER_TEST_ID, +} from './test_ids'; import { Assignees } from './assignees'; import { useGetCurrentUserProfile } from '../../../../common/components/user_profiles/use_get_current_user_profile'; @@ -44,7 +48,8 @@ const mockUserProfiles = [ const renderAssignees = ( eventId = 'event-1', alertAssignees = ['user-id-1'], - onAssigneesUpdated = jest.fn() + onAssigneesUpdated = jest.fn(), + isPreview = false ) => { const assignedProfiles = mockUserProfiles.filter((user) => alertAssignees.includes(user.uid)); (useBulkGetUserProfiles as jest.Mock).mockReturnValue({ @@ -57,6 +62,7 @@ const renderAssignees = ( eventId={eventId} assignedUserIds={alertAssignees} onAssigneesUpdated={onAssigneesUpdated} + isPreview={isPreview} /> ); @@ -163,4 +169,17 @@ describe('', () => { expect(getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).toBeInTheDocument(); expect(getByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).toBeDisabled(); }); + + it('should render empty tag in preview mode', () => { + const assignees = ['user-id-1', 'user-id-2']; + const { getByTestId, queryByTestId } = renderAssignees( + 'test-event', + assignees, + jest.fn(), + true + ); + + expect(queryByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).not.toBeInTheDocument(); + expect(getByTestId(ASSIGNEES_HEADER_TEST_ID)).toHaveTextContent('Assignees—'); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.tsx index 7544388a62f966..39429c96b0534a 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/assignees.tsx @@ -12,7 +12,7 @@ import React, { memo, useCallback, useMemo, useState } from 'react'; import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; - +import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { useUpsellingMessage } from '../../../../common/hooks/use_upselling'; import { useLicense } from '../../../../common/hooks/use_license'; import { useAlertsPrivileges } from '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges'; @@ -60,13 +60,18 @@ export interface AssigneesProps { * Callback to handle the successful assignees update */ onAssigneesUpdated?: () => void; + + /** + * Boolean to indicate whether it is a preview flyout + */ + isPreview?: boolean; } /** * Document assignees details displayed in flyout right section header */ export const Assignees: FC = memo( - ({ eventId, assignedUserIds, onAssigneesUpdated }) => { + ({ eventId, assignedUserIds, onAssigneesUpdated, isPreview }) => { const isPlatinumPlus = useLicense().isPlatinumPlus(); const upsellingMessage = useUpsellingMessage('alert_assignments'); @@ -108,9 +113,8 @@ export const Assignees: FC = memo( return ( @@ -118,39 +122,45 @@ export const Assignees: FC = memo(

- {assignedUsers && ( - - - - )} - - + {assignedUsers && ( + + + + )} + + } + isPopoverOpen={isPopoverOpen} + closePopover={togglePopover} + onAssigneesApply={onAssigneesApply} /> - } - isPopoverOpen={isPopoverOpen} - closePopover={togglePopover} - onAssigneesApply={onAssigneesApply} - /> - + + + )}
); } diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.test.tsx index 75ddf6ed6dec48..95af8ecb3351be 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.test.tsx @@ -13,6 +13,8 @@ import { SEVERITY_VALUE_TEST_ID, FLYOUT_HEADER_TITLE_TEST_ID, STATUS_BUTTON_TEST_ID, + ASSIGNEES_HEADER_TEST_ID, + ALERT_SUMMARY_PANEL_TEST_ID, } from './test_ids'; import { HeaderTitle } from './header_title'; import moment from 'moment-timezone'; @@ -51,9 +53,12 @@ describe('', () => { const { getByTestId } = renderHeader(mockContextValue); expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(RISK_SCORE_VALUE_TEST_ID)).toBeInTheDocument(); expect(getByTestId(SEVERITY_VALUE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(ALERT_SUMMARY_PANEL_TEST_ID)).toBeInTheDocument(); + + expect(getByTestId(RISK_SCORE_VALUE_TEST_ID)).toBeInTheDocument(); expect(getByTestId(STATUS_BUTTON_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(ASSIGNEES_HEADER_TEST_ID)).toBeInTheDocument(); }); it('should render rule name in the title if document is an alert', () => { @@ -81,7 +86,7 @@ describe('', () => { expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent('Event details'); }); - it('should not render document status if document is not an alert', () => { + it('should not render alert summary kpis if document is not an alert', () => { const contextValue = { ...mockContextValue, dataFormattedForFieldBrowser: [ @@ -96,16 +101,9 @@ describe('', () => { } as unknown as RightPanelContext; const { queryByTestId } = renderHeader(contextValue); + expect(queryByTestId(ALERT_SUMMARY_PANEL_TEST_ID)).not.toBeInTheDocument(); expect(queryByTestId(STATUS_BUTTON_TEST_ID)).not.toBeInTheDocument(); - }); - - it('should not render document status if flyout is open in preview', () => { - const contextValue = { - ...mockContextValue, - isPreview: true, - } as unknown as RightPanelContext; - - const { queryByTestId } = renderHeader(contextValue); - expect(queryByTestId(STATUS_BUTTON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(RISK_SCORE_VALUE_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(ASSIGNEES_HEADER_TEST_ID)).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.tsx index 0d8f581626736c..d4bbeb42d2e2fc 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/header_title.tsx @@ -7,9 +7,17 @@ import type { FC } from 'react'; import React, { memo, useCallback, useMemo } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiPanel, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; import { isEmpty } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/css'; import { ALERT_WORKFLOW_ASSIGNEE_IDS } from '@kbn/rule-data-utils'; import { DocumentStatus } from './status'; import { DocumentSeverity } from './severity'; @@ -20,7 +28,7 @@ import { useRightPanelContext } from '../context'; import { PreferenceFormattedDate } from '../../../../common/components/formatted_date'; import { RenderRuleName } from '../../../../timelines/components/timeline/body/renderers/formatted_field_helpers'; import { SIGNAL_RULE_NAME_FIELD_NAME } from '../../../../timelines/components/timeline/body/renderers/constants'; -import { FLYOUT_HEADER_TITLE_TEST_ID } from './test_ids'; +import { FLYOUT_HEADER_TITLE_TEST_ID, ALERT_SUMMARY_PANEL_TEST_ID } from './test_ids'; import { Assignees } from './assignees'; import { FlyoutTitle } from '../../../shared/components/flyout_title'; @@ -39,6 +47,7 @@ export const HeaderTitle: FC = memo(() => { const { isAlert, ruleName, timestamp, ruleId } = useBasicDataFromDetailsData( dataFormattedForFieldBrowser ); + const { euiTheme } = useEuiTheme(); const ruleTitle = useMemo( () => @@ -102,25 +111,41 @@ export const HeaderTitle: FC = memo(() => { {isAlert && !isEmpty(ruleName) ? ruleTitle : eventTitle} - - {isAlert && !isPreview && ( - - - - )} - - - - {isAlert && !isPreview && ( - - - - )} - + {isAlert && ( + + + + + + + + + + + + + + )} ); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/risk_score.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/risk_score.tsx index a603cd9563d288..6266b349f40782 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/risk_score.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/risk_score.tsx @@ -34,13 +34,13 @@ export const RiskScore: FC = memo(() => { } return ( - +

diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/status.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/status.test.tsx index 82ebab4f962de2..2d11e3dc24f487 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/status.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/status.test.tsx @@ -56,7 +56,7 @@ describe('', () => { expect(getByTestId('data-test-subj')).toBeInTheDocument(); }); - it('should render empty component', () => { + it('should render empty tag when status is not available', () => { const contextValue = { eventId: 'eventId', browserFields: {}, @@ -66,6 +66,20 @@ describe('', () => { const { container } = renderStatus(contextValue); - expect(container).toBeEmptyDOMElement(); + expect(container).toHaveTextContent('Status—'); + }); + + it('should render empty tag in preview mode', () => { + const contextValue = { + eventId: 'eventId', + browserFields: {}, + dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, + scopeId: 'scopeId', + isPreview: true, + } as unknown as RightPanelContext; + + const { container } = renderStatus(contextValue); + + expect(container).toHaveTextContent('Status—'); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/status.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/status.tsx index cc1f7bc02f17dc..553dfd5834a0e9 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/status.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/status.tsx @@ -8,10 +8,13 @@ import type { FC } from 'react'; import React, { useMemo } from 'react'; import { find } from 'lodash/fp'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { CellActionsMode } from '@kbn/cell-actions'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { getSourcererScopeId } from '../../../../helpers'; import { SecurityCellActions } from '../../../../common/components/cell_actions'; +import { getEmptyTagValue } from '../../../../common/components/empty_value'; import type { EnrichedFieldInfo, EnrichedFieldInfoWithValues, @@ -21,6 +24,7 @@ import { StatusPopoverButton } from '../../../../common/components/event_details import { useRightPanelContext } from '../context'; import { getEnrichedFieldInfo } from '../../../../common/components/event_details/helpers'; import { SecurityCellActionsTrigger } from '../../../../actions/constants'; +import { STATUS_TITLE_TEST_ID } from './test_ids'; /** * Checks if the field info has data to convert EnrichedFieldInfo into EnrichedFieldInfoWithValues @@ -34,7 +38,8 @@ function hasData(fieldInfo?: EnrichedFieldInfo): fieldInfo is EnrichedFieldInfoW */ export const DocumentStatus: FC = () => { const { closeFlyout } = useExpandableFlyoutApi(); - const { eventId, browserFields, dataFormattedForFieldBrowser, scopeId } = useRightPanelContext(); + const { eventId, browserFields, dataFormattedForFieldBrowser, scopeId, isPreview } = + useRightPanelContext(); const statusData = useMemo(() => { const item = find( @@ -53,28 +58,44 @@ export const DocumentStatus: FC = () => { ); }, [browserFields, dataFormattedForFieldBrowser, eventId, scopeId]); - if (!statusData || !hasData(statusData)) return null; - return ( - - - + + + +

+ +

+
+
+ + {!statusData || !hasData(statusData) || isPreview ? ( + getEmptyTagValue() + ) : ( + + + + )} + +
); }; 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 5b176a34014abf..2a218e9b28234d 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 @@ -12,6 +12,8 @@ import { CONTENT_TEST_ID, HEADER_TEST_ID } from './expandable_section'; const FLYOUT_HEADER_TEST_ID = `${PREFIX}Header` as const; export const FLYOUT_HEADER_TITLE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}Title` as const; +export const ALERT_SUMMARY_PANEL_TEST_ID = `${FLYOUT_HEADER_TEST_ID}AlertSumaryPanel` as const; +export const STATUS_TITLE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}StatusTitle` as const; export const STATUS_BUTTON_TEST_ID = 'rule-status-badge' as const; export const SEVERITY_VALUE_TEST_ID = 'severity' as const; export const RISK_SCORE_TITLE_TEST_ID = `${FLYOUT_HEADER_TEST_ID}RiskScoreTitle` as const; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts index aaa8806362d066..357758aabf9c69 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts @@ -34,6 +34,7 @@ import { DOCUMENT_DETAILS_FLYOUT_HEADER_LINK_ICON, DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE, DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE, + DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES, DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE, DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS, DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE, @@ -80,11 +81,13 @@ describe('Alert details expandable flyout right panel', { tags: ['@ess', '@serve cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS).should('have.text', 'open'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE).should('have.text', 'Risk score:'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE).should('have.text', 'Risk score'); cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE) .should('be.visible') .and('have.text', rule.risk_score); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES).should('have.text', 'Assignees'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE) .should('be.visible') .and('have.text', upperFirst(rule.severity));