Skip to content

Commit

Permalink
[Security Solution][Alert Flyout] Convert rule name to rule flyout an…
Browse files Browse the repository at this point in the history
…d enable rule previews (elastic#191764)

## Summary

This PR converts rule name in alert table to be a flyout (consistent
with host name and user name) and enables rule preview whenever rule
name is present.

This PR also moved the rule details component into its own
`rule_details` folder to be independent of the `document_details`
flyout.

Dependency: elastic#190560 to be merged
first

New behavior:
- Rule link in alert table opens rule flyout
- Clicking the rule title goes to rule details page
- Clicking rule name in alert flyout opens rule preview



https://github.com/user-attachments/assets/857aa894-6253-4041-873a-18d6e8a003b6



### Checklist

- [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 and gergoabraham committed Sep 13, 2024
1 parent c56792b commit de5cb6e
Show file tree
Hide file tree
Showing 37 changed files with 1,054 additions and 707 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { mockContextValue } from '../../shared/mocks/mock_context';
import { DocumentDetailsPreviewPanelKey } from '../../shared/constants/panel_keys';
import { ALERT_PREVIEW_BANNER } from '../../preview/constants';
import { DocumentDetailsContext } from '../../shared/context';
import { RulePreviewPanelKey, RULE_PREVIEW_BANNER } from '../../../rule_details/right';

jest.mock('../hooks/use_paginated_alerts');
jest.mock('../../../../common/hooks/use_experimental_features');
Expand Down Expand Up @@ -59,6 +60,7 @@ describe('CorrelationsDetailsAlertsTable', () => {
'kibana.alert.rule.name': ['Rule1'],
'kibana.alert.reason': ['Reason1'],
'kibana.alert.severity': ['Severity1'],
'kibana.alert.rule.uuid': ['uuid1'],
},
},
{
Expand All @@ -69,6 +71,7 @@ describe('CorrelationsDetailsAlertsTable', () => {
'kibana.alert.rule.name': ['Rule2'],
'kibana.alert.reason': ['Reason2'],
'kibana.alert.severity': ['Severity2'],
'kibana.alert.rule.uuid': ['uuid2'],
},
},
],
Expand Down Expand Up @@ -124,4 +127,27 @@ describe('CorrelationsDetailsAlertsTable', () => {
},
});
});

it('opens rule preview when feature flag is on and isPreview is false', () => {
mockUseIsExperimentalFeatureEnabled.mockReturnValue(false);
const { getAllByTestId } = renderCorrelationsTable(mockContextValue);

expect(getAllByTestId(`${TEST_ID}RulePreview`).length).toBe(2);

getAllByTestId(`${TEST_ID}RulePreview`)[0].click();
expect(mockFlyoutApi.openPreviewPanel).toHaveBeenCalledWith({
id: RulePreviewPanelKey,
params: {
ruleId: 'uuid1',
banner: RULE_PREVIEW_BANNER,
isPreviewMode: true,
},
});
});

it('does not render preview link when feature flag is on and isPreview is true', () => {
mockUseIsExperimentalFeatureEnabled.mockReturnValue(false);
const { queryByTestId } = renderCorrelationsTable({ ...mockContextValue, isPreview: true });
expect(queryByTestId(`${TEST_ID}RulePreview`)).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { InvestigateInTimelineButton } from '../../../../common/components/event
import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations';
import { getDataProvider } from '../../../../common/components/event_details/use_action_cell_data_provider';
import { AlertPreviewButton } from '../../../shared/components/alert_preview_button';
import { PreviewLink } from '../../../shared/components/preview_link';
import { useDocumentDetailsContext } from '../../shared/context';

export const TIMESTAMP_DATE_FORMAT = 'MMM D, YYYY @ HH:mm:ss.SSS';
const dataProviderLimit = 5;
Expand Down Expand Up @@ -82,6 +84,8 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr
} = usePaginatedAlerts(alertIds || []);
const isPreviewEnabled = !useIsExperimentalFeatureEnabled('entityAlertPreviewDisabled');

const { isPreview } = useDocumentDetailsContext();

const onTableChange = useCallback(
({ page, sort }: Criteria<Record<string, unknown>>) => {
if (page) {
Expand Down Expand Up @@ -160,19 +164,35 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr
},
},
{
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>
),
render: (row: Record<string, unknown>) => {
const ruleName = row[ALERT_RULE_NAME] as string;
const ruleId = row['kibana.alert.rule.uuid'] as string;
return (
<CellTooltipWrapper tooltip={ruleName}>
{isPreviewEnabled ? (
<PreviewLink
field={ALERT_RULE_NAME}
value={ruleName}
scopeId={scopeId}
ruleId={ruleId}
isPreview={isPreview}
data-test-subj={`${dataTestSubj}RulePreview`}
>
<span>{ruleName}</span>
</PreviewLink>
) : (
<span>{ruleName}</span>
)}
</CellTooltipWrapper>
);
},
},
{
field: ALERT_REASON,
Expand Down Expand Up @@ -209,7 +229,7 @@ export const CorrelationsDetailsAlertsTable: FC<CorrelationsDetailsAlertsTablePr
},
},
],
[isPreviewEnabled, scopeId, dataTestSubj]
[isPreviewEnabled, scopeId, dataTestSubj, isPreview]
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import {
RULE_SUMMARY_BUTTON_TEST_ID,
ALERT_DESCRIPTION_DETAILS_TEST_ID,
} from './test_ids';
import { AlertDescription, RULE_OVERVIEW_BANNER } from './alert_description';
import { AlertDescription } from './alert_description';
import { DocumentDetailsContext } from '../../shared/context';
import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data';
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
import { DocumentDetailsRuleOverviewPanelKey } from '../../shared/constants/panel_keys';
import { TestProviders } from '../../../../common/mock';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import type { ExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { createTelemetryServiceMock } from '../../../../common/lib/telemetry/telemetry_service.mock';
import { RulePreviewPanelKey, RULE_PREVIEW_BANNER } from '../../../rule_details/right';

const mockedTelemetry = createTelemetryServiceMock();
jest.mock('../../../../common/lib/kibana', () => {
Expand Down Expand Up @@ -150,13 +150,11 @@ describe('<AlertDescription />', () => {
getByTestId(RULE_SUMMARY_BUTTON_TEST_ID).click();

expect(flyoutContextValue.openPreviewPanel).toHaveBeenCalledWith({
id: DocumentDetailsRuleOverviewPanelKey,
id: RulePreviewPanelKey,
params: {
id: panelContext.eventId,
indexName: panelContext.indexName,
scopeId: panelContext.scopeId,
banner: RULE_OVERVIEW_BANNER,
banner: RULE_PREVIEW_BANNER,
ruleId: ruleUuid.values[0],
isPreviewMode: true,
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,32 @@ import {
ALERT_DESCRIPTION_TITLE_TEST_ID,
RULE_SUMMARY_BUTTON_TEST_ID,
} from './test_ids';
import { DocumentDetailsRuleOverviewPanelKey } from '../../shared/constants/panel_keys';

export const RULE_OVERVIEW_BANNER = {
title: i18n.translate('xpack.securitySolution.flyout.right.about.description.rulePreviewTitle', {
defaultMessage: 'Preview rule details',
}),
backgroundColor: 'warning',
textColor: 'warning',
};
import { RULE_PREVIEW_BANNER, RulePreviewPanelKey } from '../../../rule_details/right';

/**
* Displays the rule description of a signal document.
*/
export const AlertDescription: FC = () => {
const { telemetry } = useKibana().services;
const { dataFormattedForFieldBrowser, scopeId, eventId, indexName, isPreview } =
useDocumentDetailsContext();
const { dataFormattedForFieldBrowser, scopeId, isPreview } = useDocumentDetailsContext();
const { isAlert, ruleDescription, ruleName, ruleId } = useBasicDataFromDetailsData(
dataFormattedForFieldBrowser
);
const { openPreviewPanel } = useExpandableFlyoutApi();
const openRulePreview = useCallback(() => {
openPreviewPanel({
id: DocumentDetailsRuleOverviewPanelKey,
id: RulePreviewPanelKey,
params: {
id: eventId,
indexName,
scopeId,
banner: RULE_OVERVIEW_BANNER,
ruleId,
banner: RULE_PREVIEW_BANNER,
isPreviewMode: true,
},
});
telemetry.reportDetailsFlyoutOpened({
location: scopeId,
panel: 'preview',
});
}, [eventId, openPreviewPanel, indexName, scopeId, ruleId, telemetry]);
}, [openPreviewPanel, scopeId, ruleId, telemetry]);

const viewRule = useMemo(
() => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ describe('TableFieldValueCell', () => {
data={hostIpData}
eventId={eventId}
values={hostIpValues}
ruleId="ruleId"
isPreview={false}
/>
</DocumentDetailsContext.Provider>
</TestProviders>
Expand All @@ -106,6 +108,8 @@ describe('TableFieldValueCell', () => {
eventId={eventId}
fieldFromBrowserField={undefined} // <-- no metadata
values={hostIpValues}
ruleId="ruleId"
isPreview={false}
/>
</DocumentDetailsContext.Provider>
</TestProviders>
Expand Down Expand Up @@ -152,6 +156,8 @@ describe('TableFieldValueCell', () => {
eventId={eventId}
fieldFromBrowserField={messageFieldFromBrowserField}
values={messageValues}
ruleId="ruleId"
isPreview={false}
/>
</DocumentDetailsContext.Provider>
</TestProviders>
Expand Down Expand Up @@ -188,6 +194,8 @@ describe('TableFieldValueCell', () => {
eventId={eventId}
fieldFromBrowserField={hostIpFieldFromBrowserField} // <-- metadata
values={hostIpValues}
ruleId="ruleId"
isPreview={false}
/>
</DocumentDetailsContext.Provider>
</TestProviders>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ export interface FieldValueCellProps {
* Field retrieved from the BrowserField
*/
fieldFromBrowserField?: Partial<FieldSpec>;
/**
* Id of the rule
*/
ruleId: string;
/**
* Whether the preview link is in rule preview
*/
isPreview: boolean;
/**
* Value of the link field if it exists. Allows to navigate to other pages like host, user, network...
*/
Expand All @@ -53,8 +61,10 @@ export const TableFieldValueCell = memo(
data,
eventId,
fieldFromBrowserField,
ruleId,
getLinkValue,
values,
isPreview,
}: FieldValueCellProps) => {
const isPreviewEnabled = !useIsExperimentalFeatureEnabled('entityAlertPreviewDisabled');
if (values == null) {
Expand Down Expand Up @@ -87,6 +97,8 @@ export const TableFieldValueCell = memo(
field={data.field}
value={value}
scopeId={scopeId}
ruleId={ruleId}
isPreview={isPreview}
data-test-subj={`${FLYOUT_TABLE_PREVIEW_LINK_FIELD_TEST_ID}-${i}`}
/>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type { EventFieldsData } from '../../../../common/components/event_detail
import { CellActions } from '../../shared/components/cell_actions';
import { useDocumentDetailsContext } from '../../shared/context';
import { isInTableScope, isTimelineScope } from '../../../../helpers';
import { useBasicDataFromDetailsData } from '../../shared/hooks/use_basic_data_from_details_data';

const COUNT_PER_PAGE_OPTIONS = [25, 50, 100];

Expand Down Expand Up @@ -76,13 +77,28 @@ export type ColumnsProvider = (providerOptions: {
* Maintain backwards compatibility // TODO remove when possible
*/
scopeId: string;
/**
* Id of the rule
*/
ruleId: string;
/**
* Whether the preview link is in preview mode
*/
isPreview: boolean;
/**
* Value of the link field if it exists. Allows to navigate to other pages like host, user, network...
*/
getLinkValue: (field: string) => string | null;
}) => Array<EuiBasicTableColumn<TimelineEventsDetailsItem>>;

export const getColumns: ColumnsProvider = ({ browserFields, eventId, scopeId, getLinkValue }) => [
export const getColumns: ColumnsProvider = ({
browserFields,
eventId,
scopeId,
getLinkValue,
ruleId,
isPreview,
}) => [
{
field: 'field',
name: (
Expand Down Expand Up @@ -113,6 +129,8 @@ export const getColumns: ColumnsProvider = ({ browserFields, eventId, scopeId, g
eventId={eventId}
fieldFromBrowserField={fieldFromBrowserField}
getLinkValue={getLinkValue}
ruleId={ruleId}
isPreview={isPreview}
values={values}
/>
</CellActions>
Expand All @@ -127,8 +145,9 @@ export const getColumns: ColumnsProvider = ({ browserFields, eventId, scopeId, g
export const TableTab = memo(() => {
const smallFontSize = useEuiFontSize('xs').fontSize;

const { browserFields, dataFormattedForFieldBrowser, eventId, scopeId } =
const { browserFields, dataFormattedForFieldBrowser, eventId, scopeId, isPreview } =
useDocumentDetailsContext();
const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser);

const [pagination, setPagination] = useState<{ pageIndex: number }>({
pageIndex: 0,
Expand Down Expand Up @@ -199,8 +218,10 @@ export const TableTab = memo(() => {
eventId,
scopeId,
getLinkValue,
ruleId,
isPreview,
}),
[browserFields, eventId, scopeId, getLinkValue]
[browserFields, eventId, scopeId, getLinkValue, ruleId, isPreview]
);

return (
Expand Down

This file was deleted.

Loading

0 comments on commit de5cb6e

Please sign in to comment.