Skip to content

Commit

Permalink
[Security Solution] Alert Flyout - update header summary panel (elast…
Browse files Browse the repository at this point in the history
…ic#175075)

## Summary

Follow up PR of elastic#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
  • Loading branch information
christineweng authored Jan 23, 2024
1 parent f77542c commit e8ad8cb
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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({
Expand All @@ -57,6 +62,7 @@ const renderAssignees = (
eventId={eventId}
assignedUserIds={alertAssignees}
onAssigneesUpdated={onAssigneesUpdated}
isPreview={isPreview}
/>
</TestProviders>
);
Expand Down Expand Up @@ -163,4 +169,17 @@ describe('<Assignees />', () => {
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—');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<AssigneesProps> = memo(
({ eventId, assignedUserIds, onAssigneesUpdated }) => {
({ eventId, assignedUserIds, onAssigneesUpdated, isPreview }) => {
const isPlatinumPlus = useLicense().isPlatinumPlus();
const upsellingMessage = useUpsellingMessage('alert_assignments');

Expand Down Expand Up @@ -108,49 +113,54 @@ export const Assignees: FC<AssigneesProps> = memo(
return (
<EuiFlexGroup
data-test-subj={ASSIGNEES_HEADER_TEST_ID}
alignItems="center"
direction="row"
gutterSize="xs"
direction="column"
gutterSize="none"
responsive={false}
>
<EuiFlexItem grow={false}>
<EuiTitle size="xxs" data-test-subj={ASSIGNEES_TITLE_TEST_ID}>
<h3>
<FormattedMessage
id="xpack.securitySolution.flyout.right.header.assignedTitle"
defaultMessage="Assignees:"
defaultMessage="Assignees"
/>
</h3>
</EuiTitle>
</EuiFlexItem>
{assignedUsers && (
<EuiFlexItem grow={false}>
<UsersAvatarsPanel userProfiles={assignedUsers} maxVisibleAvatars={2} />
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<AssigneesPopover
assignedUserIds={assignedUserIds}
button={
<UpdateAssigneesButton
togglePopover={togglePopover}
isDisabled={!hasIndexWrite || !isPlatinumPlus}
toolTipMessage={
upsellingMessage ??
i18n.translate(
'xpack.securitySolution.flyout.right.visualizations.assignees.popoverTooltip',
{
defaultMessage: 'Assign alert',
{isPreview ? (
getEmptyTagValue()
) : (
<>
{assignedUsers && (
<EuiFlexItem grow={false}>
<UsersAvatarsPanel userProfiles={assignedUsers} maxVisibleAvatars={2} />
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<AssigneesPopover
assignedUserIds={assignedUserIds}
button={
<UpdateAssigneesButton
togglePopover={togglePopover}
isDisabled={!hasIndexWrite || !isPlatinumPlus}
toolTipMessage={
upsellingMessage ??
i18n.translate(
'xpack.securitySolution.flyout.right.visualizations.assignees.popoverTooltip',
{
defaultMessage: 'Assign alert',
}
)
}
)
/>
}
isPopoverOpen={isPopoverOpen}
closePopover={togglePopover}
onAssigneesApply={onAssigneesApply}
/>
}
isPopoverOpen={isPopoverOpen}
closePopover={togglePopover}
onAssigneesApply={onAssigneesApply}
/>
</EuiFlexItem>
</EuiFlexItem>
</>
)}
</EuiFlexGroup>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -51,9 +53,12 @@ describe('<HeaderTitle />', () => {
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', () => {
Expand Down Expand Up @@ -81,7 +86,7 @@ describe('<HeaderTitle />', () => {
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: [
Expand All @@ -96,16 +101,9 @@ describe('<HeaderTitle />', () => {
} 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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';

Expand All @@ -39,6 +47,7 @@ export const HeaderTitle: FC = memo(() => {
const { isAlert, ruleName, timestamp, ruleId } = useBasicDataFromDetailsData(
dataFormattedForFieldBrowser
);
const { euiTheme } = useEuiTheme();

const ruleTitle = useMemo(
() =>
Expand Down Expand Up @@ -102,25 +111,41 @@ export const HeaderTitle: FC = memo(() => {
{isAlert && !isEmpty(ruleName) ? ruleTitle : eventTitle}
</div>
<EuiSpacer size="m" />
<EuiFlexGroup direction="row" gutterSize="m" responsive={false}>
{isAlert && !isPreview && (
<EuiFlexItem grow={false}>
<DocumentStatus />
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<RiskScore />
</EuiFlexItem>
{isAlert && !isPreview && (
<EuiFlexItem grow={false}>
<Assignees
eventId={eventId}
assignedUserIds={alertAssignees}
onAssigneesUpdated={onAssigneesUpdated}
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
{isAlert && (
<EuiPanel
hasShadow={false}
hasBorder
css={css`
padding: ${euiTheme.size.m} ${euiTheme.size.s};
`}
data-test-subj={ALERT_SUMMARY_PANEL_TEST_ID}
>
<EuiFlexGroup direction="row" gutterSize="m" responsive={false}>
<EuiFlexItem
css={css`
border-right: ${euiTheme.border.thin};
`}
>
<DocumentStatus />
</EuiFlexItem>
<EuiFlexItem
css={css`
border-right: ${euiTheme.border.thin};
`}
>
<RiskScore />
</EuiFlexItem>
<EuiFlexItem>
<Assignees
eventId={eventId}
assignedUserIds={alertAssignees}
onAssigneesUpdated={onAssigneesUpdated}
isPreview={isPreview}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
)}
</>
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ export const RiskScore: FC = memo(() => {
}

return (
<EuiFlexGroup alignItems="center" direction="row" gutterSize="xs" responsive={false}>
<EuiFlexGroup direction="column" gutterSize="xs" responsive={false}>
<EuiFlexItem grow={false}>
<EuiTitle size="xxs" data-test-subj={RISK_SCORE_TITLE_TEST_ID}>
<h3>
<FormattedMessage
id="xpack.securitySolution.flyout.right.header.riskScoreTitle"
defaultMessage="Risk score:"
defaultMessage="Risk score"
/>
</h3>
</EuiTitle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('<DocumentStatus />', () => {
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: {},
Expand All @@ -66,6 +66,20 @@ describe('<DocumentStatus />', () => {

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—');
});
});
Loading

0 comments on commit e8ad8cb

Please sign in to comment.