forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add RiskBadge to Asset Inventory data grid (elastic#206798)
## Summary Closes elastic/security-team#11462. Implements a RiskBadge component that maps scores with colours reusing existing risk levels and palettes. Please, let me know if there's any existing component I should reuse instead. ### Screenshots | Before | After | |--------|--------| | <img width="55" alt="Screenshot 2025-01-16 at 17 19 09" src="https://github.com/user-attachments/assets/de8ba686-7d50-4d7b-848f-3270e4c9f09f" /> | <img width="55" alt="Screenshot 2025-01-16 at 17 19 01" src="https://github.com/user-attachments/assets/187c1aa1-a1d4-489b-83c0-9edb4e61cf75" /> | ### Definition of done - [x] Implement a coloured badge to the **Risk** column of the Asset Inventory DataGrid that displays a badge representing the risk level of the asset. - [x] Implement the badge styling: - Use the **Risk Palette** defined in the [Risk Palette Utility](https://github.com/opauloh/kibana/blob/main/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/common/utils.ts) for color mapping. - Determine the color of the badge based on the **Risk Ranges** defined in the [Risk Levels Utility](https://github.com/opauloh/kibana/blob/main/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/risk_levels.ts). - [x] Ensure the badge includes: - The textual representation of the risk level (e.g., "Low," "Medium," "High," "Critical"). - A tooltip that displays additional details about the risk level when hovering over the badge. - [x] Add unit tests to verify: - Correct colour mapping based on risk ranges. - Proper rendering of the badge in the DataGrid. - Tooltip displays the expected information. - ~~[ ] Update mock data for the DataGrid to include risk values for testing and development.~~ Done in previous PR that introduced mocked data. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks No risks since it's new functionality under a feature toggle.
- Loading branch information
1 parent
fc72ba9
commit 4146dd6
Showing
4 changed files
with
214 additions
and
1 deletion.
There are no files selected for viewing
142 changes: 142 additions & 0 deletions
142
.../security/plugins/security_solution/public/asset_inventory/components/risk_badge.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,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 React from 'react'; | ||
import { waitForEuiToolTipVisible } from '@elastic/eui/lib/test/rtl'; | ||
import { screen, render, cleanup, fireEvent } from '@testing-library/react'; | ||
import { RiskSeverity } from '../../../common/search_strategy'; | ||
import { RiskBadge } from './risk_badge'; | ||
|
||
describe('AssetInventory', () => { | ||
describe('RiskBadge', () => { | ||
beforeEach(() => { | ||
cleanup(); | ||
}); | ||
|
||
it('renders unknown risk with 0 risk score', async () => { | ||
render(<RiskBadge risk={0} data-test-subj="badge" />); | ||
const badge = screen.getByTestId('badge'); | ||
expect(badge).toHaveTextContent('0'); | ||
|
||
fireEvent.mouseOver(badge.parentElement as Node); | ||
await waitForEuiToolTipVisible(); | ||
|
||
const tooltip = screen.getByRole('tooltip'); | ||
expect(tooltip).toHaveTextContent(RiskSeverity.Unknown); | ||
}); | ||
it('renders unknown risk with 19 risk score', async () => { | ||
render(<RiskBadge risk={19} data-test-subj="badge" />); | ||
const badge = screen.getByTestId('badge'); | ||
expect(badge).toHaveTextContent('19'); | ||
|
||
fireEvent.mouseOver(badge.parentElement as Node); | ||
await waitForEuiToolTipVisible(); | ||
|
||
const tooltip = screen.getByRole('tooltip'); | ||
expect(tooltip).toHaveTextContent(RiskSeverity.Unknown); | ||
}); | ||
it('renders low risk with 20 risk score', async () => { | ||
render(<RiskBadge risk={20} data-test-subj="badge" />); | ||
const badge = screen.getByTestId('badge'); | ||
expect(badge).toHaveTextContent('20'); | ||
|
||
fireEvent.mouseOver(badge.parentElement as Node); | ||
await waitForEuiToolTipVisible(); | ||
|
||
const tooltip = screen.getByRole('tooltip'); | ||
expect(tooltip).toHaveTextContent(RiskSeverity.Low); | ||
}); | ||
it('renders low risk with 39 risk score', async () => { | ||
render(<RiskBadge risk={39} data-test-subj="badge" />); | ||
const badge = screen.getByTestId('badge'); | ||
expect(badge).toHaveTextContent('39'); | ||
|
||
fireEvent.mouseOver(badge.parentElement as Node); | ||
await waitForEuiToolTipVisible(); | ||
|
||
const tooltip = screen.getByRole('tooltip'); | ||
expect(tooltip).toHaveTextContent(RiskSeverity.Low); | ||
}); | ||
it('renders moderate risk with 40 risk score', async () => { | ||
render(<RiskBadge risk={40} data-test-subj="badge" />); | ||
const badge = screen.getByTestId('badge'); | ||
expect(badge).toHaveTextContent('40'); | ||
|
||
fireEvent.mouseOver(badge.parentElement as Node); | ||
await waitForEuiToolTipVisible(); | ||
|
||
const tooltip = screen.getByRole('tooltip'); | ||
expect(tooltip).toHaveTextContent(RiskSeverity.Moderate); | ||
}); | ||
it('renders moderate risk with 69 risk score', async () => { | ||
render(<RiskBadge risk={69} data-test-subj="badge" />); | ||
const badge = screen.getByTestId('badge'); | ||
expect(badge).toHaveTextContent('69'); | ||
|
||
fireEvent.mouseOver(badge.parentElement as Node); | ||
await waitForEuiToolTipVisible(); | ||
|
||
const tooltip = screen.getByRole('tooltip'); | ||
expect(tooltip).toHaveTextContent(RiskSeverity.Moderate); | ||
}); | ||
it('renders high risk with 70 risk score', async () => { | ||
render(<RiskBadge risk={70} data-test-subj="badge" />); | ||
const badge = screen.getByTestId('badge'); | ||
expect(badge).toHaveTextContent('70'); | ||
|
||
fireEvent.mouseOver(badge.parentElement as Node); | ||
await waitForEuiToolTipVisible(); | ||
|
||
const tooltip = screen.getByRole('tooltip'); | ||
expect(tooltip).toHaveTextContent(RiskSeverity.High); | ||
}); | ||
it('renders high risk with 89 risk score', async () => { | ||
render(<RiskBadge risk={89} data-test-subj="badge" />); | ||
const badge = screen.getByTestId('badge'); | ||
expect(badge).toHaveTextContent('89'); | ||
|
||
fireEvent.mouseOver(badge.parentElement as Node); | ||
await waitForEuiToolTipVisible(); | ||
|
||
const tooltip = screen.getByRole('tooltip'); | ||
expect(tooltip).toHaveTextContent(RiskSeverity.High); | ||
}); | ||
it('renders critical risk with 90 risk score', async () => { | ||
render(<RiskBadge risk={90} data-test-subj="badge" />); | ||
const badge = screen.getByTestId('badge'); | ||
expect(badge).toHaveTextContent('90'); | ||
|
||
fireEvent.mouseOver(badge.parentElement as Node); | ||
await waitForEuiToolTipVisible(); | ||
|
||
const tooltip = screen.getByRole('tooltip'); | ||
expect(tooltip).toHaveTextContent(RiskSeverity.Critical); | ||
}); | ||
it('renders critical risk with 100 risk score', async () => { | ||
render(<RiskBadge risk={100} data-test-subj="badge" />); | ||
const badge = screen.getByTestId('badge'); | ||
expect(badge).toHaveTextContent('100'); | ||
|
||
fireEvent.mouseOver(badge.parentElement as Node); | ||
await waitForEuiToolTipVisible(); | ||
|
||
const tooltip = screen.getByRole('tooltip'); | ||
expect(tooltip).toHaveTextContent(RiskSeverity.Critical); | ||
}); | ||
it('renders critical risk with risk score over limit (100)', async () => { | ||
render(<RiskBadge risk={400} data-test-subj="badge" />); | ||
const badge = screen.getByTestId('badge'); | ||
expect(badge).toHaveTextContent('400'); | ||
|
||
fireEvent.mouseOver(badge.parentElement as Node); | ||
await waitForEuiToolTipVisible(); | ||
|
||
const tooltip = screen.getByRole('tooltip'); | ||
expect(tooltip).toHaveTextContent(RiskSeverity.Critical); | ||
}); | ||
}); | ||
}); |
51 changes: 51 additions & 0 deletions
51
...tions/security/plugins/security_solution/public/asset_inventory/components/risk_badge.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,51 @@ | ||
/* | ||
* 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 { i18n } from '@kbn/i18n'; | ||
import { EuiBadge, EuiToolTip } from '@elastic/eui'; | ||
import { RiskSeverity } from '../../../common/search_strategy'; | ||
import { RISK_SEVERITY_COLOUR } from '../../entity_analytics/common/utils'; | ||
import { getRiskLevel } from '../../../common/entity_analytics/risk_engine/risk_levels'; | ||
|
||
export interface RiskBadgeProps { | ||
risk: number; | ||
'data-test-subj'?: string; | ||
} | ||
|
||
const tooltips = { | ||
[RiskSeverity.Unknown]: i18n.translate( | ||
'xpack.securitySolution.assetInventory.allAssets.risks.unknown', | ||
{ defaultMessage: RiskSeverity.Unknown } | ||
), | ||
[RiskSeverity.Low]: i18n.translate('xpack.securitySolution.assetInventory.allAssets.risks.low', { | ||
defaultMessage: RiskSeverity.Low, | ||
}), | ||
[RiskSeverity.Moderate]: i18n.translate( | ||
'xpack.securitySolution.assetInventory.allAssets.risks.moderate', | ||
{ defaultMessage: RiskSeverity.Moderate } | ||
), | ||
[RiskSeverity.High]: i18n.translate( | ||
'xpack.securitySolution.assetInventory.allAssets.risks.high', | ||
{ defaultMessage: RiskSeverity.High } | ||
), | ||
[RiskSeverity.Critical]: i18n.translate( | ||
'xpack.securitySolution.assetInventory.allAssets.risks.critical', | ||
{ defaultMessage: RiskSeverity.Critical } | ||
), | ||
}; | ||
|
||
export const RiskBadge = ({ risk, ...props }: RiskBadgeProps) => { | ||
const riskLevel = getRiskLevel(risk); | ||
const color = RISK_SEVERITY_COLOUR[riskLevel]; | ||
return ( | ||
<EuiToolTip content={tooltips[riskLevel]}> | ||
<EuiBadge {...props} color={color}> | ||
{risk} | ||
</EuiBadge> | ||
</EuiToolTip> | ||
); | ||
}; |
19 changes: 19 additions & 0 deletions
19
x-pack/solutions/security/plugins/security_solution/public/asset_inventory/jest.config.js
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,19 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
module.exports = { | ||
preset: '@kbn/test', | ||
rootDir: '../../../../../../..', | ||
roots: ['<rootDir>/x-pack/solutions/security/plugins/security_solution/public/asset_inventory'], | ||
coverageDirectory: | ||
'<rootDir>/target/kibana-coverage/jest/x-pack/solutions/security/plugins/security_solution/public/asset_inventory', | ||
coverageReporters: ['text', 'html'], | ||
collectCoverageFrom: [ | ||
'<rootDir>/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/**/*.{ts,tsx}', | ||
], | ||
moduleNameMapper: require('../../server/__mocks__/module_name_map'), | ||
}; |
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