Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Enable alert preview in document details flyout #186857

Merged
merged 9 commits into from
Jul 2, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
swap icon and css updates
christineweng committed Jun 27, 2024
commit b793fb661b130e5eaa5c54e464cdd23b4d6f41e4
Original file line number Diff line number Diff line change
@@ -129,8 +129,8 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({
<div
css={css`
position: absolute;
top: 4px;
bottom: 12px;
top: 8px;
bottom: 8px;
christineweng marked this conversation as resolved.
Show resolved Hide resolved
right: 4px;
left: ${left}px;
z-index: 1000;
@@ -139,7 +139,7 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({
<EuiSplitPanel.Outer
css={css`
margin: ${euiTheme.size.xs};
box-shadow: 0 0 4px 4px ${euiTheme.colors.darkShade};
box-shadow: 0 0 16px 0px rgb(105, 112, 125, 0.5);
christineweng marked this conversation as resolved.
Show resolved Hide resolved
`}
data-test-subj={PREVIEW_SECTION_TEST_ID}
className="eui-fullHeight"
@@ -148,7 +148,7 @@ export const PreviewSection: React.FC<PreviewSectionProps> = ({
<EuiSplitPanel.Inner
grow={false}
color={banner.backgroundColor}
paddingSize="none"
paddingSize="xs"
christineweng marked this conversation as resolved.
Show resolved Hide resolved
data-test-subj={`${PREVIEW_SECTION_TEST_ID}BannerPanel`}
>
<EuiText
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import { TestProviders } from '../../../../common/mock';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { CorrelationsDetailsAlertsTable } from './correlations_details_alerts_table';
import { usePaginatedAlerts } from '../hooks/use_paginated_alerts';
import { CORRELATIONS_DETAILS_ALERT_LINK_TEST_ID } from './test_ids';
import { CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID } from './test_ids';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
import { mockFlyoutApi } from '../../shared/mocks/mock_flyout_context';
import { mockContextValue } from '../../shared/mocks/mock_context';
@@ -94,7 +94,9 @@ describe('CorrelationsDetailsAlertsTable', () => {

expect(getByTestId(`${TEST_ID}InvestigateInTimeline`)).toBeInTheDocument();
expect(getByTestId(`${TEST_ID}Table`)).toBeInTheDocument();
expect(queryByTestId(CORRELATIONS_DETAILS_ALERT_LINK_TEST_ID)).not.toBeInTheDocument();
expect(
queryByTestId(CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID)
).not.toBeInTheDocument();

expect(jest.mocked(usePaginatedAlerts)).toHaveBeenCalled();

@@ -106,17 +108,17 @@ describe('CorrelationsDetailsAlertsTable', () => {
expect(queryAllByRole('row')[1].textContent).toContain('Severity1');
});

it('renders alert preview link when feature flag is on', () => {
it('renders open preview button when feature flag is on', () => {
mockUseIsExperimentalFeatureEnabled.mockReturnValue(true);
const { getByTestId, getAllByTestId } = renderCorrelationsTable({
...mockContextValue,
isPreviewMode: true,
});

expect(getByTestId(`${TEST_ID}InvestigateInTimeline`)).toBeInTheDocument();
expect(getAllByTestId(CORRELATIONS_DETAILS_ALERT_LINK_TEST_ID).length).toBe(2);
expect(getAllByTestId(CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID).length).toBe(2);

getAllByTestId(CORRELATIONS_DETAILS_ALERT_LINK_TEST_ID)[0].click();
getAllByTestId(CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID)[0].click();
expect(mockFlyoutApi.openPreviewPanel).toHaveBeenCalledWith({
id: DocumentDetailsPreviewPanelKey,
params: {
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

import type { ReactElement, ReactNode } from 'react';
import React, { type FC, useMemo, useCallback } from 'react';
import { type Criteria, EuiBasicTable, formatDate, EuiLink } from '@elastic/eui';
import { type Criteria, EuiBasicTable, formatDate, EuiButtonIcon } from '@elastic/eui';
import { Severity } from '@kbn/securitysolution-io-ts-alerting-types';
import type { Filter } from '@kbn/es-query';
import { isRight } from 'fp-ts/lib/Either';
@@ -17,7 +17,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
import { useDocumentDetailsContext } from '../../shared/context';
import { CORRELATIONS_DETAILS_ALERT_LINK_TEST_ID } from './test_ids';
import { CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID } from './test_ids';
import { CellTooltipWrapper } from '../../shared/components/cell_tooltip_wrapper';
import type { DataProvider } from '../../../../../common/types';
import { SeverityBadge } from '../../../../common/components/severity_badge';
@@ -32,11 +32,7 @@ import { ALERT_PREVIEW_BANNER } from '../../preview';
export const TIMESTAMP_DATE_FORMAT = 'MMM D, YYYY @ HH:mm:ss.SSS';
const dataProviderLimit = 5;

interface RuleLinkProps {
/**
* Rule name of the alert
*/
ruleName: string;
interface AlertPreviewButtonProps {
/**
* Id of the document
*/
@@ -47,10 +43,9 @@ interface RuleLinkProps {
indexName: string;
}

const RuleLink: FC<RuleLinkProps> = ({ ruleName, id, indexName }) => {
const AlertPreviewButton: FC<AlertPreviewButtonProps> = ({ id, indexName }) => {
const { openPreviewPanel } = useExpandableFlyoutApi();
const { scopeId } = useDocumentDetailsContext();
const isPreviewEnabled = useIsExperimentalFeatureEnabled('entityAlertPreviewEnabled');

const openAlertPreview = useCallback(
() =>
@@ -67,88 +62,15 @@ const RuleLink: FC<RuleLinkProps> = ({ ruleName, id, indexName }) => {
[openPreviewPanel, id, indexName, scopeId]
);

return isPreviewEnabled ? (
<EuiLink data-test-subj={CORRELATIONS_DETAILS_ALERT_LINK_TEST_ID} onClick={openAlertPreview}>
{ruleName}
</EuiLink>
) : (
<span>{ruleName}</span>
return (
<EuiButtonIcon
iconType="expand"
data-test-subj={CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID}
onClick={openAlertPreview}
/>
);
};

export const columns = [
christineweng marked this conversation as resolved.
Show resolved Hide resolved
{
field: '@timestamp',
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.insights.correlations.timestampColumnLabel"
defaultMessage="Timestamp"
/>
),
truncateText: true,
dataType: 'date' as const,
render: (value: string) => {
const date = formatDate(value, TIMESTAMP_DATE_FORMAT);
return (
<CellTooltipWrapper tooltip={date}>
<span>{date}</span>
</CellTooltipWrapper>
);
},
},
{
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.insights.correlations.ruleColumnLabel"
defaultMessage="Alert"
/>
),
truncateText: true,
render: (data: Record<string, unknown>) => {
const ruleName = data[ALERT_RULE_NAME] as string;
return (
<CellTooltipWrapper tooltip={ruleName}>
<RuleLink ruleName={ruleName} id={data.id as string} indexName={data.index as string} />
</CellTooltipWrapper>
);
},
},
{
field: ALERT_REASON,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.insights.correlations.reasonColumnLabel"
defaultMessage="Reason"
/>
),
truncateText: true,
render: (value: string) => (
<CellTooltipWrapper tooltip={value} anchorPosition="left">
<span>{value}</span>
</CellTooltipWrapper>
),
},
{
field: 'kibana.alert.severity',
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.insights.correlations.severityColumnLabel"
defaultMessage="Severity"
/>
),
truncateText: true,
render: (value: string) => {
const decodedSeverity = Severity.decode(value);
const renderValue = isRight(decodedSeverity) ? (
<SeverityBadge value={decodedSeverity.right} />
) : (
<p>{value}</p>
);
return <CellTooltipWrapper tooltip={value}>{renderValue}</CellTooltipWrapper>;
},
},
];

export interface CorrelationsDetailsAlertsTableProps {
/**
* Text to display in the ExpandablePanel title section
@@ -201,6 +123,7 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr
sorting,
error,
} = usePaginatedAlerts(alertIds || []);
const isPreviewEnabled = useIsExperimentalFeatureEnabled('entityAlertPreviewEnabled');

const onTableChange = useCallback(
({ page, sort }: Criteria<Record<string, unknown>>) => {
@@ -245,6 +168,87 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr
[alertIds, shouldUseFilters]
);

const columns = [
...(isPreviewEnabled
? [
{
render: (row: Record<string, unknown>) => (
<AlertPreviewButton id={row.id as string} indexName={row.index as string} />
),
width: '5%',
},
]
: []),
{
field: '@timestamp',
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.insights.correlations.timestampColumnLabel"
defaultMessage="Timestamp"
/>
),
truncateText: true,
dataType: 'date' as const,
render: (value: string) => {
const date = formatDate(value, TIMESTAMP_DATE_FORMAT);
return (
<CellTooltipWrapper tooltip={date}>
<span>{date}</span>
</CellTooltipWrapper>
);
},
},
{
field: ALERT_RULE_NAME,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.insights.correlations.ruleColumnLabel"
defaultMessage="Rule"
/>
),
truncateText: true,
render: (value: string) => (
<CellTooltipWrapper tooltip={value}>
<span>{value}</span>
</CellTooltipWrapper>
),
},
{
field: ALERT_REASON,
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.insights.correlations.reasonColumnLabel"
defaultMessage="Reason"
/>
),
truncateText: true,
render: (value: string) => (
<CellTooltipWrapper tooltip={value} anchorPosition="left">
<span>{value}</span>
</CellTooltipWrapper>
),
},
{
field: 'kibana.alert.severity',
name: (
<FormattedMessage
id="xpack.securitySolution.flyout.left.insights.correlations.severityColumnLabel"
defaultMessage="Severity"
/>
),
truncateText: true,
render: (value: string) => {
const decodedSeverity = Severity.decode(value);
const renderValue = isRight(decodedSeverity) ? (
<SeverityBadge value={decodedSeverity.right} />
) : (
<p>{value}</p>
);
return <CellTooltipWrapper tooltip={value}>{renderValue}</CellTooltipWrapper>;
},
},
];

return (
<ExpandablePanel
header={{
Original file line number Diff line number Diff line change
@@ -69,8 +69,8 @@ export const THREAT_INTELLIGENCE_DETAILS_LOADING_TEST_ID =

export const CORRELATIONS_DETAILS_TEST_ID = `${PREFIX}CorrelationsDetails` as const;

export const CORRELATIONS_DETAILS_ALERT_LINK_TEST_ID =
`${CORRELATIONS_DETAILS_TEST_ID}AlertLink` as const;
export const CORRELATIONS_DETAILS_ALERT_PREVIEW_BUTTON_TEST_ID =
`${CORRELATIONS_DETAILS_TEST_ID}AlertPreviewButton` as const;

export const CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TEST_ID =
`${CORRELATIONS_DETAILS_TEST_ID}AlertsByAncestrySection` as const;
christineweng marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ export const PreviewPanelFooter = () => {
data-test-subj={PREVIEW_FOOTER_LINK_TEST_ID}
>
{i18n.translate('xpack.securitySolution.flyout.preview.openFlyoutLabel', {
defaultMessage: 'Open alert details flyout',
defaultMessage: 'Replace with this flyout',
christineweng marked this conversation as resolved.
Show resolved Hide resolved
})}
</EuiLink>
</EuiFlexItem>
Original file line number Diff line number Diff line change
@@ -76,14 +76,12 @@ const columns: Array<EuiBasicTableColumn<HighlightedFieldsTableRow>> = [
values: string[] | null | undefined;
scopeId: string;
isPreview: boolean;
isPreviewMode: boolean;
}) => (
<CellActions field={description.field} value={description.values}>
<HighlightedFieldsCell
values={description.values}
field={description.field}
originalField={description.originalField}
isPreviewMode={description.isPreviewMode}
/>
</CellActions>
),
@@ -94,8 +92,7 @@ const columns: Array<EuiBasicTableColumn<HighlightedFieldsTableRow>> = [
* Component that displays the highlighted fields in the right panel under the Investigation section.
*/
export const HighlightedFields: FC = () => {
const { dataFormattedForFieldBrowser, scopeId, isPreview, isPreviewMode } =
useDocumentDetailsContext();
const { dataFormattedForFieldBrowser, scopeId, isPreview } = useDocumentDetailsContext();
const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser);
const { loading, rule: maybeRule } = useRuleWithFallback(ruleId);

@@ -104,8 +101,8 @@ export const HighlightedFields: FC = () => {
investigationFields: maybeRule?.investigation_fields?.field_names ?? [],
});
const items = useMemo(
() => convertHighlightedFieldsToTableRow(highlightedFields, scopeId, isPreview, isPreviewMode),
[highlightedFields, scopeId, isPreview, isPreviewMode]
() => convertHighlightedFieldsToTableRow(highlightedFields, scopeId, isPreview),
[highlightedFields, scopeId, isPreview]
);

return (
Loading