[Security Solution] Make rule detail link work for both signal.rule.name and kibana.alert.rule.name (#122437)

* Make rule detail link work for both signal.rule.name and kibana.alert.rule.name

* Remove failing test

* Remove incorrect comment about possible bug

* PR feedback

* More cleanup/feedback

* Memoize hook usage
This commit is contained in:
Kevin Qualters 2022-01-12 21:52:15 -05:00 committed by GitHub
parent a6d21cc715
commit 6c72063531
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 323 additions and 202 deletions

View file

@ -53,7 +53,6 @@ describe('default cell actions', () => {
});
expect(columnsWithCellActions[0]?.cellActions?.length).toEqual(5);
expect(columnsWithCellActions[0]?.cellActions![4]).toEqual(EmptyComponent);
});
const columnHeadersToTest = COLUMNS_WITH_LINKS.map((c) => [

View file

@ -9,10 +9,7 @@ import { EuiDataGridColumnCellActionProps } from '@elastic/eui';
import { head, getOr, get, isEmpty } from 'lodash/fp';
import React, { useMemo } from 'react';
import type {
BrowserFields,
TimelineNonEcsData,
} from '../../../../../timelines/common/search_strategy';
import type { TimelineNonEcsData } from '../../../../../timelines/common/search_strategy';
import {
ColumnHeaderOptions,
DataProvider,
@ -20,13 +17,14 @@ import {
} from '../../../../../timelines/common/types';
import { getPageRowIndex } from '../../../../../timelines/public';
import { Ecs } from '../../../../common/ecs';
import { getMappedNonEcsValue } from '../../../timelines/components/timeline/body/data_driven_columns';
import { useGetMappedNonEcsValue } from '../../../timelines/components/timeline/body/data_driven_columns';
import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field';
import { parseValue } from '../../../timelines/components/timeline/body/renderers/parse_value';
import { IS_OPERATOR } from '../../../timelines/components/timeline/data_providers/data_provider';
import { escapeDataProviderId } from '../../components/drag_and_drop/helpers';
import { useKibana } from '../kibana';
import { getLink } from './helpers';
import { getLinkColumnDefinition } from './helpers';
import { getField, getFieldKey } from '../../../helpers';
/** a noop required by the filter in / out buttons */
const onFilterAdded = () => {};
@ -45,139 +43,167 @@ const useKibanaServices = () => {
export const EmptyComponent = () => <></>;
const cellActionLink = [
({
browserFields,
data,
ecsData,
header,
timelineId,
pageSize,
}: {
browserFields: BrowserFields;
data: TimelineNonEcsData[][];
ecsData: Ecs[];
header?: ColumnHeaderOptions;
timelineId: string;
pageSize: number;
}) => {
return getLink(header?.id, header?.type, header?.linkField)
? ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => {
const pageRowIndex = getPageRowIndex(rowIndex, pageSize);
const ecs = pageRowIndex < ecsData.length ? ecsData[pageRowIndex] : null;
const link = getLink(columnId, header?.type, header?.linkField);
const linkField = header?.linkField ? header?.linkField : link?.linkField;
const linkValues = header && getOr([], linkField ?? '', ecs);
const eventId = header && get('_id' ?? '', ecs);
if (pageRowIndex >= data.length) {
// data grid expects each cell action always return an element, it crashes if returns null
return <></>;
}
const useFormattedFieldProps = ({
rowIndex,
pageSize,
ecsData,
columnId,
header,
data,
}: {
rowIndex: number;
data: TimelineNonEcsData[][];
ecsData: Ecs[];
header?: ColumnHeaderOptions;
columnId: string;
pageSize: number;
}) => {
const pageRowIndex = getPageRowIndex(rowIndex, pageSize);
const ecs = ecsData[pageRowIndex];
const link = getLinkColumnDefinition(columnId, header?.type, header?.linkField);
const linkField = header?.linkField ? header?.linkField : link?.linkField;
const linkValues = header && getOr([], linkField ?? '', ecs);
const eventId = (header && get('_id' ?? '', ecs)) || '';
const rowData = useMemo(() => {
return {
data: data[pageRowIndex],
fieldName: columnId,
};
}, [pageRowIndex, columnId, data]);
const values = getMappedNonEcsValue({
data: data[pageRowIndex],
fieldName: columnId,
});
const value = parseValue(head(values));
return link && eventId && values && !isEmpty(value) ? (
<FormattedFieldValue
Component={Component}
contextId={`expanded-value-${columnId}-row-${pageRowIndex}-${timelineId}`}
eventId={eventId}
fieldFormat={header?.format || ''}
fieldName={columnId}
fieldType={header?.type || ''}
isButton={true}
isDraggable={false}
value={value}
truncate={false}
title={values.length > 1 ? `${link?.label}: ${value}` : link?.label}
linkValue={head(linkValues)}
onClick={closePopover}
/>
) : (
// data grid expects each cell action always return an element, it crashes if returns null
<></>
);
}
: EmptyComponent;
},
];
const values = useGetMappedNonEcsValue(rowData);
const value = parseValue(head(values));
const title = values && values.length > 1 ? `${link?.label}: ${value}` : link?.label;
// if linkField is defined but link values is empty, it's possible we are trying to look for a column definition for an old event set
if (linkField !== undefined && linkValues.length === 0 && values !== undefined) {
const normalizedLinkValue = getField(ecs, linkField);
const normalizedLinkField = getFieldKey(ecs, linkField);
const normalizedColumnId = getFieldKey(ecs, columnId);
const normalizedLink = getLinkColumnDefinition(
normalizedColumnId,
header?.type,
normalizedLinkField
);
return {
pageRowIndex,
link: normalizedLink,
eventId,
fieldFormat: header?.format || '',
fieldName: normalizedColumnId,
fieldType: header?.type || '',
value: parseValue(head(normalizedColumnId)),
values,
title,
linkValue: head<string>(normalizedLinkValue),
};
} else {
return {
pageRowIndex,
link,
eventId,
fieldFormat: header?.format || '',
fieldName: columnId,
fieldType: header?.type || '',
value,
values,
title,
linkValue: head<string>(linkValues),
};
}
};
export const cellActions: TGridCellAction[] = [
({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) =>
({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => {
function FilterFor({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) {
const { timelines, filterManager } = useKibanaServices();
const pageRowIndex = getPageRowIndex(rowIndex, pageSize);
const rowData = useMemo(() => {
return {
data: data[pageRowIndex],
fieldName: columnId,
};
}, [pageRowIndex, columnId]);
const value = useGetMappedNonEcsValue(rowData);
const filterForButton = useMemo(
() => timelines.getHoverActions().getFilterForValueButton,
[timelines]
);
const filterForProps = useMemo(() => {
return {
Component,
field: columnId,
filterManager,
onFilterAdded,
ownFocus: false,
showTooltip: false,
value,
};
}, [Component, columnId, filterManager, value]);
if (pageRowIndex >= data.length) {
// data grid expects each cell action always return an element, it crashes if returns null
return <></>;
}
const value = getMappedNonEcsValue({
data: data[pageRowIndex],
fieldName: columnId,
});
return (
<>
{timelines.getHoverActions().getFilterForValueButton({
Component,
field: columnId,
filterManager,
onFilterAdded,
ownFocus: false,
showTooltip: false,
value,
})}
</>
);
return <>{filterForButton(filterForProps)}</>;
},
({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) =>
({ rowIndex, columnId, Component }) => {
function FilterOut({ rowIndex, columnId, Component }) {
const { timelines, filterManager } = useKibanaServices();
const pageRowIndex = getPageRowIndex(rowIndex, pageSize);
const rowData = useMemo(() => {
return {
data: data[pageRowIndex],
fieldName: columnId,
};
}, [pageRowIndex, columnId]);
const value = useGetMappedNonEcsValue(rowData);
const filterOutButton = useMemo(
() => timelines.getHoverActions().getFilterOutValueButton,
[timelines]
);
const filterOutProps = useMemo(() => {
return {
Component,
field: columnId,
filterManager,
onFilterAdded,
ownFocus: false,
showTooltip: false,
value,
};
}, [Component, columnId, filterManager, value]);
if (pageRowIndex >= data.length) {
// data grid expects each cell action always return an element, it crashes if returns null
return <></>;
}
const value = getMappedNonEcsValue({
data: data[pageRowIndex],
fieldName: columnId,
});
return (
<>
{timelines.getHoverActions().getFilterOutValueButton({
Component,
field: columnId,
filterManager,
onFilterAdded,
ownFocus: false,
showTooltip: false,
value,
})}
</>
);
return <>{filterOutButton(filterOutProps)}</>;
},
({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) =>
({ rowIndex, columnId, Component }) => {
function AddToTimeline({ rowIndex, columnId, Component }) {
const { timelines } = useKibanaServices();
const pageRowIndex = getPageRowIndex(rowIndex, pageSize);
if (pageRowIndex >= data.length) {
// data grid expects each cell action always return an element, it crashes if returns null
return <></>;
}
const rowData = useMemo(() => {
return {
data: data[pageRowIndex],
fieldName: columnId,
};
}, [pageRowIndex, columnId]);
const value = getMappedNonEcsValue({
data: data[pageRowIndex],
fieldName: columnId,
});
const value = useGetMappedNonEcsValue(rowData);
const addToTimelineButton = useMemo(
() => timelines.getHoverActions().getAddToTimelineButton,
[timelines]
);
const dataProvider: DataProvider[] = useMemo(
() =>
@ -196,48 +222,124 @@ export const cellActions: TGridCellAction[] = [
})) ?? [],
[columnId, rowIndex, value]
);
return (
<>
{timelines.getHoverActions().getAddToTimelineButton({
Component,
dataProvider,
field: columnId,
ownFocus: false,
showTooltip: false,
})}
</>
);
},
({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) =>
({ rowIndex, columnId, Component }) => {
const { timelines } = useKibanaServices();
const pageRowIndex = getPageRowIndex(rowIndex, pageSize);
const addToTimelineProps = useMemo(() => {
return {
Component,
dataProvider,
field: columnId,
ownFocus: false,
showTooltip: false,
};
}, [Component, columnId, dataProvider]);
if (pageRowIndex >= data.length) {
// data grid expects each cell action always return an element, it crashes if returns null
return <></>;
}
const value = getMappedNonEcsValue({
data: data[pageRowIndex],
fieldName: columnId,
});
return (
<>
{timelines.getHoverActions().getCopyButton({
Component,
field: columnId,
isHoverAction: false,
ownFocus: false,
showTooltip: false,
value,
})}
</>
);
return <>{addToTimelineButton(addToTimelineProps)}</>;
},
({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) =>
function CopyButton({ rowIndex, columnId, Component }) {
const { timelines } = useKibanaServices();
const pageRowIndex = getPageRowIndex(rowIndex, pageSize);
const copyButton = useMemo(() => timelines.getHoverActions().getCopyButton, [timelines]);
const rowData = useMemo(() => {
return {
data: data[pageRowIndex],
fieldName: columnId,
};
}, [pageRowIndex, columnId]);
const value = useGetMappedNonEcsValue(rowData);
const copyButtonProps = useMemo(() => {
return {
Component,
field: columnId,
isHoverAction: false,
ownFocus: false,
showTooltip: false,
value,
};
}, [Component, columnId, value]);
if (pageRowIndex >= data.length) {
// data grid expects each cell action always return an element, it crashes if returns null
return <></>;
}
return <>{copyButton(copyButtonProps)}</>;
},
({
data,
ecsData,
header,
timelineId,
pageSize,
}: {
data: TimelineNonEcsData[][];
ecsData: Ecs[];
header?: ColumnHeaderOptions;
timelineId: string;
pageSize: number;
}) => {
if (header !== undefined) {
return function FieldValue({
rowIndex,
columnId,
Component,
closePopover,
}: EuiDataGridColumnCellActionProps) {
const {
pageRowIndex,
link,
eventId,
value,
values,
title,
fieldName,
fieldFormat,
fieldType,
linkValue,
} = useFormattedFieldProps({ rowIndex, pageSize, ecsData, columnId, header, data });
const showEmpty = useMemo(() => {
const hasLink = link !== undefined && values && !isEmpty(value);
if (pageRowIndex >= data.length) {
return true;
} else {
return hasLink !== true;
}
}, [link, pageRowIndex, value, values]);
return showEmpty === false ? (
<FormattedFieldValue
Component={Component}
contextId={`expanded-value-${columnId}-row-${pageRowIndex}-${timelineId}`}
eventId={eventId}
fieldFormat={fieldFormat}
fieldName={fieldName}
fieldType={fieldType}
isButton={true}
isDraggable={false}
value={value}
truncate={false}
title={title}
linkValue={linkValue}
onClick={closePopover}
/>
) : (
// data grid expects each cell action always return an element, it crashes if returns null
<></>
);
};
} else {
return EmptyComponent;
}
},
];
/** the default actions shown in `EuiDataGrid` cells */
export const defaultCellActions = [...cellActions, ...cellActionLink];
export const defaultCellActions = [...cellActions];

View file

@ -35,6 +35,11 @@ export const COLUMNS_WITH_LINKS = [
{
columnId: SIGNAL_RULE_NAME_FIELD_NAME,
label: i18n.VIEW_RULE_DETAILS,
linkField: 'kibana.alert.rule.uuid',
},
{
columnId: 'signal.rule.name',
label: i18n.VIEW_RULE_DETAILS,
linkField: 'signal.rule.id',
},
...PORT_NAMES.map((p) => ({
@ -59,9 +64,22 @@ export const COLUMNS_WITH_LINKS = [
},
];
export const getLink = (cId?: string, fieldType?: string, linkField?: string) =>
COLUMNS_WITH_LINKS.find(
(c) =>
(cId && c.columnId === cId) ||
(c.fieldType && fieldType === c.fieldType && (linkField != null || c.linkField !== undefined))
);
export const getLinkColumnDefinition = (
columnIdToFind: string,
fieldType?: string,
linkField?: string
) => {
return COLUMNS_WITH_LINKS.find((column) => {
if (column.columnId === columnIdToFind) {
return true;
} else if (
column.fieldType &&
fieldType === column.fieldType &&
(linkField !== undefined || column.linkField !== undefined)
) {
return true;
} else {
return false;
}
});
};

View file

@ -13,7 +13,7 @@ import { ALERT_DURATION, ALERT_REASON, ALERT_SEVERITY, ALERT_STATUS } from '@kbn
import { TruncatableText } from '../../../../common/components/truncatable_text';
import { Severity } from '../../../components/severity';
import { getMappedNonEcsValue } from '../../../../timelines/components/timeline/body/data_driven_columns';
import { useGetMappedNonEcsValue } from '../../../../timelines/components/timeline/body/data_driven_columns';
import { CellValueElementProps } from '../../../../timelines/components/timeline/cell_rendering';
import { DefaultCellRenderer } from '../../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
import { Status } from '../../../components/status';
@ -43,7 +43,7 @@ export const RenderCellValue: React.FC<
timelineId,
}) => {
const value =
getMappedNonEcsValue({
useGetMappedNonEcsValue({
data,
fieldName: columnId,
})?.reduce((x) => x[0]) ?? '';

View file

@ -12,7 +12,7 @@ import React from 'react';
import { DefaultDraggable } from '../../../../common/components/draggables';
import { TruncatableText } from '../../../../common/components/truncatable_text';
import { Severity } from '../../../components/severity';
import { getMappedNonEcsValue } from '../../../../timelines/components/timeline/body/data_driven_columns';
import { useGetMappedNonEcsValue } from '../../../../timelines/components/timeline/body/data_driven_columns';
import { CellValueElementProps } from '../../../../timelines/components/timeline/cell_rendering';
import { DefaultCellRenderer } from '../../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
@ -40,7 +40,7 @@ export const RenderCellValue: React.FC<
timelineId,
}) => {
const value =
getMappedNonEcsValue({
useGetMappedNonEcsValue({
data,
fieldName: columnId,
})?.reduce((x) => x[0]) ?? '';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { ALERT_RULE_UUID, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils';
import { ALERT_RULE_UUID, ALERT_RULE_NAME, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils';
import { has, get, isEmpty } from 'lodash/fp';
import React from 'react';
import { matchPath, RouteProps, Redirect } from 'react-router-dom';
@ -209,6 +209,7 @@ RedirectRoute.displayName = 'RedirectRoute';
const siemSignalsFieldMappings: Record<string, string> = {
[ALERT_RULE_UUID]: 'signal.rule.id',
[ALERT_RULE_NAME]: 'signal.rule.name',
[`${ALERT_RULE_PARAMETERS}.filters`]: 'signal.rule.filters',
[`${ALERT_RULE_PARAMETERS}.language`]: 'signal.rule.language',
[`${ALERT_RULE_PARAMETERS}.query`]: 'signal.rule.query',
@ -216,6 +217,7 @@ const siemSignalsFieldMappings: Record<string, string> = {
const alertFieldMappings: Record<string, string> = {
'signal.rule.id': ALERT_RULE_UUID,
'signal.rule.name': ALERT_RULE_NAME,
'signal.rule.filters': `${ALERT_RULE_PARAMETERS}.filters`,
'signal.rule.language': `${ALERT_RULE_PARAMETERS}.language`,
'signal.rule.query': `${ALERT_RULE_PARAMETERS}.query`,

View file

@ -448,3 +448,13 @@ export const getMappedNonEcsValue = ({
}
return undefined;
};
export const useGetMappedNonEcsValue = ({
data,
fieldName,
}: {
data: TimelineNonEcsData[];
fieldName: string;
}): string[] | undefined => {
return useMemo(() => getMappedNonEcsValue({ data, fieldName }), [data, fieldName]);
};

View file

@ -18,7 +18,7 @@ import {
} from '../../../../../../common/types/timeline';
import { StatefulCell } from './stateful_cell';
import { getMappedNonEcsValue } from '.';
import { useGetMappedNonEcsValue } from '.';
/**
* This (test) component implement's `EuiDataGrid`'s `renderCellValue` interface,
@ -30,14 +30,13 @@ import { getMappedNonEcsValue } from '.';
* https://codesandbox.io/s/zhxmo
*/
const RenderCellValue: React.FC<CellValueElementProps> = ({ columnId, data, setCellProps }) => {
const value = useGetMappedNonEcsValue({
data,
fieldName: columnId,
});
useEffect(() => {
// branching logic that conditionally renders a specific cell green:
if (columnId === defaultHeaders[0].id) {
const value = getMappedNonEcsValue({
data,
fieldName: columnId,
});
if (value?.length) {
setCellProps({
style: {
@ -46,16 +45,9 @@ const RenderCellValue: React.FC<CellValueElementProps> = ({ columnId, data, setC
});
}
}
}, [columnId, data, setCellProps]);
}, [columnId, data, setCellProps, value]);
return (
<div data-test-subj="renderCellValue">
{getMappedNonEcsValue({
data,
fieldName: columnId,
})}
</div>
);
return <div data-test-subj="renderCellValue">{value}</div>;
};
describe('StatefulCell', () => {

View file

@ -39,7 +39,7 @@ import { getRowRenderer } from '../renderers/get_row_renderer';
import { StatefulRowRenderer } from './stateful_row_renderer';
import { NOTES_BUTTON_CLASS_NAME } from '../../properties/helpers';
import { timelineDefaults } from '../../../../store/timeline/defaults';
import { getMappedNonEcsValue } from '../data_driven_columns';
import { useGetMappedNonEcsValue } from '../data_driven_columns';
import { StatefulEventContext } from '../../../../../../../timelines/public';
interface Props {
@ -115,21 +115,23 @@ const StatefulEventComponent: React.FC<Props> = ({
const expandedDetail = useDeepEqualSelector(
(state) => (getTimeline(state, timelineId) ?? timelineDefaults).expandedDetail ?? {}
);
const hostName = useMemo(() => {
const hostNameArr = getMappedNonEcsValue({ data: event?.data, fieldName: 'host.name' });
return hostNameArr && hostNameArr.length > 0 ? hostNameArr[0] : null;
}, [event?.data]);
const hostNameArr = useGetMappedNonEcsValue({ data: event?.data, fieldName: 'host.name' });
const hostName = useMemo(() => {
return hostNameArr && hostNameArr.length > 0 ? hostNameArr[0] : null;
}, [hostNameArr]);
const hostIpList = useGetMappedNonEcsValue({ data: event?.data, fieldName: 'host.ip' });
const sourceIpList = useGetMappedNonEcsValue({ data: event?.data, fieldName: 'source.ip' });
const destinationIpList = useGetMappedNonEcsValue({
data: event?.data,
fieldName: 'destination.ip',
});
const hostIPAddresses = useMemo(() => {
const hostIpList = getMappedNonEcsValue({ data: event?.data, fieldName: 'host.ip' }) ?? [];
const sourceIpList = getMappedNonEcsValue({ data: event?.data, fieldName: 'source.ip' }) ?? [];
const destinationIpList =
getMappedNonEcsValue({
data: event?.data,
fieldName: 'destination.ip',
}) ?? [];
return new Set([...hostIpList, ...sourceIpList, ...destinationIpList]);
}, [event?.data]);
const hostIps = hostIpList ?? [];
const sourceIps = sourceIpList ?? [];
const destinationIps = destinationIpList ?? [];
return new Set([...hostIps, ...sourceIps, ...destinationIps]);
}, [destinationIpList, sourceIpList, hostIpList]);
const activeTab = tabType ?? TimelineTabs.query;
const activeExpandedDetail = expandedDetail[activeTab];

View file

@ -221,7 +221,6 @@ const FormattedFieldValueComponent: React.FC<{
Component,
eventId,
fieldName,
linkValue,
isDraggable,
truncate,
title,

View file

@ -250,7 +250,6 @@ export const renderUrl = ({
eventId,
fieldName,
isDraggable,
linkValue,
truncate,
title,
value,
@ -261,7 +260,6 @@ export const renderUrl = ({
eventId: string;
fieldName: string;
isDraggable: boolean;
linkValue: string | null | undefined;
truncate?: boolean;
title?: string;
value: string | number | null | undefined;

View file

@ -5,14 +5,14 @@
* 2.0.
*/
import React from 'react';
import React, { useMemo } from 'react';
import { getMappedNonEcsValue } from '../body/data_driven_columns';
import { useGetMappedNonEcsValue } from '../body/data_driven_columns';
import { columnRenderers } from '../body/renderers';
import { getColumnRenderer } from '../body/renderers/get_column_renderer';
import { CellValueElementProps } from '.';
import { getLink } from '../../../../common/lib/cell_actions/helpers';
import { getLinkColumnDefinition } from '../../../../common/lib/cell_actions/helpers';
import { FIELDS_WITHOUT_CELL_ACTIONS } from '../../../../common/lib/cell_actions/constants';
import {
ExpandedCellValueActions,
@ -39,7 +39,10 @@ export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
timelineId,
truncate,
}) => {
const values = getMappedNonEcsValue({
const asPlainText = useMemo(() => {
return getLinkColumnDefinition(header.id, header.type) !== undefined && !isTimeline;
}, [header.id, header.type, isTimeline]);
const values = useGetMappedNonEcsValue({
data,
fieldName: header.id,
});
@ -50,7 +53,7 @@ export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
<>
<StyledContent className={styledContentClassName} $isDetails={isDetails}>
{getColumnRenderer(header.id, columnRenderers, data).renderColumn({
asPlainText: !!getLink(header.id, header.type) && !isTimeline, // we want to render value with links as plain text but keep other formatters like badge.
asPlainText, // we want to render value with links as plain text but keep other formatters like badge.
browserFields,
columnName: header.id,
ecsData,
@ -62,10 +65,7 @@ export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
rowRenderers,
timelineId,
truncate,
values: getMappedNonEcsValue({
data,
fieldName: header.id,
}),
values,
})}
</StyledContent>
{isDetails && browserFields && hasCellActions(header.id) && (

View file

@ -672,7 +672,6 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
pageSize,
timelineId: id,
});
return {
...header,
actions: {