mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Security Solution][Alerts] Fix suppression icon in rule name for preview and popover (#145587)
## Summary https://github.com/elastic/kibana/issues/145544 - Suppression icon should show up in rule name even when `kibana.alert.suppression.docs_count` column is not included in the table https://github.com/elastic/kibana/issues/145669 - Rule name cell popover formatting No issue - adds the rule name icon for rule preview table `props.data` is the fetched columns, `props.ecsData` always has the fields listed in `requiredFieldsForActions` so we can use `kibana.alert.suppression.docs_count` even when that column is missing.
This commit is contained in:
parent
6c2087cc51
commit
5582afe4fd
6 changed files with 24 additions and 282 deletions
|
@ -1,167 +0,0 @@
|
|||
/*
|
||||
* 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 { mount } from 'enzyme';
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
|
||||
import { mockBrowserFields } from '../../../../common/containers/source/mock';
|
||||
import { DragDropContextWrapper } from '../../../../common/components/drag_and_drop/drag_drop_context_wrapper';
|
||||
import { defaultHeaders, mockTimelineData, TestProviders } from '../../../../common/mock';
|
||||
import { PreviewTableCellRenderer } from './preview_table_cell_renderer';
|
||||
import { getColumnRenderer } from '../../../../timelines/components/timeline/body/renderers/get_column_renderer';
|
||||
import { DroppableWrapper } from '../../../../common/components/drag_and_drop/droppable_wrapper';
|
||||
import type { BrowserFields } from '@kbn/timelines-plugin/common/search_strategy';
|
||||
import type { Ecs } from '../../../../../common/ecs';
|
||||
import { columnRenderers } from '../../../../timelines/components/timeline/body/renderers';
|
||||
import { TimelineId } from '../../../../../common/types';
|
||||
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
jest.mock('../../../../timelines/components/timeline/body/renderers/get_column_renderer');
|
||||
|
||||
const getColumnRendererMock = getColumnRenderer as jest.Mock;
|
||||
const mockImplementation = {
|
||||
renderColumn: jest.fn(),
|
||||
};
|
||||
|
||||
describe('PreviewTableCellRenderer', () => {
|
||||
const columnId = '@timestamp';
|
||||
const eventId = '_id-123';
|
||||
const isExpandable = true;
|
||||
const isExpanded = true;
|
||||
const linkValues = ['foo', 'bar', '@baz'];
|
||||
const rowIndex = 3;
|
||||
const colIndex = 0;
|
||||
const setCellProps = jest.fn();
|
||||
const scopeId = TimelineId.test;
|
||||
const ecsData = {} as Ecs;
|
||||
const browserFields = {} as BrowserFields;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
getColumnRendererMock.mockImplementation(() => mockImplementation);
|
||||
});
|
||||
|
||||
test('it invokes `getColumnRenderer` with the expected arguments', () => {
|
||||
const data = cloneDeep(mockTimelineData[0].data);
|
||||
const header = cloneDeep(defaultHeaders[0]);
|
||||
const isDetails = true;
|
||||
|
||||
mount(
|
||||
<TestProviders>
|
||||
<DragDropContextWrapper browserFields={mockBrowserFields}>
|
||||
<DroppableWrapper droppableId="testing">
|
||||
<PreviewTableCellRenderer
|
||||
browserFields={browserFields}
|
||||
columnId={columnId}
|
||||
data={data}
|
||||
ecsData={ecsData}
|
||||
eventId={eventId}
|
||||
header={header}
|
||||
isDetails={isDetails}
|
||||
isDraggable={true}
|
||||
isExpandable={isExpandable}
|
||||
isExpanded={isExpanded}
|
||||
linkValues={linkValues}
|
||||
rowIndex={rowIndex}
|
||||
colIndex={colIndex}
|
||||
setCellProps={setCellProps}
|
||||
scopeId={scopeId}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContextWrapper>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(getColumnRenderer).toBeCalledWith(header.id, columnRenderers, data);
|
||||
});
|
||||
|
||||
test('if in tgrid expanded value, it invokes `renderColumn` with the expected arguments', () => {
|
||||
const data = cloneDeep(mockTimelineData[0].data);
|
||||
const header = cloneDeep(defaultHeaders[0]);
|
||||
const isDetails = true;
|
||||
const truncate = isDetails ? false : true;
|
||||
|
||||
mount(
|
||||
<TestProviders>
|
||||
<DragDropContextWrapper browserFields={mockBrowserFields}>
|
||||
<DroppableWrapper droppableId="testing">
|
||||
<PreviewTableCellRenderer
|
||||
browserFields={browserFields}
|
||||
columnId={columnId}
|
||||
data={data}
|
||||
ecsData={ecsData}
|
||||
eventId={eventId}
|
||||
header={header}
|
||||
isDetails={isDetails}
|
||||
isDraggable={true}
|
||||
isExpandable={isExpandable}
|
||||
isExpanded={isExpanded}
|
||||
linkValues={linkValues}
|
||||
rowIndex={rowIndex}
|
||||
colIndex={colIndex}
|
||||
setCellProps={setCellProps}
|
||||
scopeId={scopeId}
|
||||
truncate={truncate}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContextWrapper>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(mockImplementation.renderColumn).toBeCalledWith({
|
||||
asPlainText: false,
|
||||
columnName: header.id,
|
||||
ecsData,
|
||||
eventId,
|
||||
field: header,
|
||||
isDetails,
|
||||
isDraggable: true,
|
||||
linkValues,
|
||||
rowRenderers: undefined,
|
||||
scopeId,
|
||||
truncate,
|
||||
values: ['2018-11-05T19:03:25.937Z'],
|
||||
});
|
||||
});
|
||||
|
||||
test('if in tgrid expanded value, it does not render any actions', () => {
|
||||
const data = cloneDeep(mockTimelineData[0].data);
|
||||
const header = cloneDeep(defaultHeaders[1]);
|
||||
const isDetails = true;
|
||||
const id = 'event.severity';
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DragDropContextWrapper browserFields={mockBrowserFields}>
|
||||
<DroppableWrapper droppableId="testing">
|
||||
<PreviewTableCellRenderer
|
||||
browserFields={browserFields}
|
||||
columnId={id}
|
||||
ecsData={ecsData}
|
||||
data={data}
|
||||
eventId={eventId}
|
||||
header={header}
|
||||
isDetails={isDetails}
|
||||
isDraggable={true}
|
||||
isExpandable={isExpandable}
|
||||
isExpanded={isExpanded}
|
||||
linkValues={linkValues}
|
||||
rowIndex={rowIndex}
|
||||
colIndex={colIndex}
|
||||
setCellProps={setCellProps}
|
||||
scopeId={scopeId}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContextWrapper>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="data-grid-expanded-cell-value-actions"]').exists()
|
||||
).toBeFalsy();
|
||||
});
|
||||
});
|
|
@ -5,102 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import type React from 'react';
|
||||
import type { EuiDataGridCellValueElementProps } from '@elastic/eui';
|
||||
import type { CellValueElementProps } from '@kbn/timelines-plugin/common';
|
||||
import { StyledContent } from '../../../../common/lib/cell_actions/expanded_cell_value_actions';
|
||||
import { getLinkColumnDefinition } from '../../../../common/lib/cell_actions/helpers';
|
||||
import { useGetMappedNonEcsValue } from '../../../../timelines/components/timeline/body/data_driven_columns';
|
||||
import { columnRenderers } from '../../../../timelines/components/timeline/body/renderers';
|
||||
import { getColumnRenderer } from '../../../../timelines/components/timeline/body/renderers/get_column_renderer';
|
||||
import { RenderCellValue } from '../../../configurations/security_solution_detections';
|
||||
|
||||
export const PreviewRenderCellValue: React.FC<
|
||||
EuiDataGridCellValueElementProps & CellValueElementProps
|
||||
> = ({
|
||||
columnId,
|
||||
data,
|
||||
ecsData,
|
||||
eventId,
|
||||
globalFilters,
|
||||
header,
|
||||
isDetails,
|
||||
isDraggable,
|
||||
isExpandable,
|
||||
isExpanded,
|
||||
linkValues,
|
||||
rowIndex,
|
||||
colIndex,
|
||||
rowRenderers,
|
||||
setCellProps,
|
||||
scopeId,
|
||||
truncate,
|
||||
}) => (
|
||||
<PreviewTableCellRenderer
|
||||
columnId={columnId}
|
||||
data={data}
|
||||
ecsData={ecsData}
|
||||
eventId={eventId}
|
||||
globalFilters={globalFilters}
|
||||
header={header}
|
||||
isDetails={isDetails}
|
||||
isDraggable={isDraggable}
|
||||
isExpandable={isExpandable}
|
||||
isExpanded={isExpanded}
|
||||
linkValues={linkValues}
|
||||
rowIndex={rowIndex}
|
||||
colIndex={colIndex}
|
||||
rowRenderers={rowRenderers}
|
||||
setCellProps={setCellProps}
|
||||
scopeId={scopeId}
|
||||
truncate={truncate}
|
||||
/>
|
||||
);
|
||||
|
||||
export const PreviewTableCellRenderer: React.FC<CellValueElementProps> = ({
|
||||
data,
|
||||
ecsData,
|
||||
eventId,
|
||||
header,
|
||||
isDetails,
|
||||
isDraggable,
|
||||
isTimeline,
|
||||
linkValues,
|
||||
rowRenderers,
|
||||
scopeId,
|
||||
truncate,
|
||||
key,
|
||||
}) => {
|
||||
const asPlainText = useMemo(() => {
|
||||
return getLinkColumnDefinition(header.id, header.type, undefined) !== undefined && !isTimeline;
|
||||
}, [header.id, header.type, isTimeline]);
|
||||
|
||||
const values = useGetMappedNonEcsValue({
|
||||
data,
|
||||
fieldName: header.id,
|
||||
});
|
||||
const styledContentClassName = isDetails
|
||||
? 'eui-textBreakWord'
|
||||
: 'eui-displayInlineBlock eui-textTruncate';
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContent className={styledContentClassName} $isDetails={isDetails}>
|
||||
{getColumnRenderer(header.id, columnRenderers, data).renderColumn({
|
||||
asPlainText,
|
||||
columnName: header.id,
|
||||
ecsData,
|
||||
eventId,
|
||||
field: header,
|
||||
isDetails,
|
||||
isDraggable,
|
||||
linkValues,
|
||||
rowRenderers,
|
||||
scopeId,
|
||||
truncate,
|
||||
values,
|
||||
key,
|
||||
})}
|
||||
</StyledContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
> = (props) => RenderCellValue({ ...props, enableActions: false });
|
||||
|
|
|
@ -6,10 +6,8 @@
|
|||
*/
|
||||
|
||||
import type { EuiDataGridCellValueElementProps } from '@elastic/eui';
|
||||
import { EuiIcon, EuiToolTip } from '@elastic/eui';
|
||||
import { EuiIcon, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { find } from 'lodash/fp';
|
||||
import { GuidedOnboardingTourStep } from '../../../common/components/guided_onboarding_tour/tour_step';
|
||||
import { isDetectionsAlertsTable } from '../../../common/components/top_n/helpers';
|
||||
import {
|
||||
|
@ -26,10 +24,6 @@ import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell
|
|||
|
||||
import { SUPPRESSED_ALERT_TOOLTIP } from './translations';
|
||||
|
||||
const SuppressedAlertIconWrapper = styled.div`
|
||||
display: inline-flex;
|
||||
`;
|
||||
|
||||
/**
|
||||
* This implementation of `EuiDataGrid`'s `renderCellValue`
|
||||
* accepts `EuiDataGridCellValueElementProps`, plus `data`
|
||||
|
@ -48,7 +42,7 @@ export const RenderCellValue: React.FC<EuiDataGridCellValueElementProps & CellVa
|
|||
[columnId, props.isDetails, rowIndex, scopeId]
|
||||
);
|
||||
|
||||
const suppressionCount = find({ field: 'kibana.alert.suppression.docs_count' }, props.data);
|
||||
const suppressionCount = props.ecsData?.kibana?.alert.suppression?.docs_count;
|
||||
|
||||
const component = (
|
||||
<GuidedOnboardingTourStep
|
||||
|
@ -61,18 +55,19 @@ export const RenderCellValue: React.FC<EuiDataGridCellValueElementProps & CellVa
|
|||
);
|
||||
|
||||
return columnId === SIGNAL_RULE_NAME_FIELD_NAME &&
|
||||
suppressionCount?.value &&
|
||||
parseInt(suppressionCount.value[0], 10) > 0 ? (
|
||||
<SuppressedAlertIconWrapper>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={SUPPRESSED_ALERT_TOOLTIP(parseInt(suppressionCount.value[0], 10))}
|
||||
>
|
||||
<EuiIcon type="layers" />
|
||||
</EuiToolTip>
|
||||
|
||||
{component}
|
||||
</SuppressedAlertIconWrapper>
|
||||
suppressionCount &&
|
||||
parseInt(suppressionCount[0], 10) > 0 ? (
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
content={SUPPRESSED_ALERT_TOOLTIP(parseInt(suppressionCount[0], 10))}
|
||||
>
|
||||
<EuiIcon type="layers" />
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{component}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
component
|
||||
);
|
||||
|
|
|
@ -37,6 +37,7 @@ export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
|
|||
scopeId,
|
||||
truncate,
|
||||
closeCellPopover,
|
||||
enableActions = true,
|
||||
}) => {
|
||||
const asPlainText = useMemo(() => {
|
||||
return getLinkColumnDefinition(header.id, header.type, undefined) !== undefined && !isTimeline;
|
||||
|
@ -67,7 +68,7 @@ export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
|
|||
values,
|
||||
})}
|
||||
</StyledContent>
|
||||
{isDetails && hasCellActions(header.id) && (
|
||||
{enableActions && isDetails && hasCellActions(header.id) && (
|
||||
<ExpandedCellValueActions
|
||||
field={header}
|
||||
globalFilters={globalFilters}
|
||||
|
|
|
@ -35,6 +35,9 @@ export type SignalEcsAAD = Exclude<SignalEcs, 'rule' | 'status'> & {
|
|||
rule?: Exclude<RuleEcs, 'id'> & { parameters: Record<string, unknown>; uuid: string[] };
|
||||
building_block_type?: string[];
|
||||
workflow_status?: string[];
|
||||
suppression?: {
|
||||
docs_count: string[];
|
||||
};
|
||||
};
|
||||
export interface Ecs {
|
||||
_id: string;
|
||||
|
|
|
@ -31,4 +31,5 @@ export type CellValueElementProps = EuiDataGridCellValueElementProps & {
|
|||
truncate?: boolean;
|
||||
key?: string;
|
||||
closeCellPopover?: () => void;
|
||||
enableActions?: boolean;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue