mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Fix Incorrect alerts are displayed in timeline when navigating from Detection & Responses page (#144319)
* Fix Incorrect alerts are displayed in the timeline when navigating from Detection & Responses page
This commit is contained in:
parent
3f620428b3
commit
10dc2c6f78
9 changed files with 219 additions and 14 deletions
|
@ -13,6 +13,7 @@ import { TestProviders } from '../../../../common/mock';
|
|||
import { parsedVulnerableHostsAlertsResult } from './mock_data';
|
||||
import type { UseHostAlertsItems } from './use_host_alerts_items';
|
||||
import { HostAlertsTable } from './host_alerts_table';
|
||||
import { openAlertsFilter } from '../utils';
|
||||
|
||||
const mockGetAppUrl = jest.fn();
|
||||
jest.mock('../../../../common/lib/kibana/hooks', () => {
|
||||
|
@ -25,6 +26,15 @@ jest.mock('../../../../common/lib/kibana/hooks', () => {
|
|||
};
|
||||
});
|
||||
|
||||
const mockOpenTimelineWithFilters = jest.fn();
|
||||
jest.mock('../hooks/use_navigate_to_timeline', () => {
|
||||
return {
|
||||
useNavigateToTimeline: () => ({
|
||||
openTimelineWithFilters: mockOpenTimelineWithFilters,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
type UseHostAlertsItemsReturn = ReturnType<UseHostAlertsItems>;
|
||||
const defaultUseHostAlertsItemsReturn: UseHostAlertsItemsReturn = {
|
||||
items: [],
|
||||
|
@ -124,4 +134,42 @@ describe('HostAlertsTable', () => {
|
|||
fireEvent.click(page3);
|
||||
expect(mockSetPage).toHaveBeenCalledWith(2);
|
||||
});
|
||||
|
||||
it('should open timeline with filters when total alerts is clicked', () => {
|
||||
mockUseHostAlertsItemsReturn({ items: [parsedVulnerableHostsAlertsResult[0]] });
|
||||
const { getByTestId } = renderComponent();
|
||||
|
||||
fireEvent.click(getByTestId('hostSeverityAlertsTable-totalAlertsLink'));
|
||||
|
||||
expect(mockOpenTimelineWithFilters).toHaveBeenCalledWith([
|
||||
[
|
||||
{
|
||||
field: 'host.name',
|
||||
value: 'Host-342m5gl1g2',
|
||||
},
|
||||
openAlertsFilter,
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should open timeline with filters when critical alert count is clicked', () => {
|
||||
mockUseHostAlertsItemsReturn({ items: [parsedVulnerableHostsAlertsResult[0]] });
|
||||
const { getByTestId } = renderComponent();
|
||||
|
||||
fireEvent.click(getByTestId('hostSeverityAlertsTable-criticalLink'));
|
||||
|
||||
expect(mockOpenTimelineWithFilters).toHaveBeenCalledWith([
|
||||
[
|
||||
{
|
||||
field: 'host.name',
|
||||
value: 'Host-342m5gl1g2',
|
||||
},
|
||||
openAlertsFilter,
|
||||
{
|
||||
field: 'kibana.alert.severity',
|
||||
value: 'critical',
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ import { HostDetailsLink } from '../../../../common/components/links';
|
|||
import { useQueryToggle } from '../../../../common/containers/query_toggle';
|
||||
import { useNavigateToTimeline } from '../hooks/use_navigate_to_timeline';
|
||||
import * as i18n from '../translations';
|
||||
import { ITEMS_PER_PAGE, SEVERITY_COLOR } from '../utils';
|
||||
import { ITEMS_PER_PAGE, openAlertsFilter, SEVERITY_COLOR } from '../utils';
|
||||
import type { HostAlertsItem } from './use_host_alerts_items';
|
||||
import { useHostAlertsItems } from './use_host_alerts_items';
|
||||
|
||||
|
@ -53,7 +53,9 @@ export const HostAlertsTable = React.memo(({ signalIndexName }: HostAlertsTableP
|
|||
: undefined;
|
||||
|
||||
openTimelineWithFilters(
|
||||
severityFilter ? [[hostNameFilter, severityFilter]] : [[hostNameFilter]]
|
||||
severityFilter
|
||||
? [[hostNameFilter, openAlertsFilter, severityFilter]]
|
||||
: [[hostNameFilter, openAlertsFilter]]
|
||||
);
|
||||
},
|
||||
[openTimelineWithFilters]
|
||||
|
@ -133,7 +135,11 @@ const getTableColumns: GetTableColumns = (handleClick) => [
|
|||
name: i18n.ALERTS_TEXT,
|
||||
'data-test-subj': 'hostSeverityAlertsTable-totalAlerts',
|
||||
render: (totalAlerts: number, { hostName }) => (
|
||||
<EuiLink disabled={totalAlerts === 0} onClick={() => handleClick({ hostName })}>
|
||||
<EuiLink
|
||||
data-test-subj="hostSeverityAlertsTable-totalAlertsLink"
|
||||
disabled={totalAlerts === 0}
|
||||
onClick={() => handleClick({ hostName })}
|
||||
>
|
||||
<FormattedCount count={totalAlerts} />
|
||||
</EuiLink>
|
||||
),
|
||||
|
@ -144,6 +150,7 @@ const getTableColumns: GetTableColumns = (handleClick) => [
|
|||
render: (count: number, { hostName }) => (
|
||||
<EuiHealth data-test-subj="hostSeverityAlertsTable-critical" color={SEVERITY_COLOR.critical}>
|
||||
<EuiLink
|
||||
data-test-subj="hostSeverityAlertsTable-criticalLink"
|
||||
disabled={count === 0}
|
||||
onClick={() => handleClick({ hostName, severity: 'critical' })}
|
||||
>
|
||||
|
|
|
@ -8,13 +8,14 @@
|
|||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
|
||||
import { SecurityPageName } from '../../../../../common/constants';
|
||||
import { TestProviders } from '../../../../common/mock';
|
||||
import type { RuleAlertsTableProps } from './rule_alerts_table';
|
||||
import { RuleAlertsTable } from './rule_alerts_table';
|
||||
import type { RuleAlertsItem, UseRuleAlertsItems } from './use_rule_alerts_items';
|
||||
import { openAlertsFilter } from '../utils';
|
||||
|
||||
const mockGetAppUrl = jest.fn();
|
||||
jest.mock('../../../../common/lib/kibana/hooks', () => {
|
||||
|
@ -27,6 +28,15 @@ jest.mock('../../../../common/lib/kibana/hooks', () => {
|
|||
};
|
||||
});
|
||||
|
||||
const mockOpenTimelineWithFilters = jest.fn();
|
||||
jest.mock('../hooks/use_navigate_to_timeline', () => {
|
||||
return {
|
||||
useNavigateToTimeline: () => ({
|
||||
openTimelineWithFilters: mockOpenTimelineWithFilters,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
type UseRuleAlertsItemsReturn = ReturnType<UseRuleAlertsItems>;
|
||||
const defaultUseRuleAlertsItemsReturn: UseRuleAlertsItemsReturn = {
|
||||
items: [],
|
||||
|
@ -44,10 +54,11 @@ jest.mock('./use_rule_alerts_items', () => ({
|
|||
const defaultProps: RuleAlertsTableProps = {
|
||||
signalIndexName: '',
|
||||
};
|
||||
const ruleName = 'ruleName';
|
||||
const items: RuleAlertsItem[] = [
|
||||
{
|
||||
id: 'ruleId',
|
||||
name: 'ruleName',
|
||||
name: ruleName,
|
||||
last_alert_at: moment().subtract(1, 'day').format(),
|
||||
alert_count: 10,
|
||||
severity: 'high',
|
||||
|
@ -144,4 +155,25 @@ describe('RuleAlertsTable', () => {
|
|||
|
||||
expect(result.getByTestId('severityRuleAlertsTable-name')).toHaveAttribute('href', linkUrl);
|
||||
});
|
||||
|
||||
it('should open timeline with filters when total alerts is clicked', () => {
|
||||
mockUseRuleAlertsItemsReturn({ items });
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<RuleAlertsTable {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId('severityRuleAlertsTable-alertCountLink'));
|
||||
|
||||
expect(mockOpenTimelineWithFilters).toHaveBeenCalledWith([
|
||||
[
|
||||
{
|
||||
field: 'kibana.alert.rule.name',
|
||||
value: ruleName,
|
||||
},
|
||||
openAlertsFilter,
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ import { FormattedRelative } from '@kbn/i18n-react';
|
|||
import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import { HeaderSection } from '../../../../common/components/header_section';
|
||||
|
||||
import { SEVERITY_COLOR } from '../utils';
|
||||
import { openAlertsFilter, SEVERITY_COLOR } from '../utils';
|
||||
import * as i18n from '../translations';
|
||||
import type { RuleAlertsItem } from './use_rule_alerts_items';
|
||||
import { useRuleAlertsItems } from './use_rule_alerts_items';
|
||||
|
@ -90,7 +90,11 @@ export const getTableColumns: GetTableColumns = ({ getAppUrl, navigateTo, openRu
|
|||
name: i18n.RULE_ALERTS_COLUMN_ALERT_COUNT,
|
||||
'data-test-subj': 'severityRuleAlertsTable-alertCount',
|
||||
render: (alertCount: number, { name }) => (
|
||||
<EuiLink disabled={alertCount === 0} onClick={() => openRuleInTimeline(name)}>
|
||||
<EuiLink
|
||||
data-test-subj="severityRuleAlertsTable-alertCountLink"
|
||||
disabled={alertCount === 0}
|
||||
onClick={() => openRuleInTimeline(name)}
|
||||
>
|
||||
<FormattedCount count={alertCount} />
|
||||
</EuiLink>
|
||||
),
|
||||
|
@ -118,7 +122,9 @@ export const RuleAlertsTable = React.memo<RuleAlertsTableProps>(({ signalIndexNa
|
|||
|
||||
const openRuleInTimeline = useCallback(
|
||||
(ruleName: string) => {
|
||||
openTimelineWithFilters([[{ field: 'kibana.alert.rule.name', value: ruleName }]]);
|
||||
openTimelineWithFilters([
|
||||
[{ field: 'kibana.alert.rule.name', value: ruleName }, openAlertsFilter],
|
||||
]);
|
||||
},
|
||||
[openTimelineWithFilters]
|
||||
);
|
||||
|
|
|
@ -13,7 +13,9 @@ import { TestProviders } from '../../../../common/mock';
|
|||
import { parsedVulnerableUserAlertsResult } from './mock_data';
|
||||
import type { UseUserAlertsItems } from './use_user_alerts_items';
|
||||
import { UserAlertsTable } from './user_alerts_table';
|
||||
import { openAlertsFilter } from '../utils';
|
||||
|
||||
const userName = 'crffn20qcs';
|
||||
const mockGetAppUrl = jest.fn();
|
||||
jest.mock('../../../../common/lib/kibana/hooks', () => {
|
||||
const original = jest.requireActual('../../../../common/lib/kibana/hooks');
|
||||
|
@ -25,6 +27,15 @@ jest.mock('../../../../common/lib/kibana/hooks', () => {
|
|||
};
|
||||
});
|
||||
|
||||
const mockOpenTimelineWithFilters = jest.fn();
|
||||
jest.mock('../hooks/use_navigate_to_timeline', () => {
|
||||
return {
|
||||
useNavigateToTimeline: () => ({
|
||||
openTimelineWithFilters: mockOpenTimelineWithFilters,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
type UseUserAlertsItemsReturn = ReturnType<UseUserAlertsItems>;
|
||||
const defaultUseUserAlertsItemsReturn: UseUserAlertsItemsReturn = {
|
||||
items: [],
|
||||
|
@ -98,7 +109,7 @@ describe('UserAlertsTable', () => {
|
|||
mockUseUserAlertsItemsReturn({ items: [parsedVulnerableUserAlertsResult[0]] });
|
||||
const { queryByTestId } = renderComponent();
|
||||
|
||||
expect(queryByTestId('userSeverityAlertsTable-userName')).toHaveTextContent('crffn20qcs');
|
||||
expect(queryByTestId('userSeverityAlertsTable-userName')).toHaveTextContent(userName);
|
||||
expect(queryByTestId('userSeverityAlertsTable-totalAlerts')).toHaveTextContent('4');
|
||||
expect(queryByTestId('userSeverityAlertsTable-critical')).toHaveTextContent('4');
|
||||
expect(queryByTestId('userSeverityAlertsTable-high')).toHaveTextContent('1');
|
||||
|
@ -124,4 +135,42 @@ describe('UserAlertsTable', () => {
|
|||
fireEvent.click(page3);
|
||||
expect(mockSetPage).toHaveBeenCalledWith(2);
|
||||
});
|
||||
|
||||
it('should open timeline with filters when total alerts is clicked', () => {
|
||||
mockUseUserAlertsItemsReturn({ items: [parsedVulnerableUserAlertsResult[0]] });
|
||||
const { getByTestId } = renderComponent();
|
||||
|
||||
fireEvent.click(getByTestId('userSeverityAlertsTable-totalAlertsLink'));
|
||||
|
||||
expect(mockOpenTimelineWithFilters).toHaveBeenCalledWith([
|
||||
[
|
||||
{
|
||||
field: 'user.name',
|
||||
value: userName,
|
||||
},
|
||||
openAlertsFilter,
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should open timeline with filters when critical alerts link is clicked', () => {
|
||||
mockUseUserAlertsItemsReturn({ items: [parsedVulnerableUserAlertsResult[0]] });
|
||||
const { getByTestId } = renderComponent();
|
||||
|
||||
fireEvent.click(getByTestId('userSeverityAlertsTable-criticalLink'));
|
||||
|
||||
expect(mockOpenTimelineWithFilters).toHaveBeenCalledWith([
|
||||
[
|
||||
{
|
||||
field: 'user.name',
|
||||
value: userName,
|
||||
},
|
||||
openAlertsFilter,
|
||||
{
|
||||
field: 'kibana.alert.severity',
|
||||
value: 'critical',
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ import { UserDetailsLink } from '../../../../common/components/links';
|
|||
import { useQueryToggle } from '../../../../common/containers/query_toggle';
|
||||
import { useNavigateToTimeline } from '../hooks/use_navigate_to_timeline';
|
||||
import * as i18n from '../translations';
|
||||
import { ITEMS_PER_PAGE, SEVERITY_COLOR } from '../utils';
|
||||
import { ITEMS_PER_PAGE, openAlertsFilter, SEVERITY_COLOR } from '../utils';
|
||||
import type { UserAlertsItem } from './use_user_alerts_items';
|
||||
import { useUserAlertsItems } from './use_user_alerts_items';
|
||||
|
||||
|
@ -53,7 +53,9 @@ export const UserAlertsTable = React.memo(({ signalIndexName }: UserAlertsTableP
|
|||
: undefined;
|
||||
|
||||
openTimelineWithFilters(
|
||||
severityFilter ? [[userNameFilter, severityFilter]] : [[userNameFilter]]
|
||||
severityFilter
|
||||
? [[userNameFilter, openAlertsFilter, severityFilter]]
|
||||
: [[userNameFilter, openAlertsFilter]]
|
||||
);
|
||||
},
|
||||
[openTimelineWithFilters]
|
||||
|
@ -132,7 +134,11 @@ const getTableColumns: GetTableColumns = (handleClick) => [
|
|||
name: i18n.ALERTS_TEXT,
|
||||
'data-test-subj': 'userSeverityAlertsTable-totalAlerts',
|
||||
render: (totalAlerts: number, { userName }) => (
|
||||
<EuiLink disabled={totalAlerts === 0} onClick={() => handleClick({ userName })}>
|
||||
<EuiLink
|
||||
data-test-subj="userSeverityAlertsTable-totalAlertsLink"
|
||||
disabled={totalAlerts === 0}
|
||||
onClick={() => handleClick({ userName })}
|
||||
>
|
||||
<FormattedCount count={totalAlerts} />
|
||||
</EuiLink>
|
||||
),
|
||||
|
@ -143,6 +149,7 @@ const getTableColumns: GetTableColumns = (handleClick) => [
|
|||
render: (count: number, { userName }) => (
|
||||
<EuiHealth data-test-subj="userSeverityAlertsTable-critical" color={SEVERITY_COLOR.critical}>
|
||||
<EuiLink
|
||||
data-test-subj="userSeverityAlertsTable-criticalLink"
|
||||
disabled={count === 0}
|
||||
onClick={() => handleClick({ userName, severity: 'critical' })}
|
||||
>
|
||||
|
|
|
@ -21,3 +21,5 @@ const MAX_ALLOWED_RESULTS = 100;
|
|||
* */
|
||||
export const getPageCount = (count: number = 0) =>
|
||||
Math.ceil(Math.min(count || 0, MAX_ALLOWED_RESULTS) / ITEMS_PER_PAGE);
|
||||
|
||||
export const openAlertsFilter = { field: 'kibana.alert.workflow_status', value: 'open' };
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { TestProviders } from '../../../../common/mock';
|
||||
import { EntityAnalyticsRiskScores } from '.';
|
||||
|
@ -13,6 +13,7 @@ import type { UserRiskScore } from '../../../../../common/search_strategy';
|
|||
import { RiskScoreEntity, RiskSeverity } from '../../../../../common/search_strategy';
|
||||
import type { SeverityCount } from '../../../../common/components/severity/types';
|
||||
import { useRiskScore, useRiskScoreKpi } from '../../../../risk_score/containers';
|
||||
import { openAlertsFilter } from '../../detection_response/utils';
|
||||
|
||||
const mockSeverityCount: SeverityCount = {
|
||||
[RiskSeverity.low]: 1,
|
||||
|
@ -42,6 +43,15 @@ const mockUseRiskScore = useRiskScore as jest.Mock;
|
|||
const mockUseRiskScoreKpi = useRiskScoreKpi as jest.Mock;
|
||||
jest.mock('../../../../risk_score/containers');
|
||||
|
||||
const mockOpenTimelineWithFilters = jest.fn();
|
||||
jest.mock('../../detection_response/hooks/use_navigate_to_timeline', () => {
|
||||
return {
|
||||
useNavigateToTimeline: () => ({
|
||||
openTimelineWithFilters: mockOpenTimelineWithFilters,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
|
||||
'EntityAnalyticsRiskScores entityType: %s',
|
||||
(riskEntity) => {
|
||||
|
@ -150,5 +160,48 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
|
|||
|
||||
expect(queryByTestId('risk-score-alerts')).toHaveTextContent(alertsCount.toString());
|
||||
});
|
||||
|
||||
it('navigates to timeline with filters when alerts count is clicked', () => {
|
||||
mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() });
|
||||
mockUseRiskScoreKpi.mockReturnValue({
|
||||
severityCount: mockSeverityCount,
|
||||
loading: false,
|
||||
});
|
||||
const name = 'testName';
|
||||
const data = [
|
||||
{
|
||||
'@timestamp': '1234567899',
|
||||
[riskEntity]: {
|
||||
name,
|
||||
risk: {
|
||||
rule_risks: [],
|
||||
calculated_level: RiskSeverity.high,
|
||||
calculated_score_norm: 75,
|
||||
multipliers: [],
|
||||
},
|
||||
},
|
||||
alertsCount: 999,
|
||||
},
|
||||
];
|
||||
mockUseRiskScore.mockReturnValue({ ...defaultProps, data });
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<EntityAnalyticsRiskScores riskEntity={riskEntity} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
fireEvent.click(getByTestId('risk-score-alerts'));
|
||||
|
||||
expect(mockOpenTimelineWithFilters.mock.calls[0][0]).toEqual([
|
||||
[
|
||||
{
|
||||
field: riskEntity === RiskScoreEntity.host ? 'host.name' : 'user.name',
|
||||
value: name,
|
||||
},
|
||||
openAlertsFilter,
|
||||
],
|
||||
]);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -42,6 +42,7 @@ import * as commonI18n from '../common/translations';
|
|||
import { usersActions } from '../../../../users/store';
|
||||
import { useNavigateToTimeline } from '../../detection_response/hooks/use_navigate_to_timeline';
|
||||
import type { TimeRange } from '../../../../common/store/inputs/model';
|
||||
import { openAlertsFilter } from '../../detection_response/utils';
|
||||
|
||||
const HOST_RISK_TABLE_QUERY_ID = 'hostRiskDashboardTable';
|
||||
const HOST_RISK_KPI_QUERY_ID = 'headerHostRiskScoreKpiQuery';
|
||||
|
@ -110,7 +111,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc
|
|||
field: riskEntity === RiskScoreEntity.host ? 'host.name' : 'user.name',
|
||||
value: entityName,
|
||||
};
|
||||
openTimelineWithFilters([[filter]], timeRange);
|
||||
openTimelineWithFilters([[filter, openAlertsFilter]], timeRange);
|
||||
},
|
||||
[riskEntity, openTimelineWithFilters]
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue