mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# 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\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\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\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:
parent
4cb1cc2197
commit
26eb89a395
7 changed files with 76 additions and 27 deletions
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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%');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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%' },
|
||||
];
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -21,5 +21,6 @@ export interface AlertsProgressBarData {
|
|||
key: string;
|
||||
value: number;
|
||||
percentage: number;
|
||||
percentageLabel: string;
|
||||
label: string;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue