-
Notifications
You must be signed in to change notification settings - Fork 8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Security Solution][Detection & Response] Open alerts by Rule (#129021)
* rule alerts table initial implementation * move section code to its own directory * alerts filter by rule uuid * toggle query added, translations and navigation * lower case text * use selector properly * tests * all permission checks implemented + remaining tests * fix test with search bar * rename hook file * add useCallback * fix imports Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
- Loading branch information
1 parent
b5817af
commit 2887930
Showing
12 changed files
with
1,148 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
...curity_solution/public/overview/components/detection_response/rule_alerts_table/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
export { RuleAlertsTable } from './rule_alerts_table'; |
142 changes: 142 additions & 0 deletions
142
...ity_solution/public/overview/components/detection_response/rule_alerts_table/mock_data.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/* | ||
* 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 { RuleAlertsItem, SeverityRuleAlertsAggsResponse } from './use_rule_alerts_items'; | ||
|
||
export const from = '2022-04-05T12:00:00.000Z'; | ||
export const to = '2022-04-08T12:00:00.000Z'; | ||
|
||
export const severityRuleAlertsQuery = { | ||
size: 0, | ||
query: { | ||
bool: { | ||
filter: [ | ||
{ term: { 'kibana.alert.workflow_status': 'open' } }, | ||
{ range: { '@timestamp': { gte: from, lte: to } } }, | ||
], | ||
}, | ||
}, | ||
aggs: { | ||
alertsByRule: { | ||
terms: { | ||
// top 4 rules sorted by severity counters | ||
field: 'kibana.alert.rule.uuid', | ||
size: 4, | ||
order: [{ critical: 'desc' }, { high: 'desc' }, { medium: 'desc' }, { low: 'desc' }], | ||
}, | ||
aggs: { | ||
// severity aggregations for sorting | ||
critical: { filter: { term: { 'kibana.alert.severity': 'critical' } } }, | ||
high: { filter: { term: { 'kibana.alert.severity': 'high' } } }, | ||
medium: { filter: { term: { 'kibana.alert.severity': 'medium' } } }, | ||
low: { filter: { term: { 'kibana.alert.severity': 'low' } } }, | ||
// get the newest alert to extract timestamp and rule name | ||
lastRuleAlert: { | ||
top_hits: { | ||
size: 1, | ||
sort: { | ||
'@timestamp': 'desc', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
export const mockSeverityRuleAlertsResponse: { aggregations: SeverityRuleAlertsAggsResponse } = { | ||
aggregations: { | ||
alertsByRule: { | ||
buckets: [ | ||
{ | ||
key: '79ec0270-b4c5-11ec-970e-8f7c5a7144f7', | ||
doc_count: 54, | ||
lastRuleAlert: { | ||
hits: { | ||
total: { | ||
value: 54, | ||
}, | ||
hits: [ | ||
{ | ||
_source: { | ||
'kibana.alert.rule.name': 'RULE_1', | ||
'@timestamp': '2022-04-05T15:58:35.079Z', | ||
'kibana.alert.severity': 'critical', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
{ | ||
key: '955c79d0-b403-11ec-b5a7-6dc1ed01bdd7', | ||
doc_count: 112, | ||
lastRuleAlert: { | ||
hits: { | ||
total: { | ||
value: 112, | ||
}, | ||
hits: [ | ||
{ | ||
_source: { | ||
'kibana.alert.rule.name': 'RULE_2', | ||
'@timestamp': '2022-04-05T15:58:47.164Z', | ||
'kibana.alert.severity': 'high', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
{ | ||
key: '13bc7bc0-b1d6-11ec-a799-67811b37527a', | ||
doc_count: 170, | ||
lastRuleAlert: { | ||
hits: { | ||
total: { | ||
value: 170, | ||
}, | ||
hits: [ | ||
{ | ||
_source: { | ||
'kibana.alert.rule.name': 'RULE_3', | ||
'@timestamp': '2022-04-05T15:56:16.606Z', | ||
'kibana.alert.severity': 'low', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
|
||
export const severityRuleAlertsResponseParsed: RuleAlertsItem[] = [ | ||
{ | ||
alert_count: 54, | ||
id: '79ec0270-b4c5-11ec-970e-8f7c5a7144f7', | ||
last_alert_at: '2022-04-05T15:58:35.079Z', | ||
name: 'RULE_1', | ||
severity: 'critical', | ||
}, | ||
{ | ||
alert_count: 112, | ||
id: '955c79d0-b403-11ec-b5a7-6dc1ed01bdd7', | ||
last_alert_at: '2022-04-05T15:58:47.164Z', | ||
name: 'RULE_2', | ||
severity: 'high', | ||
}, | ||
{ | ||
alert_count: 170, | ||
id: '13bc7bc0-b1d6-11ec-a799-67811b37527a', | ||
last_alert_at: '2022-04-05T15:56:16.606Z', | ||
name: 'RULE_3', | ||
severity: 'low', | ||
}, | ||
]; |
144 changes: 144 additions & 0 deletions
144
...ublic/overview/components/detection_response/rule_alerts_table/rule_alerts_table.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
* 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 React from 'react'; | ||
import { render } from '@testing-library/react'; | ||
import { TestProviders } from '../../../../common/mock'; | ||
import { RuleAlertsTable, RuleAlertsTableProps } from './rule_alerts_table'; | ||
import { RuleAlertsItem, UseRuleAlertsItems } from './use_rule_alerts_items'; | ||
import moment from 'moment'; | ||
import { SecurityPageName } from '../../../../../common/constants'; | ||
|
||
const mockGetAppUrl = jest.fn(); | ||
jest.mock('../../../../common/lib/kibana/hooks', () => { | ||
const original = jest.requireActual('../../../../common/lib/kibana/hooks'); | ||
return { | ||
...original, | ||
useNavigation: () => ({ | ||
getAppUrl: mockGetAppUrl, | ||
}), | ||
}; | ||
}); | ||
|
||
type UseRuleAlertsItemsReturn = ReturnType<UseRuleAlertsItems>; | ||
const defaultUseRuleAlertsItemsReturn: UseRuleAlertsItemsReturn = { | ||
items: [], | ||
isLoading: false, | ||
updatedAt: Date.now(), | ||
}; | ||
const mockUseRuleAlertsItems = jest.fn(() => defaultUseRuleAlertsItemsReturn); | ||
const mockUseRuleAlertsItemsReturn = (param: Partial<UseRuleAlertsItemsReturn>) => { | ||
mockUseRuleAlertsItems.mockReturnValueOnce({ ...defaultUseRuleAlertsItemsReturn, ...param }); | ||
}; | ||
jest.mock('./use_rule_alerts_items', () => ({ | ||
useRuleAlertsItems: () => mockUseRuleAlertsItems(), | ||
})); | ||
|
||
const defaultProps: RuleAlertsTableProps = { | ||
signalIndexName: '', | ||
}; | ||
const items: RuleAlertsItem[] = [ | ||
{ | ||
id: 'ruleId', | ||
name: 'ruleName', | ||
last_alert_at: moment().subtract(1, 'day').format(), | ||
alert_count: 10, | ||
severity: 'high', | ||
}, | ||
]; | ||
|
||
describe('RuleAlertsTable', () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should render empty table', () => { | ||
const result = render( | ||
<TestProviders> | ||
<RuleAlertsTable {...defaultProps} /> | ||
</TestProviders> | ||
); | ||
|
||
expect(result.getByTestId('severityRuleAlertsPanel')).toBeInTheDocument(); | ||
expect(result.getByText('No alerts to display')).toBeInTheDocument(); | ||
expect(result.getByTestId('severityRuleAlertsButton')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should render a loading table', () => { | ||
mockUseRuleAlertsItemsReturn({ isLoading: true }); | ||
const result = render( | ||
<TestProviders> | ||
<RuleAlertsTable {...defaultProps} /> | ||
</TestProviders> | ||
); | ||
|
||
expect(result.getByText('Updating...')).toBeInTheDocument(); | ||
expect(result.getByTestId('severityRuleAlertsButton')).toBeInTheDocument(); | ||
expect(result.getByTestId('severityRuleAlertsTable')).toHaveClass('euiBasicTable-loading'); | ||
}); | ||
|
||
it('should render the updated at subtitle', () => { | ||
mockUseRuleAlertsItemsReturn({ isLoading: false }); | ||
const result = render( | ||
<TestProviders> | ||
<RuleAlertsTable {...defaultProps} /> | ||
</TestProviders> | ||
); | ||
|
||
expect(result.getByText('Updated now')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should render the table columns', () => { | ||
mockUseRuleAlertsItemsReturn({ items }); | ||
const result = render( | ||
<TestProviders> | ||
<RuleAlertsTable {...defaultProps} /> | ||
</TestProviders> | ||
); | ||
|
||
const columnHeaders = result.getAllByRole('columnheader'); | ||
expect(columnHeaders.at(0)).toHaveTextContent('Rule name'); | ||
expect(columnHeaders.at(1)).toHaveTextContent('Last alert'); | ||
expect(columnHeaders.at(2)).toHaveTextContent('Alert count'); | ||
expect(columnHeaders.at(3)).toHaveTextContent('Severity'); | ||
}); | ||
|
||
it('should render the table items', () => { | ||
mockUseRuleAlertsItemsReturn({ items }); | ||
const result = render( | ||
<TestProviders> | ||
<RuleAlertsTable {...defaultProps} /> | ||
</TestProviders> | ||
); | ||
|
||
expect(result.getByTestId('severityRuleAlertsTable-name')).toHaveTextContent('ruleName'); | ||
expect(result.getByTestId('severityRuleAlertsTable-lastAlertAt')).toHaveTextContent( | ||
'yesterday' | ||
); | ||
expect(result.getByTestId('severityRuleAlertsTable-alertCount')).toHaveTextContent('10'); | ||
expect(result.getByTestId('severityRuleAlertsTable-severity')).toHaveTextContent('High'); | ||
}); | ||
|
||
it('should generate the table items links', () => { | ||
const linkUrl = '/fake/link'; | ||
mockGetAppUrl.mockReturnValue(linkUrl); | ||
mockUseRuleAlertsItemsReturn({ items }); | ||
|
||
const result = render( | ||
<TestProviders> | ||
<RuleAlertsTable {...defaultProps} /> | ||
</TestProviders> | ||
); | ||
|
||
expect(mockGetAppUrl).toBeCalledWith({ | ||
deepLinkId: SecurityPageName.rules, | ||
path: `id/${items[0].id}`, | ||
}); | ||
|
||
expect(result.getByTestId('severityRuleAlertsTable-name')).toHaveAttribute('href', linkUrl); | ||
}); | ||
}); |
Oops, something went wrong.