From eca7146a9f60e6b42442065e372e4a1b92039176 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Tue, 10 Aug 2021 12:31:05 +0200 Subject: [PATCH] [RAC] [Alerts] Add highlight to building block alerts (#107727) * [rac] [Alerts] Add highlight to building block alerts * Pull back 'additional filters' button to t-grid --- .../common/components/events_viewer/index.tsx | 5 +- .../alerts_table/alerts_utility_bar/index.tsx | 124 ++++++++++------- .../components/alerts_table/index.tsx | 13 +- .../components/t_grid/body/helpers.test.tsx | 32 +++++ .../public/components/t_grid/body/helpers.tsx | 23 +++ .../public/components/t_grid/body/index.tsx | 131 ++++++++++-------- .../components/t_grid/integrated/index.tsx | 12 +- 7 files changed, 223 insertions(+), 117 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 8a8ebd18174b..fe6c7e85e175 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -62,6 +62,7 @@ export interface OwnProps { renderCellValue: (props: CellValueElementProps) => React.ReactNode; rowRenderers: RowRenderer[]; utilityBar?: (refetch: inputsModel.Refetch, totalCount: number) => React.ReactNode; + additionalFilters?: React.ReactNode; } type Props = OwnProps & PropsFromRedux; @@ -98,6 +99,7 @@ const StatefulEventsViewerComponent: React.FC = ({ showCheckboxes, sort, utilityBar, + additionalFilters, // If truthy, the graph viewer (Resolver) is showing graphEventId, }) => { @@ -165,7 +167,7 @@ const StatefulEventsViewerComponent: React.FC = ({ setGlobalFullScreen, start, sort, - utilityBar, + additionalFilters, graphEventId, filterStatus: currentFilter, leadingControlColumns, @@ -291,6 +293,7 @@ export const StatefulEventsViewer = connector( prevProps.showCheckboxes === nextProps.showCheckboxes && prevProps.start === nextProps.start && prevProps.utilityBar === nextProps.utilityBar && + prevProps.additionalFilters === nextProps.additionalFilters && prevProps.graphEventId === nextProps.graphEventId ) ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx index 1ef79a64f831..8a88c430b03e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx @@ -50,14 +50,12 @@ const UtilityBarFlexGroup = styled(EuiFlexGroup)` min-width: 175px; `; -const BuildingBlockContainer = styled(EuiFlexItem)` - background: repeating-linear-gradient( - 127deg, - rgba(245, 167, 0, 0.2), - rgba(245, 167, 0, 0.2) 1px, - rgba(245, 167, 0, 0.05) 2px, - rgba(245, 167, 0, 0.05) 10px - ); +const AdditionalFiltersItem = styled(EuiFlexItem)` + padding: ${({ theme }) => theme.eui.paddingSizes.s}; +`; + +const BuildingBlockContainer = styled(AdditionalFiltersItem)` + background: ${({ theme }) => theme.eui.euiColorHighlight}; `; const AlertsUtilityBarComponent: React.FC = ({ @@ -146,39 +144,6 @@ const AlertsUtilityBarComponent: React.FC = ({ ); - const UtilityBarAdditionalFiltersContent = (closePopover: () => void) => ( - - - ) => { - closePopover(); - onShowBuildingBlockAlertsChanged(e.target.checked); - }} - checked={showBuildingBlockAlerts} - color="text" - data-test-subj="showBuildingBlockAlertsCheckbox" - label={i18n.ADDITIONAL_FILTERS_ACTIONS_SHOW_BUILDING_BLOCK} - /> - - - ) => { - closePopover(); - onShowOnlyThreatIndicatorAlertsChanged(e.target.checked); - }} - checked={showOnlyThreatIndicatorAlerts} - color="text" - data-test-subj="showOnlyThreatIndicatorAlertsCheckbox" - label={i18n.ADDITIONAL_FILTERS_ACTIONS_SHOW_ONLY_THREAT_INDICATOR_ALERTS} - /> - - - ); - const handleSelectAllAlertsClick = useCallback(() => { if (!showClearSelection) { selectAll(); @@ -233,16 +198,13 @@ const AlertsUtilityBarComponent: React.FC = ({ )} - - {i18n.ADDITIONAL_FILTERS_ACTIONS} - + @@ -260,3 +222,63 @@ export const AlertsUtilityBar = React.memo( prevProps.showBuildingBlockAlerts === nextProps.showBuildingBlockAlerts && prevProps.showOnlyThreatIndicatorAlerts === nextProps.showOnlyThreatIndicatorAlerts ); + +export const AditionalFiltersAction = ({ + areEventsLoading, + onShowBuildingBlockAlertsChanged, + showBuildingBlockAlerts, + onShowOnlyThreatIndicatorAlertsChanged, + showOnlyThreatIndicatorAlerts, +}: { + areEventsLoading: boolean; + onShowBuildingBlockAlertsChanged: (showBuildingBlockAlerts: boolean) => void; + showBuildingBlockAlerts: boolean; + onShowOnlyThreatIndicatorAlertsChanged: (showOnlyThreatIndicatorAlerts: boolean) => void; + showOnlyThreatIndicatorAlerts: boolean; +}) => { + const UtilityBarAdditionalFiltersContent = (closePopover: () => void) => ( + + + ) => { + closePopover(); + onShowBuildingBlockAlertsChanged(e.target.checked); + }} + checked={showBuildingBlockAlerts} + color="text" + data-test-subj="showBuildingBlockAlertsCheckbox" + label={i18n.ADDITIONAL_FILTERS_ACTIONS_SHOW_BUILDING_BLOCK} + /> + + + ) => { + closePopover(); + onShowOnlyThreatIndicatorAlertsChanged(e.target.checked); + }} + checked={showOnlyThreatIndicatorAlerts} + color="text" + data-test-subj="showOnlyThreatIndicatorAlertsCheckbox" + label={i18n.ADDITIONAL_FILTERS_ACTIONS_SHOW_ONLY_THREAT_INDICATOR_ALERTS} + /> + + + ); + + return ( + + {i18n.ADDITIONAL_FILTERS_ACTIONS} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 40b6a21ea3e6..d8d6424ef2a7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -31,7 +31,7 @@ import { alertsDefaultModelRuleRegistry, buildAlertStatusFilterRuleRegistry, } from './default_config'; -import { AlertsUtilityBar } from './alerts_utility_bar'; +import { AditionalFiltersAction, AlertsUtilityBar } from './alerts_utility_bar'; import * as i18nCommon from '../../../common/translations'; import * as i18n from './translations'; import { @@ -313,6 +313,16 @@ export const AlertsTableComponent: React.FC = ({ ] ); + const additionalFiltersComponent = ( + 0} + onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChanged} + showBuildingBlockAlerts={showBuildingBlockAlerts} + onShowOnlyThreatIndicatorAlertsChanged={onShowOnlyThreatIndicatorAlertsChanged} + showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} + /> + ); + const defaultFiltersMemo = useMemo(() => { // TODO: Once we are past experimental phase this code should be removed const alertStatusFilter = ruleRegistryEnabled @@ -382,6 +392,7 @@ export const AlertsTableComponent: React.FC = ({ scopeId={SourcererScopeName.detections} start={from} utilityBar={utilityBarCallback} + additionalFilters={additionalFiltersComponent} /> ); }; diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx index fe9c5ea2bc33..2b58487fce53 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx @@ -14,8 +14,12 @@ import { mapSortDirectionToDirection, mapSortingColumns, stringifyEvent, + addBuildingBlockStyle, } from './helpers'; +import { euiThemeVars } from '@kbn/ui-shared-deps/theme'; +import { mockDnsEvent } from '../../../mock'; + describe('helpers', () => { describe('stringifyEvent', () => { test('it omits __typename when it appears at arbitrary levels', () => { @@ -388,4 +392,32 @@ describe('helpers', () => { ).toBe(false); }); }); + + describe('addBuildingBlockStyle', () => { + const THEME = { eui: euiThemeVars, darkMode: false }; + + test('it calls `setCellProps` with background color when event is a building block', () => { + const mockedSetCellProps = jest.fn(); + const ecs = { + ...mockDnsEvent, + ...{ signal: { rule: { building_block_type: ['default'] } } }, + }; + + addBuildingBlockStyle(ecs, THEME, mockedSetCellProps); + + expect(mockedSetCellProps).toBeCalledWith({ + style: { + backgroundColor: euiThemeVars.euiColorHighlight, + }, + }); + }); + + test('it call `setCellProps` reseting the background color when event is not a building block', () => { + const mockedSetCellProps = jest.fn(); + + addBuildingBlockStyle(mockDnsEvent, THEME, mockedSetCellProps); + + expect(mockedSetCellProps).toBeCalledWith({ style: { backgroundColor: 'inherit' } }); + }); + }); }); diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/helpers.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/helpers.tsx index fb50d5ebabb8..790414314ecd 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/helpers.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/helpers.tsx @@ -7,6 +7,7 @@ import { isEmpty } from 'lodash/fp'; +import { EuiDataGridCellValueElementProps } from '@elastic/eui'; import type { Ecs } from '../../../../common/ecs'; import type { BrowserField, @@ -20,6 +21,8 @@ import type { TimelineEventsType, } from '../../../../common/types/timeline'; +import type { EuiTheme } from '../../../../../../../src/plugins/kibana_react/common'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any export const omitTypenameAndEmpty = (k: string, v: any): any | undefined => k !== '__typename' && v != null ? v : undefined; @@ -185,3 +188,23 @@ export const allowSorting = ({ return isAllowlistedNonBrowserField || isAggregatable; }; +export const addBuildingBlockStyle = ( + ecs: Ecs, + theme: EuiTheme, + setCellProps: EuiDataGridCellValueElementProps['setCellProps'] +) => { + if (isEventBuildingBlockType(ecs)) { + setCellProps({ + style: { + backgroundColor: `${theme.eui.euiColorHighlight}`, + }, + }); + } else { + // reset cell style + setCellProps({ + style: { + backgroundColor: 'inherit', + }, + }); + } +}; diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx index 00f9d513a1a1..cc94f901446a 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx @@ -24,28 +24,34 @@ import React, { useEffect, useMemo, useState, + useContext, } from 'react'; + import { connect, ConnectedProps, useDispatch } from 'react-redux'; +import { ThemeContext } from 'styled-components'; import { TGridCellAction, - TimelineId, - TimelineTabs, BulkActionsProp, - SortColumnTimeline, -} from '../../../../common/types/timeline'; - -import type { CellValueElementProps, ColumnHeaderOptions, ControlColumnProps, RowRenderer, AlertStatus, + SortColumnTimeline, + TimelineId, + TimelineTabs, } from '../../../../common/types/timeline'; + import type { TimelineItem, TimelineNonEcsData } from '../../../../common/search_strategy/timeline'; import { getActionsColumnWidth, getColumnHeaders } from './column_headers/helpers'; -import { getEventIdToDataMapping, mapSortDirectionToDirection, mapSortingColumns } from './helpers'; +import { + addBuildingBlockStyle, + getEventIdToDataMapping, + mapSortDirectionToDirection, + mapSortingColumns, +} from './helpers'; import { DEFAULT_ICON_BUTTON_WIDTH } from '../helpers'; import type { BrowserFields } from '../../../../common/search_strategy/index_fields'; @@ -58,6 +64,7 @@ import { RowAction } from './row_action'; import * as i18n from './translations'; import { AlertCount } from '../styles'; import { checkBoxControlColumn } from './control_columns'; +import type { EuiTheme } from '../../../../../../../src/plugins/kibana_react/common'; const StatefulAlertStatusBulkActions = lazy( () => import('../toolbar/bulk_actions/alert_status_bulk_actions') @@ -121,6 +128,7 @@ const transformControlColumns = ({ onSelectPage, browserFields, sort, + theme, }: { actionColumnsWidth: number; columnHeaders: ColumnHeaderOptions[]; @@ -138,6 +146,7 @@ const transformControlColumns = ({ browserFields: BrowserFields; onSelectPage: OnSelectAll; sort: SortColumnTimeline[]; + theme: EuiTheme; }): EuiDataGridControlColumn[] => controlColumns.map( ({ id: columnId, headerCellRender = EmptyHeaderCellRender, rowCellRender, width }, i) => ({ @@ -173,29 +182,33 @@ const transformControlColumns = ({ isExpanded, rowIndex, setCellProps, - }: EuiDataGridCellValueElementProps) => ( - - ), + }: EuiDataGridCellValueElementProps) => { + addBuildingBlockStyle(data[rowIndex].ecs, theme, setCellProps); + + return ( + + ); + }, width: width ?? actionColumnsWidth, }) ); @@ -252,6 +265,7 @@ export const BodyComponent = React.memo( const selectedCount = useMemo(() => Object.keys(selectedEventIds).length, [selectedEventIds]); + const theme: EuiTheme = useContext(ThemeContext); const onRowSelected: OnRowSelected = useCallback( ({ eventIds, isSelected }: { eventIds: string[]; isSelected: boolean }) => { setSelected({ @@ -444,6 +458,7 @@ export const BodyComponent = React.memo( sort, browserFields, onSelectPage, + theme, }) ); }, [ @@ -463,6 +478,7 @@ export const BodyComponent = React.memo( browserFields, onSelectPage, sort, + theme, ]); const columnsWithCellActions: EuiDataGridColumn[] = useMemo( @@ -483,34 +499,37 @@ export const BodyComponent = React.memo( [browserFields, columnHeaders, data, defaultCellActions] ); - const renderTGridCellValue: (x: EuiDataGridCellValueElementProps) => React.ReactNode = ({ - columnId, - rowIndex, - setCellProps, - }) => { - const rowData = rowIndex < data.length ? data[rowIndex].data : null; - const header = columnHeaders.find((h) => h.id === columnId); - const eventId = rowIndex < data.length ? data[rowIndex]._id : null; + const renderTGridCellValue: ( + x: EuiDataGridCellValueElementProps + ) => React.ReactNode = useCallback( + ({ columnId, rowIndex, setCellProps }) => { + const rowData = rowIndex < data.length ? data[rowIndex].data : null; + const header = columnHeaders.find((h) => h.id === columnId); + const eventId = rowIndex < data.length ? data[rowIndex]._id : null; - if (rowData == null || header == null || eventId == null) { - return null; - } + addBuildingBlockStyle(data[rowIndex].ecs, theme, setCellProps); - return renderCellValue({ - columnId: header.id, - eventId, - data: rowData, - header, - isDraggable: false, - isExpandable: true, - isExpanded: false, - isDetails: false, - linkValues: getOr([], header.linkField ?? '', data[rowIndex].ecs), - rowIndex, - setCellProps, - timelineId: tabType != null ? `${id}-${tabType}` : id, - }); - }; + if (rowData == null || header == null || eventId == null) { + return null; + } + + return renderCellValue({ + columnId: header.id, + eventId, + data: rowData, + header, + isDraggable: false, + isExpandable: true, + isExpanded: false, + isDetails: false, + linkValues: getOr([], header.linkField ?? '', data[rowIndex].ecs), + rowIndex, + setCellProps, + timelineId: tabType != null ? `${id}-${tabType}` : id, + }); + }, + [columnHeaders, data, id, renderCellValue, tabType, theme] + ); return ( void; start: string; sort: Sort[]; - utilityBar?: (refetch: Refetch, totalCount: number) => React.ReactNode; + additionalFilters: React.ReactNode; // If truthy, the graph viewer (Resolver) is showing graphEventId: string | undefined; leadingControlColumns?: ControlColumnProps[]; @@ -167,7 +165,7 @@ const TGridIntegratedComponent: React.FC = ({ setGlobalFullScreen, start, sort, - utilityBar, + additionalFilters, graphEventId, leadingControlColumns, trailingControlColumns, @@ -239,7 +237,7 @@ const TGridIntegratedComponent: React.FC = ({ const [ loading, - { events, updatedAt, loadPage, pageInfo, refetch, totalCount = 0, inspect }, + { events, loadPage, pageInfo, refetch, totalCount = 0, inspect }, ] = useTimelineEvents({ alertConsumers: SECURITY_ALERTS_CONSUMERS, docValueFields, @@ -294,19 +292,17 @@ const TGridIntegratedComponent: React.FC = ({ height={ headerFilterGroup == null ? COMPACT_HEADER_HEIGHT : EVENTS_VIEWER_HEADER_HEIGHT } - subtitle={utilityBar} title={globalFullScreen ? titleWithExitFullScreen : justTitle} > {HeaderSectionContent} - - + {!resolverIsShowing(graphEventId) && additionalFilters}