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

View file

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

View file

@ -160,6 +160,60 @@ let testProps = {
};
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', () => {
let wrapper: ReactWrapper;

View file

@ -41,11 +41,11 @@ const makeMapStateToProps = () => {
// 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 in the active timeline:
const mapStateToProps = (state: State) => {
const mapStateToProps = (state: State, ownProps: { globalFilters?: Filter[] }) => {
const activeTimeline: TimelineModel = getTimeline(state, TimelineId.active) ?? timelineDefaults;
const activeTimelineFilters = activeTimeline.filters ?? EMPTY_FILTERS;
const activeTimelineInput: inputsModel.InputsRange = getInputsTimeline(state);
const { globalFilters } = ownProps;
return {
activeTimelineEventType: activeTimeline.eventType,
activeTimelineFilters:
@ -59,7 +59,7 @@ const makeMapStateToProps = () => {
dataProviders:
activeTimeline.activeTab === TimelineTabs.query ? activeTimeline.dataProviders : [],
globalQuery: getGlobalQuerySelector(state),
globalFilters: getGlobalFiltersQuerySelector(state),
globalFilters: globalFilters ?? getGlobalFiltersQuerySelector(state),
kqlMode: activeTimeline.kqlMode,
};
};
@ -82,6 +82,7 @@ export interface OwnProps {
toggleTopN: () => void;
onFilterAdded?: () => void;
value?: string[] | string | null;
globalFilters?: Filter[];
}
type PropsFromRedux = ConnectedProps<typeof connector>;
type Props = OwnProps & PropsFromRedux;

View file

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

View file

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

View file

@ -8,7 +8,7 @@
import { ReactNode } from 'react';
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 { TimelineNonEcsData } from '../../../search_strategy/timeline';
@ -45,14 +45,16 @@ export type ColumnId = string;
export type TGridCellAction = ({
browserFields,
data,
timelineId,
globalFilters,
pageSize,
timelineId,
}: {
browserFields: BrowserFields;
/** each row of data is represented as one TimelineNonEcsData[] */
data: TimelineNonEcsData[][];
timelineId: string;
globalFilters?: Filter[];
pageSize: number;
timelineId: string;
}) => (props: EuiDataGridColumnCellActionProps) => ReactNode;
/** 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 { EventRenderedView } from '../event_rendered_view';
import { useDataGridHeightHack } from './height_hack';
import { Filter } from '../../../../../../../src/plugins/data/public';
const StatefulAlertStatusBulkActions = lazy(
() => import('../toolbar/bulk_actions/alert_status_bulk_actions')
@ -86,6 +87,7 @@ interface OwnProps {
bulkActions?: BulkActionsProp;
data: TimelineItem[];
defaultCellActions?: TGridCellAction[];
filters?: Filter[];
filterQuery: string;
filterStatus?: AlertStatus;
id: string;
@ -300,15 +302,18 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
data,
defaultCellActions,
filterQuery,
filters,
filterStatus,
hasAlertsCrud,
hasAlertsCrudPermissions,
id,
indexNames,
isEventViewer = false,
isLoading,
isSelectAllChecked,
itemsPerPageOptions,
leadingControlColumns = EMPTY_CONTROL_COLUMNS,
loadingEventIds,
isLoading,
loadPage,
onRuleChange,
pageSize,
@ -322,11 +327,9 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
tableView = 'gridView',
tabType,
totalItems,
totalSelectAllAlerts,
trailingControlColumns = EMPTY_CONTROL_COLUMNS,
unit = defaultUnit,
hasAlertsCrud,
hasAlertsCrudPermissions,
totalSelectAllAlerts,
}) => {
const dispatch = useDispatch();
const getManageTimeline = useMemo(() => tGridSelectors.getManageTimelineById(), []);
@ -641,10 +644,11 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
columnHeaders.map((header) => {
const buildAction = (tGridCellAction: TGridCellAction) =>
tGridCellAction({
data: data.map((row) => row.data),
browserFields,
timelineId: id,
data: data.map((row) => row.data),
globalFilters: filters,
pageSize,
timelineId: id,
});
return {
@ -653,7 +657,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
header.tGridCellActions?.map(buildAction) ?? defaultCellActions?.map(buildAction),
};
}),
[browserFields, columnHeaders, data, defaultCellActions, id, pageSize]
[browserFields, columnHeaders, data, defaultCellActions, id, pageSize, filters]
);
const renderTGridCellValue = useMemo(() => {

View file

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