[8.9] [Security Solution] Fix rounding in top alerts chart on alerts page (#162647) (#162741)

# Backport

This will backport the following commits from `main` to `8.9`:
- [[Security Solution] Fix rounding in top alerts chart on alerts page
(#162647)](https://github.com/elastic/kibana/pull/162647)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"christineweng","email":"18648970+christineweng@users.noreply.github.com"},"sourceCommit":{"committedDate":"2023-07-28T20:04:37Z","message":"[Security
Solution] Fix rounding in top alerts chart on alerts page
(#162647)\n\n## Summary\r\n\r\nThis PR addresses
(https://github.com/elastic/kibana/issues/162521). In\r\nextreme cases
when there are a lot of alerts without user names, entries\r\nin top
alerts chart disappear. This is due to entries being rounded to
3\r\ndecimal places, when denominator grows too big, the percentages
are\r\nrounded to zero.\r\n\r\nThis PR removed the rounding and added a
label `<1%` when top alerts\r\ngrouping drops below 1%. User should
always see entries if they
are\r\navailable.\r\n\r\n\r\n![image](42ae8453-ccc7-4c71-8bd0-27a79b9b8318)\r\n\r\n**How
to test**\r\nFirst generate a few alerts with user name:\r\n- Go to
`security_solution` folder:
`cd\r\nx-pack/plugins/security_solution`\r\n- Run resolver generate data
script `yarn test:generate --ne 5\r\n--relAlerts 1` - this command will
generate 5 hosts each with 1 alert\r\n\r\nTo generate alerts without
user names, comment lines that populate user\r\nnames
in\r\n`x-pack/plugins/security_solution/common/endpoint/generate_data.ts`\r\n(there
are multiple places)\r\n```\r\nuser: {\r\n domain:
this.randomString(10),\r\n name: this.randomString(10),\r\n
},\r\n```\r\n- run the script again `yarn test:generate --ne 10
--relAlerts 50` -\r\nthis command will generate 50 alerts each for 10
hosts\r\n\r\n### Checklist\r\n\r\nDelete any items that are not
applicable to this PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"af15cc19c429aa4dd318e21c74e47fb0f13bad8c","branchLabelMapping":{"^v8.10.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team:Threat
Hunting:Investigations","v8.10.0","v8.9.1"],"number":162647,"url":"https://github.com/elastic/kibana/pull/162647","mergeCommit":{"message":"[Security
Solution] Fix rounding in top alerts chart on alerts page
(#162647)\n\n## Summary\r\n\r\nThis PR addresses
(https://github.com/elastic/kibana/issues/162521). In\r\nextreme cases
when there are a lot of alerts without user names, entries\r\nin top
alerts chart disappear. This is due to entries being rounded to
3\r\ndecimal places, when denominator grows too big, the percentages
are\r\nrounded to zero.\r\n\r\nThis PR removed the rounding and added a
label `<1%` when top alerts\r\ngrouping drops below 1%. User should
always see entries if they
are\r\navailable.\r\n\r\n\r\n![image](42ae8453-ccc7-4c71-8bd0-27a79b9b8318)\r\n\r\n**How
to test**\r\nFirst generate a few alerts with user name:\r\n- Go to
`security_solution` folder:
`cd\r\nx-pack/plugins/security_solution`\r\n- Run resolver generate data
script `yarn test:generate --ne 5\r\n--relAlerts 1` - this command will
generate 5 hosts each with 1 alert\r\n\r\nTo generate alerts without
user names, comment lines that populate user\r\nnames
in\r\n`x-pack/plugins/security_solution/common/endpoint/generate_data.ts`\r\n(there
are multiple places)\r\n```\r\nuser: {\r\n domain:
this.randomString(10),\r\n name: this.randomString(10),\r\n
},\r\n```\r\n- run the script again `yarn test:generate --ne 10
--relAlerts 50` -\r\nthis command will generate 50 alerts each for 10
hosts\r\n\r\n### Checklist\r\n\r\nDelete any items that are not
applicable to this PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"af15cc19c429aa4dd318e21c74e47fb0f13bad8c"}},"sourceBranch":"main","suggestedTargetBranches":["8.9"],"targetPullRequestStates":[{"branch":"main","label":"v8.10.0","labelRegex":"^v8.10.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/162647","number":162647,"mergeCommit":{"message":"[Security
Solution] Fix rounding in top alerts chart on alerts page
(#162647)\n\n## Summary\r\n\r\nThis PR addresses
(https://github.com/elastic/kibana/issues/162521). In\r\nextreme cases
when there are a lot of alerts without user names, entries\r\nin top
alerts chart disappear. This is due to entries being rounded to
3\r\ndecimal places, when denominator grows too big, the percentages
are\r\nrounded to zero.\r\n\r\nThis PR removed the rounding and added a
label `<1%` when top alerts\r\ngrouping drops below 1%. User should
always see entries if they
are\r\navailable.\r\n\r\n\r\n![image](42ae8453-ccc7-4c71-8bd0-27a79b9b8318)\r\n\r\n**How
to test**\r\nFirst generate a few alerts with user name:\r\n- Go to
`security_solution` folder:
`cd\r\nx-pack/plugins/security_solution`\r\n- Run resolver generate data
script `yarn test:generate --ne 5\r\n--relAlerts 1` - this command will
generate 5 hosts each with 1 alert\r\n\r\nTo generate alerts without
user names, comment lines that populate user\r\nnames
in\r\n`x-pack/plugins/security_solution/common/endpoint/generate_data.ts`\r\n(there
are multiple places)\r\n```\r\nuser: {\r\n domain:
this.randomString(10),\r\n name: this.randomString(10),\r\n
},\r\n```\r\n- run the script again `yarn test:generate --ne 10
--relAlerts 50` -\r\nthis command will generate 50 alerts each for 10
hosts\r\n\r\n### Checklist\r\n\r\nDelete any items that are not
applicable to this PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"af15cc19c429aa4dd318e21c74e47fb0f13bad8c"}},{"branch":"8.9","label":"v8.9.1","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: christineweng <18648970+christineweng@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2023-08-03 16:34:51 -04:00 committed by GitHub
parent 4cb1cc2197
commit 26eb89a395
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 76 additions and 27 deletions

View file

@ -72,7 +72,7 @@ describe('Alert by grouping', () => {
).toContain(parsedAlerts[i].label);
expect(
container.querySelector(`[data-test-subj="progress-bar-${alert.key}"]`)?.textContent
).toContain(parsedAlerts[i].percentage.toString());
).toContain(parsedAlerts[i].percentageLabel);
}
});
});

View file

@ -20,7 +20,7 @@ import React, { useState } from 'react';
import styled from 'styled-components';
import type { AlertsProgressBarData, GroupBySelection } from './types';
import type { AddFilterProps } from '../common/types';
import { getNonEmptyPercent } from './helpers';
import { getAggregateData } from './helpers';
import { DefaultDraggable } from '../../../../common/components/draggables';
import * as i18n from './translations';
@ -61,7 +61,7 @@ export const AlertsProgressBar: React.FC<AlertsProcessBarProps> = ({
const onButtonClick = () => setIsPopoverOpen(!isPopoverOpen);
const closePopover = () => setIsPopoverOpen(false);
const validPercent = getNonEmptyPercent(data);
const [nonEmpty, formattedNonEmptyPercent] = getAggregateData(data);
const dataStatsButton = (
<EuiButtonIcon
@ -75,7 +75,7 @@ export const AlertsProgressBar: React.FC<AlertsProcessBarProps> = ({
const dataStatsMessage = (
<DataStatsWrapper>
<EuiPopoverTitle>{i18n.DATA_STATISTICS_TITLE(validPercent.toString())}</EuiPopoverTitle>
<EuiPopoverTitle>{i18n.DATA_STATISTICS_TITLE(formattedNonEmptyPercent)}</EuiPopoverTitle>
<EuiText size="s">
{i18n.DATA_STATISTICS_MESSAGE(groupBySelection)}
<EuiLink
@ -137,7 +137,7 @@ export const AlertsProgressBar: React.FC<AlertsProcessBarProps> = ({
<>
<StyledEuiHorizontalRule />
<ProgressWrapper data-test-subj="progress-bar" className="eui-yScroll">
{validPercent === 0 ? (
{nonEmpty === 0 ? (
<>
<EuiText size="s" textAlign="center" data-test-subj="empty-proress-bar">
{i18n.EMPTY_DATA_MESSAGE}
@ -153,10 +153,10 @@ export const AlertsProgressBar: React.FC<AlertsProcessBarProps> = ({
<EuiProgress
valueText={
<EuiText size="xs" color="default">
<strong>{`${item.percentage}%`}</strong>
<strong>{item.percentageLabel}</strong>
</EuiText>
}
max={100}
max={1}
color={`vis9`}
size="s"
value={item.percentage}

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { parseAlertsGroupingData, getNonEmptyPercent } from './helpers';
import { parseAlertsGroupingData, getAggregateData, formatPercentage } from './helpers';
import * as mock from './mock_data';
import type { AlertsByGroupingAgg } from './types';
import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types';
@ -25,10 +25,18 @@ describe('parse progress bar data', () => {
});
});
describe('test non-empty percentage', () => {
test('should return correct non-empty percentage', () => {
const expected = Math.round((620 / 630) * 100);
const res = getNonEmptyPercent(mock.parsedAlerts);
expect(res).toEqual(expected);
describe('test getAggregateData', () => {
test('should return correct non-empty value and percentage', () => {
const res = getAggregateData(mock.parsedAlerts);
expect(res).toEqual([620, '98.4%']);
expect(getAggregateData(mock.parsedLargeEmptyAlerts)).toEqual([1, '<1%']);
});
});
describe('test formatPercentage ', () => {
test('should return percetange string', () => {
expect(formatPercentage(0.255)).toEqual('25.5%');
expect(formatPercentage(0.01)).toEqual('1%');
expect(formatPercentage(0.005)).toEqual('<1%');
});
});

View file

@ -12,6 +12,10 @@ import type { BucketItem } from '../../../../../common/search_strategy/security_
import type { SummaryChartsData, SummaryChartsAgg } from '../alerts_summary_charts_panel/types';
import * as i18n from './translations';
export const formatPercentage = (percent: number): string => {
return percent > 0 && percent < 0.01 ? '<1%' : `${(Math.round(percent * 1000) / 10).toString()}%`;
};
export const parseAlertsGroupingData = (
response: AlertSearchResponse<{}, AlertsByGroupingAgg>
): AlertsProgressBarData[] => {
@ -31,7 +35,8 @@ export const parseAlertsGroupingData = (
return {
key: group.key,
value: group.doc_count,
percentage: Math.round((group.doc_count / total) * 1000) / 10,
percentage: group.doc_count / total,
percentageLabel: formatPercentage(group.doc_count / total),
label: group.key,
};
});
@ -40,7 +45,8 @@ export const parseAlertsGroupingData = (
topAlerts.push({
key: 'Other',
value: other,
percentage: Math.round((other / total) * 1000) / 10,
percentage: other / total,
percentageLabel: formatPercentage(other / total),
label: i18n.OTHER,
});
}
@ -49,7 +55,8 @@ export const parseAlertsGroupingData = (
topAlerts.push({
key: '-',
value: emptyFieldCount,
percentage: Math.round((emptyFieldCount / total) * 1000) / 10,
percentage: emptyFieldCount / total,
percentageLabel: formatPercentage(emptyFieldCount / total),
label: '-',
});
}
@ -57,8 +64,8 @@ export const parseAlertsGroupingData = (
return topAlerts;
};
export const getNonEmptyPercent = (topAlerts: AlertsProgressBarData[]): number => {
const consolidated = topAlerts.reduce(
export const getAggregateData = (topAlerts: AlertsProgressBarData[]): [number, string] => {
const { total, nonEmpty } = topAlerts.reduce(
(ret, cur) => {
ret.total += cur.value;
if (cur.key !== '-') {
@ -68,9 +75,7 @@ export const getNonEmptyPercent = (topAlerts: AlertsProgressBarData[]): number =
},
{ total: 0, nonEmpty: 0 }
);
return consolidated.total > 0
? Math.round((consolidated.nonEmpty / consolidated.total) * 100)
: 0;
return [nonEmpty, formatPercentage(total === 0 ? 0 : nonEmpty / total)];
};
export const getIsAlertsProgressBarData = (

View file

@ -105,9 +105,44 @@ export const query = {
};
export const parsedAlerts = [
{ key: 'Host-v5biklvcy8', value: 234, label: 'Host-v5biklvcy8', percentage: 37.1 },
{ key: 'Host-5y1uprxfv2', value: 186, label: 'Host-5y1uprxfv2', percentage: 29.5 },
{ key: 'Host-ssf1mhgy5c', value: 150, label: 'Host-ssf1mhgy5c', percentage: 23.8 },
{ key: 'Other', value: 50, label: 'Other', percentage: 7.9 },
{ key: '-', value: 10, label: '-', percentage: 1.6 },
{
key: 'Host-v5biklvcy8',
value: 234,
label: 'Host-v5biklvcy8',
percentage: 0.37142857142857144,
percentageLabel: '37.1%',
},
{
key: 'Host-5y1uprxfv2',
value: 186,
label: 'Host-5y1uprxfv2',
percentage: 0.29523809523809524,
percentageLabel: '29.5%',
},
{
key: 'Host-ssf1mhgy5c',
value: 150,
label: 'Host-ssf1mhgy5c',
percentage: 0.23809523809523808,
percentageLabel: '23.8%',
},
{
key: 'Other',
value: 50,
label: 'Other',
percentage: 0.07936507936507936,
percentageLabel: '7.9%',
},
{ key: '-', value: 10, label: '-', percentage: 0.015873015873015872, percentageLabel: '1.6%' },
];
export const parsedLargeEmptyAlerts = [
{
key: 'Host-v5biklvcy8',
value: 1,
label: 'Host-v5biklvcy8',
percentage: 0.0001,
percentageLabel: '<1%',
},
{ key: '-', value: 9999, label: '-', percentage: 0.9999, percentageLabel: '100%' },
];

View file

@ -55,7 +55,7 @@ export const SOURCE_LABEL = i18n.translate(
export const DATA_STATISTICS_TITLE = (percent: string) =>
i18n.translate('xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.dataStatsTitle', {
values: { percent },
defaultMessage: `This field exists in {percent}% of alerts.`,
defaultMessage: `This field exists in {percent} of alerts.`,
});
export const DATA_STATISTICS_MESSAGE = (groupbySelection: string) =>

View file

@ -21,5 +21,6 @@ export interface AlertsProgressBarData {
key: string;
value: number;
percentage: number;
percentageLabel: string;
label: string;
}