Skip to content

Commit

Permalink
[Security Solution][Notes] - add note block to alert defaitls flyout …
Browse files Browse the repository at this point in the history
…header (elastic#193373)
  • Loading branch information
PhilippeOberti committed Sep 20, 2024
1 parent ef5aee1 commit 258adf5
Show file tree
Hide file tree
Showing 14 changed files with 465 additions and 101 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.
*/

import React from 'react';
import { render } from '@testing-library/react';
import { AlertHeaderBlock } from './alert_header_block';

const title = <div>{'title'}</div>;
const children = <div data-test-subj={'CHILDREN_TEST_ID'}>{'children'}</div>;
const dataTestSubj = 'TITLE_TEST_ID';

describe('<AlertHeaderBlock />', () => {
it('should render component', () => {
const { getByTestId } = render(
<AlertHeaderBlock title={title} data-test-subj={dataTestSubj}>
{children}
</AlertHeaderBlock>
);

expect(getByTestId('TITLE_TEST_ID')).toHaveTextContent('title');
expect(getByTestId('CHILDREN_TEST_ID')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.
*/

import type { ReactElement, ReactNode, VFC } from 'react';
import React, { memo } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui';

export interface AlertHeaderBlockProps {
/**
* React component to render as the title
*/
title: ReactElement;
/**
* React component to render as the value
*/
children: ReactNode;
/**
* data-test-subj to use for the title
*/
['data-test-subj']?: string;
}

/**
* Reusable component for rendering a block with rounded edges, show a title and value below one another
*/
export const AlertHeaderBlock: VFC<AlertHeaderBlockProps> = memo(
({ title, children, 'data-test-subj': dataTestSubj }) => (
<EuiPanel hasShadow={false} hasBorder paddingSize="s">
<EuiFlexGroup direction="column" gutterSize="xs" responsive={false}>
<EuiFlexItem grow={false}>
<EuiTitle size="xxs" data-test-subj={dataTestSubj}>
<h3>{title}</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>{children}</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
)
);

AlertHeaderBlock.displayName = 'AlertHeaderBlock';
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ import {
SEVERITY_VALUE_TEST_ID,
FLYOUT_ALERT_HEADER_TITLE_TEST_ID,
STATUS_BUTTON_TEST_ID,
ASSIGNEES_HEADER_TEST_ID,
ALERT_SUMMARY_PANEL_TEST_ID,
ASSIGNEES_TEST_ID,
ASSIGNEES_EMPTY_TEST_ID,
NOTES_TITLE_TEST_ID,
} from './test_ids';
import { AlertHeaderTitle } from './alert_header_title';
import moment from 'moment-timezone';
import { useDateFormat, useTimeZone } from '../../../../common/lib/kibana';
import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data';
import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser';
import { TestProvidersComponent } from '../../../../common/mock';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';

jest.mock('../../../../common/lib/kibana');
jest.mock('../../../../common/hooks/use_experimental_features');

moment.suppressDeprecationWarnings = true;
moment.tz.setDefault('UTC');
Expand Down Expand Up @@ -51,15 +55,16 @@ describe('<AlertHeaderTitle />', () => {
});

it('should render component', () => {
const { getByTestId } = renderHeader(mockContextValue);
const { getByTestId, queryByTestId } = renderHeader(mockContextValue);

expect(getByTestId(HEADER_TEXT_TEST_ID)).toHaveTextContent('rule-name');
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();
expect(getByTestId(ASSIGNEES_TEST_ID)).toBeInTheDocument();
expect(queryByTestId(ASSIGNEES_EMPTY_TEST_ID)).not.toBeInTheDocument();
});

it('should render title correctly if flyout is in preview', () => {
Expand All @@ -68,7 +73,15 @@ describe('<AlertHeaderTitle />', () => {

expect(getByTestId(RISK_SCORE_VALUE_TEST_ID)).toBeInTheDocument();
expect(queryByTestId(STATUS_BUTTON_TEST_ID)).not.toBeInTheDocument();
expect(getByTestId(ASSIGNEES_HEADER_TEST_ID)).toHaveTextContent('Assignees—');
expect(getByTestId(ASSIGNEES_EMPTY_TEST_ID)).toBeInTheDocument();
expect(queryByTestId(ASSIGNEES_TEST_ID)).not.toBeInTheDocument();
});

it('should render notes section if experimental flag is enabled', () => {
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true);

const { getByTestId } = renderHeader(mockContextValue);
expect(getByTestId(NOTES_TITLE_TEST_ID)).toBeInTheDocument();
});

it('should render fall back values if document is not alert', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
*/

import React, { memo, useCallback, useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiPanel, useEuiTheme, EuiLink } from '@elastic/eui';
import { css } from '@emotion/css';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiLink } from '@elastic/eui';
import { ALERT_WORKFLOW_ASSIGNEE_IDS } from '@kbn/rule-data-utils';
import { i18n } from '@kbn/i18n';
import { FlyoutTitle } from '@kbn/security-solution-common';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
import { Notes } from './notes';
import { useRuleDetailsLink } from '../../shared/hooks/use_rule_details_link';
import { DocumentStatus } from './status';
import { DocumentSeverity } from './severity';
Expand All @@ -22,6 +23,11 @@ import { PreferenceFormattedDate } from '../../../../common/components/formatted
import { FLYOUT_ALERT_HEADER_TITLE_TEST_ID, ALERT_SUMMARY_PANEL_TEST_ID } from './test_ids';
import { Assignees } from './assignees';

// minWidth for each block, allows to switch for a 1 row 4 blocks to 2 rows with 2 block each
const blockStyles = {
minWidth: 280,
};

/**
* Alert details flyout right section header
*/
Expand All @@ -34,10 +40,13 @@ export const AlertHeaderTitle = memo(() => {
refetchFlyoutData,
getFieldsData,
} = useDocumentDetailsContext();
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
'securitySolutionNotesEnabled'
);

const { isAlert, ruleName, timestamp, ruleId } = useBasicDataFromDetailsData(
dataFormattedForFieldBrowser
);
const { euiTheme } = useEuiTheme();

const href = useRuleDetailsLink({ ruleId: !isPreview ? ruleId : null });
const ruleTitle = useMemo(
Expand Down Expand Up @@ -89,27 +98,52 @@ export const AlertHeaderTitle = memo(() => {
/>
)}
<EuiSpacer size="m" />
<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};
`}
>
{securitySolutionNotesEnabled ? (
<EuiFlexGroup
direction="row"
gutterSize="s"
responsive={false}
wrap
data-test-subj={ALERT_SUMMARY_PANEL_TEST_ID}
>
<EuiFlexItem style={blockStyles}>
<EuiFlexGroup direction="row" gutterSize="s" responsive={false}>
<EuiFlexItem>
<DocumentStatus />
</EuiFlexItem>
<EuiFlexItem>
<RiskScore />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem style={blockStyles}>
<EuiFlexGroup direction="row" gutterSize="s" responsive={false}>
<EuiFlexItem>
<Assignees
eventId={eventId}
assignedUserIds={alertAssignees}
onAssigneesUpdated={onAssigneesUpdated}
isPreview={isPreview}
/>
</EuiFlexItem>
<EuiFlexItem>
<Notes />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
) : (
<EuiFlexGroup
direction="row"
gutterSize="s"
responsive={false}
wrap
data-test-subj={ALERT_SUMMARY_PANEL_TEST_ID}
>
<EuiFlexItem>
<DocumentStatus />
</EuiFlexItem>
<EuiFlexItem
css={css`
border-right: ${euiTheme.border.thin};
`}
>
<EuiFlexItem>
<RiskScore />
</EuiFlexItem>
<EuiFlexItem>
Expand All @@ -121,7 +155,7 @@ export const AlertHeaderTitle = memo(() => {
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
)}
</>
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { render } from '@testing-library/react';

import {
ASSIGNEES_ADD_BUTTON_TEST_ID,
ASSIGNEES_EMPTY_TEST_ID,
ASSIGNEES_TITLE_TEST_ID,
ASSIGNEES_HEADER_TEST_ID,
} from './test_ids';
import { Assignees } from './assignees';

Expand Down Expand Up @@ -180,6 +180,6 @@ describe('<Assignees />', () => {
);

expect(queryByTestId(ASSIGNEES_ADD_BUTTON_TEST_ID)).not.toBeInTheDocument();
expect(getByTestId(ASSIGNEES_HEADER_TEST_ID)).toHaveTextContent('Assignees—');
expect(getByTestId(ASSIGNEES_EMPTY_TEST_ID)).toHaveTextContent('—');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiPopover,
EuiTitle,
EuiToolTip,
useGeneratedHtmlId,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { AlertHeaderBlock } from './alert_header_block';
import { useSetAlertAssignees } from '../../../../common/components/toolbar/bulk_actions/use_set_alert_assignees';
import { getEmptyTagValue } from '../../../../common/components/empty_value';
import { ASSIGNEES_PANEL_WIDTH } from '../../../../common/components/assignees/constants';
Expand All @@ -32,7 +32,8 @@ import { useBulkGetUserProfiles } from '../../../../common/components/user_profi
import { UsersAvatarsPanel } from '../../../../common/components/user_profiles/users_avatars_panel';
import {
ASSIGNEES_ADD_BUTTON_TEST_ID,
ASSIGNEES_HEADER_TEST_ID,
ASSIGNEES_EMPTY_TEST_ID,
ASSIGNEES_TEST_ID,
ASSIGNEES_TITLE_TEST_ID,
} from './test_ids';

Expand Down Expand Up @@ -158,26 +159,19 @@ export const Assignees: FC<AssigneesProps> = memo(
]);

return (
<EuiFlexGroup
data-test-subj={ASSIGNEES_HEADER_TEST_ID}
direction="column"
gutterSize="none"
responsive={false}
<AlertHeaderBlock
title={
<FormattedMessage
id="xpack.securitySolution.flyout.right.header.assignedTitle"
defaultMessage="Assignees"
/>
}
data-test-subj={ASSIGNEES_TITLE_TEST_ID}
>
<EuiFlexItem grow={false}>
<EuiTitle size="xxs" data-test-subj={ASSIGNEES_TITLE_TEST_ID}>
<h3>
<FormattedMessage
id="xpack.securitySolution.flyout.right.header.assignedTitle"
defaultMessage="Assignees"
/>
</h3>
</EuiTitle>
</EuiFlexItem>
{isPreview ? (
getEmptyTagValue()
<div data-test-subj={ASSIGNEES_EMPTY_TEST_ID}>{getEmptyTagValue()}</div>
) : (
<EuiFlexGroup gutterSize="none" responsive={false}>
<EuiFlexGroup gutterSize="none" responsive={false} data-test-subj={ASSIGNEES_TEST_ID}>
{assignedUsers && (
<EuiFlexItem grow={false}>
<UsersAvatarsPanel userProfiles={assignedUsers} maxVisibleAvatars={2} />
Expand All @@ -186,7 +180,7 @@ export const Assignees: FC<AssigneesProps> = memo(
<EuiFlexItem grow={false}>{updateAssigneesPopover}</EuiFlexItem>
</EuiFlexGroup>
)}
</EuiFlexGroup>
</AlertHeaderBlock>
);
}
);
Expand Down
Loading

0 comments on commit 258adf5

Please sign in to comment.