Skip to content

Commit

Permalink
[Security solution] [RAC] Add row renderer popover to alert table "re…
Browse files Browse the repository at this point in the history
…ason" field (#108054) (#108385)

* Add row renderer popover to alert table reason field

* Add a title to row renderer popover on alert table

* Fix issues found during code review

Co-authored-by: Pablo Machado <pablo.nevesmachado@elastic.co>
  • Loading branch information
kibanamachine and machadoum committed Aug 12, 2021
1 parent ae211c3 commit ccb3a97
Show file tree
Hide file tree
Showing 11 changed files with 385 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export const RenderCellValue: React.FC<
rowIndex,
setCellProps,
timelineId,
ecsData,
rowRenderers,
browserFields,
}) => (
<DefaultCellRenderer
columnId={columnId}
Expand All @@ -45,5 +48,8 @@ export const RenderCellValue: React.FC<
rowIndex={rowIndex}
setCellProps={setCellProps}
timelineId={timelineId}
ecsData={ecsData}
rowRenderers={rowRenderers}
browserFields={browserFields}
/>
);
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ const Link = ({ children, url }: { children: React.ReactNode; url: string }) =>
</EuiLink>
);

export const eventRendererNames: { [key in RowRendererId]: string } = {
[RowRendererId.alerts]: i18n.ALERTS_NAME,
[RowRendererId.auditd]: i18n.AUDITD_NAME,
[RowRendererId.auditd_file]: i18n.AUDITD_FILE_NAME,
[RowRendererId.library]: i18n.LIBRARY_NAME,
[RowRendererId.system_security_event]: i18n.AUTHENTICATION_NAME,
[RowRendererId.system_dns]: i18n.DNS_NAME,
[RowRendererId.netflow]: i18n.FLOW_NAME,
[RowRendererId.system]: i18n.SYSTEM_NAME,
[RowRendererId.system_endgame_process]: i18n.PROCESS,
[RowRendererId.registry]: i18n.REGISTRY_NAME,
[RowRendererId.system_fim]: i18n.FIM_NAME,
[RowRendererId.system_file]: i18n.FILE_NAME,
[RowRendererId.system_socket]: i18n.SOCKET_NAME,
[RowRendererId.suricata]: 'Suricata',
[RowRendererId.threat_match]: i18n.THREAT_MATCH_NAME,
[RowRendererId.zeek]: i18n.ZEEK_NAME,
[RowRendererId.plain]: '',
};

export interface RowRendererOption {
id: RowRendererId;
name: string;
Expand All @@ -51,14 +71,14 @@ export interface RowRendererOption {
export const renderers: RowRendererOption[] = [
{
id: RowRendererId.alerts,
name: i18n.ALERTS_NAME,
name: eventRendererNames[RowRendererId.alerts],
description: i18n.ALERTS_DESCRIPTION,
example: AlertsExample,
searchableDescription: i18n.ALERTS_DESCRIPTION,
},
{
id: RowRendererId.auditd,
name: i18n.AUDITD_NAME,
name: eventRendererNames[RowRendererId.auditd],
description: (
<span>
<Link url="https://www.elastic.co/guide/en/beats/auditbeat/current/auditbeat-module-auditd.html">
Expand All @@ -72,7 +92,7 @@ export const renderers: RowRendererOption[] = [
},
{
id: RowRendererId.auditd_file,
name: i18n.AUDITD_FILE_NAME,
name: eventRendererNames[RowRendererId.auditd_file],
description: (
<span>
<Link url="https://www.elastic.co/guide/en/beats/auditbeat/current/auditbeat-module-auditd.html">
Expand All @@ -86,14 +106,14 @@ export const renderers: RowRendererOption[] = [
},
{
id: RowRendererId.library,
name: i18n.LIBRARY_NAME,
name: eventRendererNames[RowRendererId.library],
description: i18n.LIBRARY_DESCRIPTION,
example: LibraryExample,
searchableDescription: i18n.LIBRARY_DESCRIPTION,
},
{
id: RowRendererId.system_security_event,
name: i18n.AUTHENTICATION_NAME,
name: eventRendererNames[RowRendererId.system_security_event],
description: (
<div>
<p>{i18n.AUTHENTICATION_DESCRIPTION_PART1}</p>
Expand All @@ -106,14 +126,14 @@ export const renderers: RowRendererOption[] = [
},
{
id: RowRendererId.system_dns,
name: i18n.DNS_NAME,
name: eventRendererNames[RowRendererId.system_dns],
description: i18n.DNS_DESCRIPTION_PART1,
example: SystemDnsExample,
searchableDescription: i18n.DNS_DESCRIPTION_PART1,
},
{
id: RowRendererId.netflow,
name: i18n.FLOW_NAME,
name: eventRendererNames[RowRendererId.netflow],
description: (
<div>
<p>{i18n.FLOW_DESCRIPTION_PART1}</p>
Expand All @@ -126,7 +146,7 @@ export const renderers: RowRendererOption[] = [
},
{
id: RowRendererId.system,
name: i18n.SYSTEM_NAME,
name: eventRendererNames[RowRendererId.system],
description: (
<div>
<p>
Expand All @@ -145,7 +165,7 @@ export const renderers: RowRendererOption[] = [
},
{
id: RowRendererId.system_endgame_process,
name: i18n.PROCESS,
name: eventRendererNames[RowRendererId.system_endgame_process],
description: (
<div>
<p>{i18n.PROCESS_DESCRIPTION_PART1}</p>
Expand All @@ -158,28 +178,28 @@ export const renderers: RowRendererOption[] = [
},
{
id: RowRendererId.registry,
name: i18n.REGISTRY_NAME,
name: eventRendererNames[RowRendererId.registry],
description: i18n.REGISTRY_DESCRIPTION,
example: RegistryExample,
searchableDescription: i18n.REGISTRY_DESCRIPTION,
},
{
id: RowRendererId.system_fim,
name: i18n.FIM_NAME,
name: eventRendererNames[RowRendererId.system_fim],
description: i18n.FIM_DESCRIPTION_PART1,
example: SystemFimExample,
searchableDescription: i18n.FIM_DESCRIPTION_PART1,
},
{
id: RowRendererId.system_file,
name: i18n.FILE_NAME,
name: eventRendererNames[RowRendererId.system_file],
description: i18n.FILE_DESCRIPTION_PART1,
example: SystemFileExample,
searchableDescription: i18n.FILE_DESCRIPTION_PART1,
},
{
id: RowRendererId.system_socket,
name: i18n.SOCKET_NAME,
name: eventRendererNames[RowRendererId.system_socket],
description: (
<div>
<p>{i18n.SOCKET_DESCRIPTION_PART1}</p>
Expand All @@ -192,7 +212,7 @@ export const renderers: RowRendererOption[] = [
},
{
id: RowRendererId.suricata,
name: 'Suricata',
name: eventRendererNames[RowRendererId.suricata],
description: (
<p>
{i18n.SURICATA_DESCRIPTION_PART1}{' '}
Expand All @@ -207,14 +227,14 @@ export const renderers: RowRendererOption[] = [
},
{
id: RowRendererId.threat_match,
name: i18n.THREAT_MATCH_NAME,
name: eventRendererNames[RowRendererId.threat_match],
description: i18n.THREAT_MATCH_DESCRIPTION,
example: ThreatMatchExample,
searchableDescription: `${i18n.THREAT_MATCH_NAME} ${i18n.THREAT_MATCH_DESCRIPTION}`,
},
{
id: RowRendererId.zeek,
name: i18n.ZEEK_NAME,
name: eventRendererNames[RowRendererId.zeek],
description: (
<p>
{i18n.ZEEK_DESCRIPTION_PART1}{' '}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
*/

import type React from 'react';
import { ColumnHeaderOptions } from '../../../../../../common';

import { BrowserFields, ColumnHeaderOptions, RowRenderer } from '../../../../../../common';
import { Ecs } from '../../../../../../common/ecs';
import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline';

export interface ColumnRenderer {
Expand All @@ -29,5 +31,8 @@ export interface ColumnRenderer {
truncate?: boolean;
values: string[] | null | undefined;
linkValues?: string[] | null | undefined;
ecsData?: Ecs;
rowRenderers?: RowRenderer[];
browserFields?: BrowserFields;
}) => React.ReactNode;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export const EVENT_URL_FIELD_NAME = 'event.url';
export const SIGNAL_RULE_NAME_FIELD_NAME = 'signal.rule.name';
export const SIGNAL_STATUS_FIELD_NAME = 'signal.status';
export const AGENT_STATUS_FIELD_NAME = 'agent.status';
export const REASON_FIELD_NAME = 'signal.reason';
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { unknownColumnRenderer } from './unknown_column_renderer';
import { zeekRowRenderer } from './zeek/zeek_row_renderer';
import { systemRowRenderers } from './system/generic_row_renderer';
import { threatMatchRowRenderer } from './cti/threat_match_row_renderer';
import { reasonColumnRenderer } from './reason_column_renderer';

// The row renderers are order dependent and will return the first renderer
// which returns true from its isInstance call. The bottom renderers which
Expand All @@ -34,6 +35,7 @@ export const defaultRowRenderers: RowRenderer[] = [
];

export const columnRenderers: ColumnRenderer[] = [
reasonColumnRenderer,
plainColumnRenderer,
emptyColumnRenderer,
unknownColumnRenderer,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* 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 { mockTimelineData } from '../../../../../common/mock';
import { defaultColumnHeaderType } from '../column_headers/default_headers';
import { REASON_FIELD_NAME } from './constants';
import { reasonColumnRenderer } from './reason_column_renderer';
import { plainColumnRenderer } from './plain_column_renderer';

import {
BrowserFields,
ColumnHeaderOptions,
RowRenderer,
RowRendererId,
} from '../../../../../../common';
import { fireEvent, render } from '@testing-library/react';
import { TestProviders } from '../../../../../../../timelines/public/mock';
import { useDraggableKeyboardWrapper as mockUseDraggableKeyboardWrapper } from '../../../../../../../timelines/public/components';
import { cloneDeep } from 'lodash';
jest.mock('./plain_column_renderer');

jest.mock('../../../../../common/lib/kibana', () => {
const originalModule = jest.requireActual('../../../../../common/lib/kibana');
return {
...originalModule,
useKibana: () => ({
services: {
timelines: {
getUseDraggableKeyboardWrapper: () => mockUseDraggableKeyboardWrapper,
},
},
}),
};
});

jest.mock('../../../../../common/components/link_to', () => {
const original = jest.requireActual('../../../../../common/components/link_to');
return {
...original,
useFormatUrl: () => ({
formatUrl: () => '',
}),
};
});

const invalidEcs = cloneDeep(mockTimelineData[0].ecs);
const validEcs = cloneDeep(mockTimelineData[28].ecs);

const field: ColumnHeaderOptions = {
id: 'test-field-id',
columnHeaderType: defaultColumnHeaderType,
};

const rowRenderers: RowRenderer[] = [
{
id: RowRendererId.alerts,
isInstance: (ecs) => ecs === validEcs,
// eslint-disable-next-line react/display-name
renderRow: () => <span data-test-subj="test-row-render" />,
},
];
const browserFields: BrowserFields = {};

const defaultProps = {
columnName: REASON_FIELD_NAME,
eventId: 'test-event-id',
field,
timelineId: 'test-timeline-id',
values: ['test-value'],
};

describe('reasonColumnRenderer', () => {
beforeEach(() => {
jest.resetAllMocks();
});

describe('isIntance', () => {
it('returns true when columnName is `signal.reason`', () => {
expect(reasonColumnRenderer.isInstance(REASON_FIELD_NAME, [])).toBeTruthy();
});
});

describe('renderColumn', () => {
it('calls `plainColumnRenderer.renderColumn` when ecsData, rowRenderers or browserFields is empty', () => {
reasonColumnRenderer.renderColumn(defaultProps);

expect(plainColumnRenderer.renderColumn).toBeCalledTimes(1);
});

it("doesn't call `plainColumnRenderer.renderColumn` when ecsData, rowRenderers or browserFields fields are not empty", () => {
reasonColumnRenderer.renderColumn({
...defaultProps,
ecsData: invalidEcs,
rowRenderers,
browserFields,
});

expect(plainColumnRenderer.renderColumn).toBeCalledTimes(0);
});

it("doesn't render popover button when getRowRenderer doesn't find a rowRenderer", () => {
const renderedColumn = reasonColumnRenderer.renderColumn({
...defaultProps,
ecsData: invalidEcs,
rowRenderers,
browserFields,
});

const wrapper = render(<TestProviders>{renderedColumn}</TestProviders>);

expect(wrapper.queryByTestId('reason-cell-button')).not.toBeInTheDocument();
});

it('render popover button when getRowRenderer finds a rowRenderer', () => {
const renderedColumn = reasonColumnRenderer.renderColumn({
...defaultProps,
ecsData: validEcs,
rowRenderers,
browserFields,
});

const wrapper = render(<TestProviders>{renderedColumn}</TestProviders>);

expect(wrapper.queryByTestId('reason-cell-button')).toBeInTheDocument();
});

it('render rowRender inside a popover when reson field button is clicked', () => {
const renderedColumn = reasonColumnRenderer.renderColumn({
...defaultProps,
ecsData: validEcs,
rowRenderers,
browserFields,
});

const wrapper = render(<TestProviders>{renderedColumn}</TestProviders>);

fireEvent.click(wrapper.getByTestId('reason-cell-button'));

expect(wrapper.queryByTestId('test-row-render')).toBeInTheDocument();
});
});
});
Loading

0 comments on commit ccb3a97

Please sign in to comment.