mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[8.0] [Security Solution] Make rule detail link work for both signal.rule.name and kibana.alert.rule.name (#122437) (#122959)
* [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
(cherry picked from commit 6c72063531
)
# Conflicts:
# x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx
# x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.tsx
* Lint
* Fix lint for real
This commit is contained in:
parent
3580bd61e9
commit
9d547ec18a
13 changed files with 323 additions and 202 deletions
|
@ -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) => [
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
|
||||
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';
|
||||
|
@ -47,7 +47,7 @@ export const RenderCellValue: React.FC<EuiDataGridCellValueElementProps & CellVa
|
|||
timelineId,
|
||||
}) => {
|
||||
const value =
|
||||
getMappedNonEcsValue({
|
||||
useGetMappedNonEcsValue({
|
||||
data,
|
||||
fieldName: columnId,
|
||||
})?.reduce((x) => x[0]) ?? '';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -39,7 +39,7 @@ export const RenderCellValue: React.FC<EuiDataGridCellValueElementProps & CellVa
|
|||
timelineId,
|
||||
}) => {
|
||||
const value =
|
||||
getMappedNonEcsValue({
|
||||
useGetMappedNonEcsValue({
|
||||
data,
|
||||
fieldName: columnId,
|
||||
})?.reduce((x) => x[0]) ?? '';
|
||||
|
|
|
@ -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';
|
||||
|
@ -213,6 +213,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',
|
||||
|
@ -220,6 +221,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`,
|
||||
|
|
|
@ -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]);
|
||||
};
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -215,7 +215,6 @@ const FormattedFieldValueComponent: React.FC<{
|
|||
Component,
|
||||
eventId,
|
||||
fieldName,
|
||||
linkValue,
|
||||
isDraggable,
|
||||
truncate,
|
||||
title,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) && (
|
||||
|
|
|
@ -676,7 +676,6 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
pageSize,
|
||||
timelineId: id,
|
||||
});
|
||||
|
||||
return {
|
||||
...header,
|
||||
actions: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue