[SecuritySolution] add global filter to topN (#112401)

* add global filter to topN

* sort lines

* add unit test

* fix duplicate queries

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Angela Chuang 2021-09-22 10:53:37 +01:00 committed by GitHub
parent 0cbdf3f259
commit 210fb50d0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 103 additions and 34 deletions

View file

@ -177,8 +177,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
<InspectButtonContainer> <InspectButtonContainer>
{tGridEnabled ? ( {tGridEnabled ? (
timelinesUi.getTGrid<'embedded'>({ timelinesUi.getTGrid<'embedded'>({
id, additionalFilters,
type: 'embedded',
browserFields, browserFields,
bulkActions, bulkActions,
columns, columns,
@ -189,9 +188,12 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
end, end,
entityType, entityType,
filters: globalFilters, filters: globalFilters,
filterStatus: currentFilter,
globalFullScreen, globalFullScreen,
graphEventId,
graphOverlay, graphOverlay,
hasAlertsCrud, hasAlertsCrud,
id,
indexNames: selectedPatterns, indexNames: selectedPatterns,
indexPattern, indexPattern,
isLive, isLive,
@ -199,19 +201,17 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
itemsPerPage, itemsPerPage,
itemsPerPageOptions: itemsPerPageOptions!, itemsPerPageOptions: itemsPerPageOptions!,
kqlMode, kqlMode,
query, leadingControlColumns,
onRuleChange, onRuleChange,
query,
renderCellValue, renderCellValue,
rowRenderers, rowRenderers,
setQuery, setQuery,
start,
sort, sort,
additionalFilters, start,
graphEventId,
filterStatus: currentFilter,
leadingControlColumns,
trailingControlColumns,
tGridEventRenderedViewEnabled, tGridEventRenderedViewEnabled,
trailingControlColumns,
type: 'embedded',
unit, unit,
}) })
) : ( ) : (

View file

@ -21,6 +21,7 @@ import { useSourcererScope } from '../../../containers/sourcerer';
import { TooltipWithKeyboardShortcut } from '../../accessibility'; import { TooltipWithKeyboardShortcut } from '../../accessibility';
import { getAdditionalScreenReaderOnlyContext } from '../utils'; import { getAdditionalScreenReaderOnlyContext } from '../utils';
import { SHOW_TOP_N_KEYBOARD_SHORTCUT } from '../keyboard_shortcut_constants'; import { SHOW_TOP_N_KEYBOARD_SHORTCUT } from '../keyboard_shortcut_constants';
import { Filter } from '../../../../../../../../src/plugins/data/public';
const SHOW_TOP = (fieldName: string) => const SHOW_TOP = (fieldName: string) =>
i18n.translate('xpack.securitySolution.hoverActions.showTopTooltip', { i18n.translate('xpack.securitySolution.hoverActions.showTopTooltip', {
@ -35,11 +36,12 @@ interface Props {
Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon | typeof EuiContextMenuItem; Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon | typeof EuiContextMenuItem;
enablePopOver?: boolean; enablePopOver?: boolean;
field: string; field: string;
globalFilters?: Filter[];
onClick: () => void; onClick: () => void;
onFilterAdded?: () => void; onFilterAdded?: () => void;
ownFocus: boolean; ownFocus: boolean;
showTopN: boolean;
showTooltip?: boolean; showTooltip?: boolean;
showTopN: boolean;
timelineId?: string | null; timelineId?: string | null;
value?: string[] | string | null; value?: string[] | string | null;
} }
@ -56,6 +58,7 @@ export const ShowTopNButton: React.FC<Props> = React.memo(
showTopN, showTopN,
timelineId, timelineId,
value, value,
globalFilters,
}) => { }) => {
const activeScope: SourcererScopeName = const activeScope: SourcererScopeName =
timelineId === TimelineId.active timelineId === TimelineId.active
@ -128,9 +131,10 @@ export const ShowTopNButton: React.FC<Props> = React.memo(
timelineId={timelineId ?? undefined} timelineId={timelineId ?? undefined}
toggleTopN={onClick} toggleTopN={onClick}
value={value} value={value}
globalFilters={globalFilters}
/> />
), ),
[browserFields, field, indexPattern, onClick, onFilterAdded, timelineId, value] [browserFields, field, indexPattern, onClick, onFilterAdded, timelineId, value, globalFilters]
); );
return showTopN ? ( return showTopN ? (

View file

@ -160,6 +160,60 @@ let testProps = {
}; };
describe('StatefulTopN', () => { describe('StatefulTopN', () => {
describe('rendering globalFilter', () => {
let wrapper: ReactWrapper;
const globalFilters = [
{
meta: {
alias: null,
negate: false,
disabled: false,
type: 'phrase',
key: 'signal.rule.id',
params: {
query: 'd62249f0-1632-11ec-b035-19607969bc20',
},
},
query: {
match_phrase: {
'signal.rule.id': 'd62249f0-1632-11ec-b035-19607969bc20',
},
},
},
];
beforeEach(() => {
wrapper = mount(
<TestProviders store={store}>
<StatefulTopN {...testProps} globalFilters={globalFilters} />
</TestProviders>
);
});
test(`provides filters from non Redux state when rendering in alerts table`, () => {
const props = wrapper.find('[data-test-subj="top-n"]').first().props() as Props;
expect(props.filters).toEqual([
{
meta: {
alias: null,
negate: false,
disabled: false,
type: 'phrase',
key: 'signal.rule.id',
params: {
query: 'd62249f0-1632-11ec-b035-19607969bc20',
},
},
query: {
match_phrase: {
'signal.rule.id': 'd62249f0-1632-11ec-b035-19607969bc20',
},
},
},
]);
});
});
describe('rendering in a global NON-timeline context', () => { describe('rendering in a global NON-timeline context', () => {
let wrapper: ReactWrapper; let wrapper: ReactWrapper;

View file

@ -41,11 +41,11 @@ const makeMapStateToProps = () => {
// The mapped Redux state provided to this component includes the global // The mapped Redux state provided to this component includes the global
// filters that appear at the top of most views in the app, and all the // filters that appear at the top of most views in the app, and all the
// filters in the active timeline: // filters in the active timeline:
const mapStateToProps = (state: State) => { const mapStateToProps = (state: State, ownProps: { globalFilters?: Filter[] }) => {
const activeTimeline: TimelineModel = getTimeline(state, TimelineId.active) ?? timelineDefaults; const activeTimeline: TimelineModel = getTimeline(state, TimelineId.active) ?? timelineDefaults;
const activeTimelineFilters = activeTimeline.filters ?? EMPTY_FILTERS; const activeTimelineFilters = activeTimeline.filters ?? EMPTY_FILTERS;
const activeTimelineInput: inputsModel.InputsRange = getInputsTimeline(state); const activeTimelineInput: inputsModel.InputsRange = getInputsTimeline(state);
const { globalFilters } = ownProps;
return { return {
activeTimelineEventType: activeTimeline.eventType, activeTimelineEventType: activeTimeline.eventType,
activeTimelineFilters: activeTimelineFilters:
@ -59,7 +59,7 @@ const makeMapStateToProps = () => {
dataProviders: dataProviders:
activeTimeline.activeTab === TimelineTabs.query ? activeTimeline.dataProviders : [], activeTimeline.activeTab === TimelineTabs.query ? activeTimeline.dataProviders : [],
globalQuery: getGlobalQuerySelector(state), globalQuery: getGlobalQuerySelector(state),
globalFilters: getGlobalFiltersQuerySelector(state), globalFilters: globalFilters ?? getGlobalFiltersQuerySelector(state),
kqlMode: activeTimeline.kqlMode, kqlMode: activeTimeline.kqlMode,
}; };
}; };
@ -82,6 +82,7 @@ export interface OwnProps {
toggleTopN: () => void; toggleTopN: () => void;
onFilterAdded?: () => void; onFilterAdded?: () => void;
value?: string[] | string | null; value?: string[] | string | null;
globalFilters?: Filter[];
} }
type PropsFromRedux = ConnectedProps<typeof connector>; type PropsFromRedux = ConnectedProps<typeof connector>;
type Props = OwnProps & PropsFromRedux; type Props = OwnProps & PropsFromRedux;

View file

@ -6,6 +6,7 @@
*/ */
import React, { useCallback, useState, useMemo } from 'react'; import React, { useCallback, useState, useMemo } from 'react';
import { Filter } from '../../../../../../../src/plugins/data/public';
import type { import type {
BrowserFields, BrowserFields,
@ -167,11 +168,13 @@ export const defaultCellActions: TGridCellAction[] = [
({ ({
browserFields, browserFields,
data, data,
globalFilters,
timelineId, timelineId,
pageSize, pageSize,
}: { }: {
browserFields: BrowserFields; browserFields: BrowserFields;
data: TimelineNonEcsData[][]; data: TimelineNonEcsData[][];
globalFilters?: Filter[];
timelineId: string; timelineId: string;
pageSize: number; pageSize: number;
}) => }) =>
@ -205,6 +208,7 @@ export const defaultCellActions: TGridCellAction[] = [
enablePopOver enablePopOver
data-test-subj="hover-actions-show-top-n" data-test-subj="hover-actions-show-top-n"
field={columnId} field={columnId}
globalFilters={globalFilters}
onClick={onClick} onClick={onClick}
onFilterAdded={onFilterAdded} onFilterAdded={onFilterAdded}
ownFocus={false} ownFocus={false}

View file

@ -391,7 +391,6 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
const alertsTableDefaultFilters = useMemo( const alertsTableDefaultFilters = useMemo(
() => [ () => [
...buildAlertsRuleIdFilter(ruleId), ...buildAlertsRuleIdFilter(ruleId),
...filters,
...(ruleRegistryEnabled ...(ruleRegistryEnabled
? [ ? [
// TODO: Once we are past experimental phase this code should be removed // TODO: Once we are past experimental phase this code should be removed
@ -400,7 +399,7 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
: [...buildShowBuildingBlockFilter(showBuildingBlockAlerts)]), : [...buildShowBuildingBlockFilter(showBuildingBlockAlerts)]),
...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts),
], ],
[ruleId, filters, ruleRegistryEnabled, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] [ruleId, ruleRegistryEnabled, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts]
); );
const alertMergedFilters = useMemo( const alertMergedFilters = useMemo(

View file

@ -8,7 +8,7 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { EuiDataGridColumn, EuiDataGridColumnCellActionProps } from '@elastic/eui'; import { EuiDataGridColumn, EuiDataGridColumnCellActionProps } from '@elastic/eui';
import { IFieldSubType } from '../../../../../../../src/plugins/data/common'; import { Filter, IFieldSubType } from '../../../../../../../src/plugins/data/common';
import { BrowserFields } from '../../../search_strategy/index_fields'; import { BrowserFields } from '../../../search_strategy/index_fields';
import { TimelineNonEcsData } from '../../../search_strategy/timeline'; import { TimelineNonEcsData } from '../../../search_strategy/timeline';
@ -45,14 +45,16 @@ export type ColumnId = string;
export type TGridCellAction = ({ export type TGridCellAction = ({
browserFields, browserFields,
data, data,
timelineId, globalFilters,
pageSize, pageSize,
timelineId,
}: { }: {
browserFields: BrowserFields; browserFields: BrowserFields;
/** each row of data is represented as one TimelineNonEcsData[] */ /** each row of data is represented as one TimelineNonEcsData[] */
data: TimelineNonEcsData[][]; data: TimelineNonEcsData[][];
timelineId: string; globalFilters?: Filter[];
pageSize: number; pageSize: number;
timelineId: string;
}) => (props: EuiDataGridColumnCellActionProps) => ReactNode; }) => (props: EuiDataGridColumnCellActionProps) => ReactNode;
/** The specification of a column header */ /** The specification of a column header */

View file

@ -74,6 +74,7 @@ import type { EuiTheme } from '../../../../../../../src/plugins/kibana_react/com
import { ViewSelection } from '../event_rendered_view/selector'; import { ViewSelection } from '../event_rendered_view/selector';
import { EventRenderedView } from '../event_rendered_view'; import { EventRenderedView } from '../event_rendered_view';
import { useDataGridHeightHack } from './height_hack'; import { useDataGridHeightHack } from './height_hack';
import { Filter } from '../../../../../../../src/plugins/data/public';
const StatefulAlertStatusBulkActions = lazy( const StatefulAlertStatusBulkActions = lazy(
() => import('../toolbar/bulk_actions/alert_status_bulk_actions') () => import('../toolbar/bulk_actions/alert_status_bulk_actions')
@ -86,6 +87,7 @@ interface OwnProps {
bulkActions?: BulkActionsProp; bulkActions?: BulkActionsProp;
data: TimelineItem[]; data: TimelineItem[];
defaultCellActions?: TGridCellAction[]; defaultCellActions?: TGridCellAction[];
filters?: Filter[];
filterQuery: string; filterQuery: string;
filterStatus?: AlertStatus; filterStatus?: AlertStatus;
id: string; id: string;
@ -300,15 +302,18 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
data, data,
defaultCellActions, defaultCellActions,
filterQuery, filterQuery,
filters,
filterStatus, filterStatus,
hasAlertsCrud,
hasAlertsCrudPermissions,
id, id,
indexNames, indexNames,
isEventViewer = false, isEventViewer = false,
isLoading,
isSelectAllChecked, isSelectAllChecked,
itemsPerPageOptions, itemsPerPageOptions,
leadingControlColumns = EMPTY_CONTROL_COLUMNS, leadingControlColumns = EMPTY_CONTROL_COLUMNS,
loadingEventIds, loadingEventIds,
isLoading,
loadPage, loadPage,
onRuleChange, onRuleChange,
pageSize, pageSize,
@ -322,11 +327,9 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
tableView = 'gridView', tableView = 'gridView',
tabType, tabType,
totalItems, totalItems,
totalSelectAllAlerts,
trailingControlColumns = EMPTY_CONTROL_COLUMNS, trailingControlColumns = EMPTY_CONTROL_COLUMNS,
unit = defaultUnit, unit = defaultUnit,
hasAlertsCrud,
hasAlertsCrudPermissions,
totalSelectAllAlerts,
}) => { }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const getManageTimeline = useMemo(() => tGridSelectors.getManageTimelineById(), []); const getManageTimeline = useMemo(() => tGridSelectors.getManageTimelineById(), []);
@ -641,10 +644,11 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
columnHeaders.map((header) => { columnHeaders.map((header) => {
const buildAction = (tGridCellAction: TGridCellAction) => const buildAction = (tGridCellAction: TGridCellAction) =>
tGridCellAction({ tGridCellAction({
data: data.map((row) => row.data),
browserFields, browserFields,
timelineId: id, data: data.map((row) => row.data),
globalFilters: filters,
pageSize, pageSize,
timelineId: id,
}); });
return { return {
@ -653,7 +657,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
header.tGridCellActions?.map(buildAction) ?? defaultCellActions?.map(buildAction), header.tGridCellActions?.map(buildAction) ?? defaultCellActions?.map(buildAction),
}; };
}), }),
[browserFields, columnHeaders, data, defaultCellActions, id, pageSize] [browserFields, columnHeaders, data, defaultCellActions, id, pageSize, filters]
); );
const renderTGridCellValue = useMemo(() => { const renderTGridCellValue = useMemo(() => {

View file

@ -347,30 +347,31 @@ const TGridIntegratedComponent: React.FC<TGridIntegratedProps> = ({
> >
<ScrollableFlexItem grow={1}> <ScrollableFlexItem grow={1}>
<StatefulBody <StatefulBody
hasAlertsCrud={hasAlertsCrud}
activePage={pageInfo.activePage} activePage={pageInfo.activePage}
browserFields={browserFields} browserFields={browserFields}
bulkActions={bulkActions} bulkActions={bulkActions}
filterQuery={filterQuery}
data={nonDeletedEvents} data={nonDeletedEvents}
defaultCellActions={defaultCellActions} defaultCellActions={defaultCellActions}
filterQuery={filterQuery}
filters={filters}
filterStatus={filterStatus}
hasAlertsCrud={hasAlertsCrud}
id={id} id={id}
indexNames={indexNames}
isEventViewer={true} isEventViewer={true}
itemsPerPageOptions={itemsPerPageOptions} itemsPerPageOptions={itemsPerPageOptions}
leadingControlColumns={leadingControlColumns}
loadPage={loadPage} loadPage={loadPage}
onRuleChange={onRuleChange} onRuleChange={onRuleChange}
pageSize={itemsPerPage} pageSize={itemsPerPage}
refetch={refetch}
renderCellValue={renderCellValue} renderCellValue={renderCellValue}
rowRenderers={rowRenderers} rowRenderers={rowRenderers}
tabType={TimelineTabs.query}
tableView={tableView} tableView={tableView}
tabType={TimelineTabs.query}
totalItems={totalCountMinusDeleted} totalItems={totalCountMinusDeleted}
unit={unit}
filterStatus={filterStatus}
leadingControlColumns={leadingControlColumns}
trailingControlColumns={trailingControlColumns} trailingControlColumns={trailingControlColumns}
refetch={refetch} unit={unit}
indexNames={indexNames}
/> />
</ScrollableFlexItem> </ScrollableFlexItem>
</FullWidthFlexGroup> </FullWidthFlexGroup>