Skip to content

Commit

Permalink
[Security solution] Grouping package cleanup (#152820)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephmilovic authored Mar 8, 2023
1 parent 55ff17b commit eef9265
Show file tree
Hide file tree
Showing 17 changed files with 400 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const rule2Desc = 'Rule 2 description';

const testProps = {
data: {
groupsNumber: {
groupCount0: {
value: 2,
},
stackByMultipleFields0: {
Expand Down Expand Up @@ -72,7 +72,7 @@ const testProps = {
sum_other_doc_count: 0,
buckets: [],
},
alertsCount: {
unitCount0: {
value: 1,
},
severitiesSubAggregation: {
Expand All @@ -94,7 +94,7 @@ const testProps = {
},
],
},
alertsCount: {
unitCount0: {
value: 2,
},
},
Expand All @@ -115,29 +115,29 @@ describe('grouping container', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('Renders group counts when groupsNumber > 0', () => {
it('Renders group counts when groupCount0 > 0', () => {
const { getByTestId, getAllByTestId, queryByTestId } = render(
<I18nProvider>
<Grouping {...testProps} />
</I18nProvider>
);
expect(getByTestId('alert-count').textContent).toBe('2 alerts');
expect(getByTestId('groups-count').textContent).toBe('2 groups');
expect(getByTestId('unit-count').textContent).toBe('2 events');
expect(getByTestId('group-count').textContent).toBe('2 groups');
expect(getAllByTestId('grouping-accordion').length).toBe(2);
expect(queryByTestId('empty-results-panel')).not.toBeInTheDocument();
});

it('Does not render group counts when groupsNumber = 0', () => {
it('Does not render group counts when groupCount0 = 0', () => {
const data = {
groupsNumber: {
groupCount0: {
value: 0,
},
stackByMultipleFields0: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [],
},
alertsCount: {
unitCount0: {
value: 0,
},
};
Expand All @@ -146,8 +146,8 @@ describe('grouping container', () => {
<Grouping {...testProps} data={data} />
</I18nProvider>
);
expect(queryByTestId('alert-count')).not.toBeInTheDocument();
expect(queryByTestId('groups-count')).not.toBeInTheDocument();
expect(queryByTestId('unit-count')).not.toBeInTheDocument();
expect(queryByTestId('group-count')).not.toBeInTheDocument();
expect(queryByTestId('grouping-accordion')).not.toBeInTheDocument();
expect(getByTestId('empty-results-panel')).toBeInTheDocument();
});
Expand Down
34 changes: 15 additions & 19 deletions packages/kbn-securitysolution-grouping/src/components/grouping.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type { BadgeMetric, CustomMetric } from './accordion_panel';
import { GroupPanel } from './accordion_panel';
import { GroupStats } from './accordion_panel/group_stats';
import { EmptyGroupingComponent } from './empty_results_panel';
import { groupingContainerCss, groupsUnitCountCss } from './styles';
import { groupingContainerCss, countCss } from './styles';
import { GROUPS_UNIT } from './translations';
import type { GroupingAggregation, GroupingFieldTotalAggregation, RawBucket } from './types';

Expand Down Expand Up @@ -64,15 +64,15 @@ const GroupingComponent = <T,>({
Record<string, { state: 'open' | 'closed' | undefined; selectedBucket: RawBucket<T> }>
>({});

const groupsNumber = data?.groupsNumber?.value ?? 0;
const unitCount = data?.unitCount0?.value ?? 0;
const unitCountText = useMemo(() => {
const count = data?.alertsCount?.value ?? 0;
return `${count.toLocaleString()} ${unit && unit(count)}`;
}, [data?.alertsCount?.value, unit]);
return `${unitCount.toLocaleString()} ${unit && unit(unitCount)}`;
}, [unitCount, unit]);

const unitGroupsCountText = useMemo(
() => `${groupsNumber.toLocaleString()} ${GROUPS_UNIT(groupsNumber)}`,
[groupsNumber]
const groupCount = data?.groupCount0?.value ?? 0;
const groupCountText = useMemo(
() => `${groupCount.toLocaleString()} ${GROUPS_UNIT(groupCount)}`,
[groupCount]
);

const groupPanels = useMemo(
Expand Down Expand Up @@ -129,8 +129,8 @@ const GroupingComponent = <T,>({
]
);
const pageCount = useMemo(
() => (groupsNumber && pagination.pageSize ? Math.ceil(groupsNumber / pagination.pageSize) : 1),
[groupsNumber, pagination.pageSize]
() => (groupCount && pagination.pageSize ? Math.ceil(groupCount / pagination.pageSize) : 1),
[groupCount, pagination.pageSize]
);
return (
<>
Expand All @@ -141,20 +141,16 @@ const GroupingComponent = <T,>({
style={{ paddingBottom: 20, paddingTop: 20 }}
>
<EuiFlexItem grow={false}>
{groupsNumber > 0 ? (
{groupCount > 0 && unitCount > 0 ? (
<EuiFlexGroup gutterSize="none">
<EuiFlexItem grow={false}>
<span css={groupsUnitCountCss} data-test-subj="alert-count">
<span css={countCss} data-test-subj="unit-count">
{unitCountText}
</span>
</EuiFlexItem>
<EuiFlexItem>
<span
css={groupsUnitCountCss}
data-test-subj="groups-count"
style={{ borderRight: 'none' }}
>
{unitGroupsCountText}
<span css={countCss} data-test-subj="group-count" style={{ borderRight: 'none' }}>
{groupCountText}
</span>
</EuiFlexItem>
</EuiFlexGroup>
Expand All @@ -168,7 +164,7 @@ const GroupingComponent = <T,>({
</EuiFlexItem>
</EuiFlexGroup>
<div css={groupingContainerCss} className="eui-xScroll">
{groupsNumber > 0 ? (
{groupCount > 0 ? (
<>
{groupPanels}
<EuiSpacer size="m" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { css } from '@emotion/react';
import { euiThemeVars } from '@kbn/ui-theme';

export const groupsUnitCountCss = css`
export const countCss = css`
font-size: ${euiThemeVars.euiFontSizeXS};
font-weight: ${euiThemeVars.euiFontWeightSemiBold};
border-right: ${euiThemeVars.euiBorderThin};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ export const CUSTOM_FIELD = i18n.translate('grouping.customGroupByOptionName', {
defaultMessage: 'Custom field',
});

export const ALERTS_UNIT = (totalCount: number) =>
export const DEFAULT_UNIT = (totalCount: number) =>
i18n.translate('grouping.eventsTab.unit', {
values: { totalCount },
defaultMessage: `{totalCount, plural, =1 {alert} other {alerts}}`,
defaultMessage: `{totalCount, plural, =1 {event} other {events}}`,
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ export const NONE_GROUP_KEY = 'none';
export type RawBucket<T> = GenericBuckets & T;

/** Defines the shape of the aggregation returned by Elasticsearch */
// TODO: write developer docs for these fields
export interface GroupingAggregation<T> {
stackByMultipleFields0?: {
buckets?: Array<RawBucket<T>>;
};
groupsCount0?: {
groupCount0?: {
value?: number | null;
};
unitCount0?: {
value?: number | null;
};
}
Expand Down
4 changes: 2 additions & 2 deletions packages/kbn-securitysolution-grouping/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export function firstNonNullValue<T>(valueOrCollection: ECSField<T>): T | undefi
}
}

export const defaultUnit = (n: number) => i18n.ALERTS_UNIT(n);
export const defaultUnit = (n: number) => i18n.DEFAULT_UNIT(n);

const LOCAL_STORAGE_GROUPING_KEY = 'groups';
export const LOCAL_STORAGE_GROUPING_KEY = 'groups';
export const getAllGroupsInStorage = (storage: Storage): GroupsById => {
const allGroups = storage.getItem(LOCAL_STORAGE_GROUPING_KEY);
if (!allGroups) {
Expand Down
101 changes: 101 additions & 0 deletions packages/kbn-securitysolution-grouping/src/hooks/state/reducer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { act, renderHook } from '@testing-library/react-hooks';
import { useReducer } from 'react';
import { groupActions, groupsReducerWithStorage, initialState } from '.';
import { defaultGroup, LOCAL_STORAGE_GROUPING_KEY } from '../..';

const groupingOptions = [
{ label: 'ruleName', key: 'kibana.alert.rule.name' },
{ label: 'userName', key: 'user.name' },
{ label: 'hostName', key: 'host.name' },
{ label: 'sourceIP', key: 'source.ip' },
];

const groupingId = 'test-table';

const groupById = {
[groupingId]: {
...defaultGroup,
options: groupingOptions,
activeGroup: 'host.name',
},
};

const setItem = jest.spyOn(window.localStorage.__proto__, 'setItem');
const getItem = jest.spyOn(window.localStorage.__proto__, 'getItem');

describe('grouping reducer', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('updateGroupOptions, initializes group with defaults and provided newOptionList', () => {
const { result } = renderHook(() => useReducer(groupsReducerWithStorage, initialState));
let [groupingState, dispatch] = result.current;
expect(groupingState.groupById).toEqual({});
act(() => {
dispatch(groupActions.updateGroupOptions({ id: groupingId, newOptionList: groupingOptions }));
});
[groupingState, dispatch] = result.current;
expect(groupingState.groupById[groupingId]).toEqual({
...defaultGroup,
options: groupingOptions,
});

expect(getItem).toHaveBeenCalledTimes(2);
expect(setItem).toHaveBeenCalledWith(
LOCAL_STORAGE_GROUPING_KEY,
JSON.stringify(groupingState.groupById)
);
});
it('updateActiveGroup', () => {
const { result } = renderHook(() =>
useReducer(groupsReducerWithStorage, {
...initialState,
groupById,
})
);
let [groupingState, dispatch] = result.current;
expect(groupingState.groupById[groupingId].activeGroup).toEqual('host.name');
act(() => {
dispatch(groupActions.updateActiveGroup({ id: groupingId, activeGroup: 'user.name' }));
});
[groupingState, dispatch] = result.current;
expect(groupingState.groupById[groupingId].activeGroup).toEqual('user.name');
});
it('updateGroupActivePage', () => {
const { result } = renderHook(() =>
useReducer(groupsReducerWithStorage, {
...initialState,
groupById,
})
);
let [groupingState, dispatch] = result.current;
expect(groupingState.groupById[groupingId].activePage).toEqual(0);
act(() => {
dispatch(groupActions.updateGroupActivePage({ id: groupingId, activePage: 12 }));
});
[groupingState, dispatch] = result.current;
expect(groupingState.groupById[groupingId].activePage).toEqual(12);
});
it('updateGroupItemsPerPage', () => {
const { result } = renderHook(() => useReducer(groupsReducerWithStorage, initialState));
let [groupingState, dispatch] = result.current;
act(() => {
dispatch(groupActions.updateGroupOptions({ id: groupingId, newOptionList: groupingOptions }));
});
[groupingState, dispatch] = result.current;
expect(groupingState.groupById[groupingId].itemsPerPage).toEqual(25);
act(() => {
dispatch(groupActions.updateGroupItemsPerPage({ id: groupingId, itemsPerPage: 12 }));
});
[groupingState, dispatch] = result.current;
expect(groupingState.groupById[groupingId].itemsPerPage).toEqual(12);
});
});
19 changes: 17 additions & 2 deletions packages/kbn-securitysolution-grouping/src/hooks/state/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const groupsReducer = (state: GroupMap, action: Action, groupsById: GroupsById)
groupById: {
...groupsById,
[id]: {
...defaultGroup,
...groupsById[id],
activeGroup,
},
Expand All @@ -45,6 +46,7 @@ const groupsReducer = (state: GroupMap, action: Action, groupsById: GroupsById)
groupById: {
...groupsById,
[id]: {
...defaultGroup,
...groupsById[id],
activePage,
},
Expand All @@ -58,6 +60,7 @@ const groupsReducer = (state: GroupMap, action: Action, groupsById: GroupsById)
groupById: {
...groupsById,
[id]: {
...defaultGroup,
...groupsById[id],
itemsPerPage,
},
Expand All @@ -82,14 +85,26 @@ const groupsReducer = (state: GroupMap, action: Action, groupsById: GroupsById)
throw Error(`Unknown grouping action`);
};
export const groupsReducerWithStorage = (state: GroupMap, action: Action) => {
let groupsInStorage = {};
let groupsInStorage: GroupsById = {};
if (storage) {
groupsInStorage = getAllGroupsInStorage(storage);
}
const trackedGroupIds = Object.keys(state.groupById);

const adjustedStorageGroups = Object.entries(groupsInStorage).reduce(
(acc: GroupsById, [key, group]) => ({
...acc,
[key]: {
// reset page to 0 if is initial state
...(trackedGroupIds.includes(key) ? group : { ...group, activePage: 0 }),
},
}),
{} as GroupsById
);

const groupsById: GroupsById = {
...state.groupById,
...groupsInStorage,
...adjustedStorageGroups,
};

const newState = groupsReducer(state, action, groupsById);
Expand Down
Loading

0 comments on commit eef9265

Please sign in to comment.