[Security solution] Grouping package - generic type argument (#152628)

This commit is contained in:
Steph Milovic 2023-03-06 12:46:12 -07:00 committed by GitHub
parent 1f6de13232
commit dd75719f8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 84 additions and 66 deletions

View file

@ -18,7 +18,7 @@ import {
} from './src';
import type { NamedAggregation, GroupingFieldTotalAggregation, GroupingAggregation } from './src';
export const getGrouping = (props: GroupingProps): React.ReactElement<GroupingProps> => (
export const getGrouping = <T,>(props: GroupingProps<T>): React.ReactElement<GroupingProps<T>> => (
<Grouping {...props} />
);

View file

@ -21,21 +21,21 @@ import { statsContainerCss } from '../styles';
import { TAKE_ACTION } from '../translations';
import type { RawBucket } from '../types';
interface GroupStatsProps {
interface GroupStatsProps<T> {
badgeMetricStats?: BadgeMetric[];
bucket: RawBucket;
bucket: RawBucket<T>;
customMetricStats?: CustomMetric[];
onTakeActionsOpen?: () => void;
takeActionItems: JSX.Element[];
}
const GroupStatsComponent = ({
const GroupStatsComponent = <T,>({
badgeMetricStats,
bucket,
customMetricStats,
onTakeActionsOpen,
takeActionItems,
}: GroupStatsProps) => {
}: GroupStatsProps<T>) => {
const [isPopoverOpen, setPopover] = useState(false);
const onButtonClick = useCallback(

View file

@ -25,16 +25,16 @@ export interface CustomMetric {
customStatRenderer: JSX.Element;
}
interface GroupPanelProps {
interface GroupPanelProps<T> {
customAccordionButtonClassName?: string;
customAccordionClassName?: string;
extraAction?: React.ReactNode;
forceState?: 'open' | 'closed';
groupBucket: RawBucket;
groupBucket: RawBucket<T>;
groupPanelRenderer?: JSX.Element;
isLoading: boolean;
level?: number;
onToggleGroup?: (isOpen: boolean, groupBucket: RawBucket) => void;
onToggleGroup?: (isOpen: boolean, groupBucket: RawBucket<T>) => void;
renderChildComponent: (groupFilter: Filter[]) => React.ReactNode;
selectedGroup: string;
}
@ -51,7 +51,7 @@ const DefaultGroupPanelRenderer = ({ title }: { title: string }) => (
</div>
);
const GroupPanelComponent = ({
const GroupPanelComponent = <T,>({
customAccordionButtonClassName = 'groupingAccordionForm__button',
customAccordionClassName = 'groupingAccordionForm',
extraAction,
@ -63,7 +63,7 @@ const GroupPanelComponent = ({
onToggleGroup,
renderChildComponent,
selectedGroup,
}: GroupPanelProps) => {
}: GroupPanelProps<T>) => {
const groupFieldValue = useMemo(() => firstNonNullValue(groupBucket.key), [groupBucket.key]);
const groupFilters = useMemo(

View file

@ -25,11 +25,11 @@ import { groupingContainerCss, groupsUnitCountCss } from './styles';
import { GROUPS_UNIT } from './translations';
import type { GroupingAggregation, GroupingFieldTotalAggregation, RawBucket } from './types';
export interface GroupingProps {
badgeMetricStats?: (fieldBucket: RawBucket) => BadgeMetric[];
customMetricStats?: (fieldBucket: RawBucket) => CustomMetric[];
data?: GroupingAggregation & GroupingFieldTotalAggregation;
groupPanelRenderer?: (fieldBucket: RawBucket) => JSX.Element | undefined;
export interface GroupingProps<T> {
badgeMetricStats?: (fieldBucket: RawBucket<T>) => BadgeMetric[];
customMetricStats?: (fieldBucket: RawBucket<T>) => CustomMetric[];
data?: GroupingAggregation<T> & GroupingFieldTotalAggregation;
groupPanelRenderer?: (fieldBucket: RawBucket<T>) => JSX.Element | undefined;
groupsSelector?: JSX.Element;
inspectButton?: JSX.Element;
isLoading: boolean;
@ -46,7 +46,7 @@ export interface GroupingProps {
unit?: (n: number) => string;
}
const GroupingComponent = ({
const GroupingComponent = <T,>({
badgeMetricStats,
customMetricStats,
data,
@ -59,9 +59,9 @@ const GroupingComponent = ({
selectedGroup,
takeActionItems,
unit = defaultUnit,
}: GroupingProps) => {
}: GroupingProps<T>) => {
const [trigger, setTrigger] = useState<
Record<string, { state: 'open' | 'closed' | undefined; selectedBucket: RawBucket }>
Record<string, { state: 'open' | 'closed' | undefined; selectedBucket: RawBucket<T> }>
>({});
const groupsNumber = data?.groupsNumber?.value ?? 0;
@ -196,4 +196,4 @@ const GroupingComponent = ({
);
};
export const Grouping = React.memo(GroupingComponent);
export const Grouping = React.memo(GroupingComponent) as typeof GroupingComponent;

View file

@ -15,41 +15,12 @@ export interface GenericBuckets {
export const NONE_GROUP_KEY = 'none';
export type RawBucket = GenericBuckets & {
alertsCount?: {
value?: number | null; // Elasticsearch returns `null` when a sub-aggregation cannot be computed
};
severitiesSubAggregation?: {
buckets?: GenericBuckets[];
};
countSeveritySubAggregation?: {
value?: number | null; // Elasticsearch returns `null` when a sub-aggregation cannot be computed
};
usersCountAggregation?: {
value?: number | null; // Elasticsearch returns `null` when a sub-aggregation cannot be computed
};
hostsCountAggregation?: {
value?: number | null; // Elasticsearch returns `null` when a sub-aggregation cannot be computed
};
rulesCountAggregation?: {
value?: number | null; // Elasticsearch returns `null` when a sub-aggregation cannot be computed
};
ruleTags?: {
doc_count_error_upper_bound?: number;
sum_other_doc_count?: number;
buckets?: GenericBuckets[];
};
stackByMultipleFields1?: {
buckets?: GenericBuckets[];
doc_count_error_upper_bound?: number;
sum_other_doc_count?: number;
};
};
export type RawBucket<T> = GenericBuckets & T;
/** Defines the shape of the aggregation returned by Elasticsearch */
export interface GroupingAggregation {
export interface GroupingAggregation<T> {
stackByMultipleFields0?: {
buckets?: RawBucket[];
buckets?: Array<RawBucket<T>>;
};
groupsCount0?: {
value?: number | null;
@ -60,11 +31,3 @@ export type GroupingFieldTotalAggregation = Record<
string,
{ value?: number | null; buckets?: Array<{ doc_count?: number | null }> }
>;
export type FlattenedBucket = Pick<
RawBucket,
'doc_count' | 'key' | 'key_as_string' | 'alertsCount'
> & {
stackByMultipleFields1Key?: string;
stackByMultipleFields1DocCount?: number;
};

View file

@ -19,6 +19,7 @@ import type {
RawBucket,
} from '@kbn/securitysolution-grouping';
import { getGrouping, isNoneGroup } from '@kbn/securitysolution-grouping';
import type { AlertsGroupingAggregation } from './grouping_settings/types';
import { useGetGroupSelector } from '../../../common/containers/grouping/hooks/use_get_group_selector';
import type { Status } from '../../../../common/detection_engine/schemas/common';
import { defaultGroup } from '../../../common/store/grouping/defaults';
@ -183,7 +184,10 @@ export const GroupedAlertsTableComponent: React.FC<AlertsTableComponentProps> =
request,
response,
setQuery: setAlertsQuery,
} = useQueryAlerts<{}, GroupingAggregation & GroupingFieldTotalAggregation>({
} = useQueryAlerts<
{},
GroupingAggregation<AlertsGroupingAggregation> & GroupingFieldTotalAggregation
>({
query: queryGroups,
indexName: signalIndexName,
queryName: ALERTS_QUERY_NAMES.ALERTS_GROUPING,
@ -236,12 +240,12 @@ export const GroupedAlertsTableComponent: React.FC<AlertsTableComponentProps> =
isNoneGroup(selectedGroup)
? renderChildComponent([])
: getGrouping({
badgeMetricStats: (fieldBucket: RawBucket) =>
badgeMetricStats: (fieldBucket: RawBucket<AlertsGroupingAggregation>) =>
getSelectedGroupBadgeMetrics(selectedGroup, fieldBucket),
customMetricStats: (fieldBucket: RawBucket) =>
customMetricStats: (fieldBucket: RawBucket<AlertsGroupingAggregation>) =>
getSelectedGroupCustomMetrics(selectedGroup, fieldBucket),
data: alertsGroupsData?.aggregations,
groupPanelRenderer: (fieldBucket: RawBucket) =>
groupPanelRenderer: (fieldBucket: RawBucket<AlertsGroupingAggregation>) =>
getSelectedGroupButtonContent(selectedGroup, fieldBucket),
groupsSelector,
inspectButton: inspect,

View file

@ -19,12 +19,16 @@ import { euiThemeVars } from '@kbn/ui-theme';
import { isArray } from 'lodash/fp';
import React from 'react';
import type { RawBucket } from '@kbn/securitysolution-grouping';
import type { AlertsGroupingAggregation } from './types';
import { firstNonNullValue } from '../../../../../common/endpoint/models/ecs_safety_helpers';
import type { GenericBuckets } from '../../../../../common/search_strategy';
import { PopoverItems } from '../../../../common/components/popover_items';
import { COLUMN_TAGS } from '../../../pages/detection_engine/rules/translations';
export const getSelectedGroupButtonContent = (selectedGroup: string, bucket: RawBucket) => {
export const getSelectedGroupButtonContent = (
selectedGroup: string,
bucket: RawBucket<AlertsGroupingAggregation>
) => {
switch (selectedGroup) {
case 'kibana.alert.rule.name':
return isArray(bucket.key) ? (

View file

@ -8,6 +8,7 @@
import { EuiIcon } from '@elastic/eui';
import React from 'react';
import type { RawBucket } from '@kbn/securitysolution-grouping';
import type { AlertsGroupingAggregation } from './types';
import * as i18n from '../translations';
const getSingleGroupSeverity = (severity?: string) => {
@ -63,7 +64,10 @@ const multiSeverity = (
</>
);
export const getSelectedGroupBadgeMetrics = (selectedGroup: string, bucket: RawBucket) => {
export const getSelectedGroupBadgeMetrics = (
selectedGroup: string,
bucket: RawBucket<AlertsGroupingAggregation>
) => {
const defaultBadges = [
{
title: i18n.STATS_GROUP_ALERTS,
@ -131,7 +135,10 @@ export const getSelectedGroupBadgeMetrics = (selectedGroup: string, bucket: RawB
];
};
export const getSelectedGroupCustomMetrics = (selectedGroup: string, bucket: RawBucket) => {
export const getSelectedGroupCustomMetrics = (
selectedGroup: string,
bucket: RawBucket<AlertsGroupingAggregation>
) => {
const singleSeverityComponent =
bucket.severitiesSubAggregation?.buckets && bucket.severitiesSubAggregation?.buckets?.length
? getSingleGroupSeverity(bucket.severitiesSubAggregation?.buckets[0].key.toString())

View file

@ -0,0 +1,40 @@
/*
* 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 type { GenericBuckets } from '@kbn/securitysolution-grouping/src';
// Elasticsearch returns `null` when a sub-aggregation cannot be computed
type NumberOrNull = number | null;
export interface AlertsGroupingAggregation {
alertsCount?: {
value?: NumberOrNull;
};
severitiesSubAggregation?: {
buckets?: GenericBuckets[];
};
countSeveritySubAggregation?: {
value?: NumberOrNull;
};
usersCountAggregation?: {
value?: NumberOrNull;
};
hostsCountAggregation?: {
value?: NumberOrNull;
};
rulesCountAggregation?: {
value?: NumberOrNull;
};
ruleTags?: {
doc_count_error_upper_bound?: number;
sum_other_doc_count?: number;
buckets?: GenericBuckets[];
};
stackByMultipleFields1?: {
buckets?: GenericBuckets[];
doc_count_error_upper_bound?: number;
sum_other_doc_count?: number;
};
}