{i18n.AUTHENTICATION_DESCRIPTION_PART1}
@@ -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: ({i18n.FLOW_DESCRIPTION_PART1}
@@ -126,7 +146,7 @@ export const renderers: RowRendererOption[] = [ }, { id: RowRendererId.system, - name: i18n.SYSTEM_NAME, + name: eventRendererNames[RowRendererId.system], description: (@@ -145,7 +165,7 @@ export const renderers: RowRendererOption[] = [ }, { id: RowRendererId.system_endgame_process, - name: i18n.PROCESS, + name: eventRendererNames[RowRendererId.system_endgame_process], description: (
{i18n.PROCESS_DESCRIPTION_PART1}
@@ -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: ({i18n.SOCKET_DESCRIPTION_PART1}
@@ -192,7 +212,7 @@ export const renderers: RowRendererOption[] = [ }, { id: RowRendererId.suricata, - name: 'Suricata', + name: eventRendererNames[RowRendererId.suricata], description: ({i18n.SURICATA_DESCRIPTION_PART1}{' '} @@ -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: (
{i18n.ZEEK_DESCRIPTION_PART1}{' '}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts
index fc13680b81be2b..1e6f613999ece3 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts
@@ -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 {
@@ -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;
}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx
index aeb40bed26c8ea..3a7a43da2aedc6 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx
@@ -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';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts
index 911dcc8cd2e875..11c501f9426f40 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts
@@ -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
@@ -34,6 +35,7 @@ export const defaultRowRenderers: RowRenderer[] = [
];
export const columnRenderers: ColumnRenderer[] = [
+ reasonColumnRenderer,
plainColumnRenderer,
emptyColumnRenderer,
unknownColumnRenderer,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx
new file mode 100644
index 00000000000000..addb991af58d72
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx
@@ -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: () => ,
+ },
+];
+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(