Skip to content

Commit

Permalink
[8.x] [Security Solution] Add alert and cloud insights to document fl…
Browse files Browse the repository at this point in the history
…yout (elastic#195509) (elastic#195825)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution] Add alert and cloud insights to document flyout
(elastic#195509)](elastic#195509)

<!--- Backport version: 9.4.3 -->

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

<!--BACKPORT
[{"author":{"name":"christineweng","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-10-10T20:46:51Z","message":"[Security
Solution] Add alert and cloud insights to document flyout
(elastic#195509)\n\n## Summary\r\n\r\nThis PR adds alert count,
misconfiguration and vulnerabilities insights\r\nto alert/event flyout.
If data is not available, the insights
are\r\nhidden.\r\n\r\n\r\n[Mocks](https://www.figma.com/design/ubvhBGHee58diJNvSiy0GZ/%5B8.%2B%5D-%5BAlerts%5D-Expandable-Event-Flyout?node-id=8017-179782&node-type=canvas&t=0YjHfPi9zOUFUScc-0)\r\n\r\n\r\n![image](https://github.com/user-attachments/assets/ba706ab8-448a-4286-8229-c4c398136638)\r\n\r\n###
Checklist\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":"cd217c072fc786cb76ee47d885501688507c2dde","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["v9.0.0","Team:Threat
Hunting","release_note:feature","Team:Threat
Hunting:Investigations","backport:prev-major","8.16
candidate","v8.16.0"],"title":"[Security Solution] Add alert and cloud
insights to document
flyout","number":195509,"url":"https://github.com/elastic/kibana/pull/195509","mergeCommit":{"message":"[Security
Solution] Add alert and cloud insights to document flyout
(elastic#195509)\n\n## Summary\r\n\r\nThis PR adds alert count,
misconfiguration and vulnerabilities insights\r\nto alert/event flyout.
If data is not available, the insights
are\r\nhidden.\r\n\r\n\r\n[Mocks](https://www.figma.com/design/ubvhBGHee58diJNvSiy0GZ/%5B8.%2B%5D-%5BAlerts%5D-Expandable-Event-Flyout?node-id=8017-179782&node-type=canvas&t=0YjHfPi9zOUFUScc-0)\r\n\r\n\r\n![image](https://github.com/user-attachments/assets/ba706ab8-448a-4286-8229-c4c398136638)\r\n\r\n###
Checklist\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":"cd217c072fc786cb76ee47d885501688507c2dde"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/195509","number":195509,"mergeCommit":{"message":"[Security
Solution] Add alert and cloud insights to document flyout
(elastic#195509)\n\n## Summary\r\n\r\nThis PR adds alert count,
misconfiguration and vulnerabilities insights\r\nto alert/event flyout.
If data is not available, the insights
are\r\nhidden.\r\n\r\n\r\n[Mocks](https://www.figma.com/design/ubvhBGHee58diJNvSiy0GZ/%5B8.%2B%5D-%5BAlerts%5D-Expandable-Event-Flyout?node-id=8017-179782&node-type=canvas&t=0YjHfPi9zOUFUScc-0)\r\n\r\n\r\n![image](https://github.com/user-attachments/assets/ba706ab8-448a-4286-8229-c4c398136638)\r\n\r\n###
Checklist\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":"cd217c072fc786cb76ee47d885501688507c2dde"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: christineweng <[email protected]>
  • Loading branch information
kibanamachine and christineweng authored Oct 10, 2024
1 parent 2ac46f4 commit e435c47
Show file tree
Hide file tree
Showing 23 changed files with 946 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ export const DistributionBar = () => {
<DistributionBarComponent stats={mockStatsAlerts} />
<EuiSpacer size={'m'} />
</React.Fragment>,
<React.Fragment key={'hideLastTooltip'}>
<EuiTitle size={'xs'}>
<h4>{'Hide last tooltip'}</h4>
</EuiTitle>
<EuiSpacer size={'s'} />
<DistributionBarComponent stats={mockStatsAlerts} hideLastTooltip />
<EuiSpacer size={'m'} />
</React.Fragment>,
<React.Fragment key={'empty'}>
<EuiTitle size={'xs'}>
<h4>{'Empty state'}</h4>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,67 @@ describe('DistributionBar', () => {
});
});

it('should render last tooltip by default', () => {
const stats = [
{
key: 'low',
count: 9,
color: 'green',
},
{
key: 'medium',
count: 90,
color: 'red',
},
{
key: 'high',
count: 900,
color: 'red',
},
];

const { container } = render(
<DistributionBar stats={stats} data-test-subj={testSubj} hideLastTooltip={true} />
);
expect(container).toBeInTheDocument();
const parts = container.querySelectorAll(`[classname*="distribution_bar--tooltip"]`);
parts.forEach((part, index) => {
if (index < parts.length - 1) {
expect(part).toHaveStyle({ opacity: 0 });
} else {
expect(part).toHaveStyle({ opacity: 1 });
}
});
});

it('should not render last tooltip when hideLastTooltip is true', () => {
const stats = [
{
key: 'low',
count: 9,
color: 'green',
},
{
key: 'medium',
count: 90,
color: 'red',
},
{
key: 'high',
count: 900,
color: 'red',
},
];

const { container } = render(
<DistributionBar stats={stats} data-test-subj={testSubj} hideLastTooltip={true} />
);
expect(container).toBeInTheDocument();
const parts = container.querySelectorAll(`[classname*="distribution_bar--tooltip"]`);
parts.forEach((part) => {
expect(part).toHaveStyle({ opacity: 0 });
});
});

// todo: test tooltip visibility logic
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { css } from '@emotion/react';
export interface DistributionBarProps {
/** distribution data points */
stats: Array<{ key: string; count: number; color: string; label?: React.ReactNode }>;
/** hide the label above the bar at first render */
hideLastTooltip?: boolean;
/** data-test-subj used for querying the component in tests */
['data-test-subj']?: string;
}
Expand Down Expand Up @@ -136,18 +138,21 @@ export const DistributionBar: React.FC<DistributionBarProps> = React.memo(functi
props
) {
const styles = useStyles();
const { stats, 'data-test-subj': dataTestSubj } = props;
const { stats, 'data-test-subj': dataTestSubj, hideLastTooltip } = props;
const parts = stats.map((stat) => {
const partStyle = [
styles.part.base,
styles.part.tick,
styles.part.hover,
styles.part.lastTooltip,
css`
background-color: ${stat.color};
flex: ${stat.count};
`,
];
if (!hideLastTooltip) {
partStyle.push(styles.part.lastTooltip);
}

const prettyNumber = numeral(stat.count).format('0,0a');

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const FIRST_RECORD_PAGINATION = {
querySize: 1,
};

const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: number) => {
export const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: number) => {
if (passedFindingsStats === 0 && failedFindingsStats === 0) return [];
return [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import React from 'react';
import { render } from '@testing-library/react';
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
import type { Anomalies } from '../../../../common/components/ml/types';
import { DocumentDetailsContext } from '../../shared/context';
import { TestProviders } from '../../../../common/mock';
Expand All @@ -24,6 +26,9 @@ import {
HOST_DETAILS_LINK_TEST_ID,
HOST_DETAILS_RELATED_USERS_LINK_TEST_ID,
HOST_DETAILS_RELATED_USERS_IP_LINK_TEST_ID,
HOST_DETAILS_MISCONFIGURATIONS_TEST_ID,
HOST_DETAILS_VULNERABILITIES_TEST_ID,
HOST_DETAILS_ALERT_COUNT_TEST_ID,
} from './test_ids';
import { EXPANDABLE_PANEL_CONTENT_TEST_ID } from '@kbn/security-solution-common';
import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_score';
Expand All @@ -35,8 +40,11 @@ import { HOST_PREVIEW_BANNER } from '../../right/components/host_entity_overview
import { UserPreviewPanelKey } from '../../../entity_details/user_right';
import { USER_PREVIEW_BANNER } from '../../right/components/user_entity_overview';
import { NetworkPanelKey, NETWORK_PREVIEW_BANNER } from '../../../network_details';
import { useSummaryChartData } from '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data';

jest.mock('@kbn/expandable-flyout');
jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview');
jest.mock('@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview');

jest.mock('../../../../common/hooks/use_experimental_features');
const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock;
Expand Down Expand Up @@ -104,6 +112,10 @@ const mockUseHostsRelatedUsers = useHostRelatedUsers as jest.Mock;
jest.mock('../../../../entity_analytics/api/hooks/use_risk_score');
const mockUseRiskScore = useRiskScore as jest.Mock;

jest.mock(
'../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data'
);

const timestamp = '2022-07-25T08:20:18.966Z';

const defaultProps = {
Expand Down Expand Up @@ -158,6 +170,9 @@ describe('<HostDetails />', () => {
mockUseRiskScore.mockReturnValue(mockRiskScoreResponse);
mockUseHostsRelatedUsers.mockReturnValue(mockRelatedUsersResponse);
mockUseIsExperimentalFeatureEnabled.mockReturnValue(true);
(useMisconfigurationPreview as jest.Mock).mockReturnValue({});
(useVulnerabilitiesPreview as jest.Mock).mockReturnValue({});
(useSummaryChartData as jest.Mock).mockReturnValue({ isLoading: false, items: [] });
});

it('should render host details correctly', () => {
Expand Down Expand Up @@ -296,4 +311,41 @@ describe('<HostDetails />', () => {
});
});
});

describe('distribution bar insights', () => {
it('should not render if no data is available', () => {
const { queryByTestId } = renderHostDetails(mockContextValue);
expect(queryByTestId(HOST_DETAILS_MISCONFIGURATIONS_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(HOST_DETAILS_VULNERABILITIES_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(HOST_DETAILS_ALERT_COUNT_TEST_ID)).not.toBeInTheDocument();
});

it('should render alert count when data is available', () => {
(useSummaryChartData as jest.Mock).mockReturnValue({
isLoading: false,
items: [{ key: 'high', value: 78, label: 'High' }],
});

const { getByTestId } = renderHostDetails(mockContextValue);
expect(getByTestId(HOST_DETAILS_ALERT_COUNT_TEST_ID)).toBeInTheDocument();
});

it('should render misconfiguration when data is available', () => {
(useMisconfigurationPreview as jest.Mock).mockReturnValue({
data: { count: { passed: 1, failed: 2 } },
});

const { getByTestId } = renderHostDetails(mockContextValue);
expect(getByTestId(HOST_DETAILS_MISCONFIGURATIONS_TEST_ID)).toBeInTheDocument();
});

it('should render vulnerabilities when data is available', () => {
(useVulnerabilitiesPreview as jest.Mock).mockReturnValue({
data: { count: { CRITICAL: 0, HIGH: 1, MEDIUM: 1, LOW: 0, UNKNOWN: 0 } },
});

const { getByTestId } = renderHostDetails(mockContextValue);
expect(getByTestId(HOST_DETAILS_VULNERABILITIES_TEST_ID)).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
EuiToolTip,
EuiIcon,
EuiPanel,
EuiHorizontalRule,
EuiFlexGrid,
} from '@elastic/eui';
import type { EuiBasicTableColumn } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
Expand Down Expand Up @@ -51,6 +53,9 @@ import {
HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID,
HOST_DETAILS_RELATED_USERS_LINK_TEST_ID,
HOST_DETAILS_RELATED_USERS_IP_LINK_TEST_ID,
HOST_DETAILS_ALERT_COUNT_TEST_ID,
HOST_DETAILS_MISCONFIGURATIONS_TEST_ID,
HOST_DETAILS_VULNERABILITIES_TEST_ID,
} from './test_ids';
import {
USER_NAME_FIELD_NAME,
Expand All @@ -63,6 +68,9 @@ import { PreviewLink } from '../../../shared/components/preview_link';
import { HostPreviewPanelKey } from '../../../entity_details/host_right';
import { HOST_PREVIEW_BANNER } from '../../right/components/host_entity_overview';
import type { NarrowDateRange } from '../../../../common/components/ml/types';
import { MisconfigurationsInsight } from '../../shared/components/misconfiguration_insight';
import { VulnerabilitiesInsight } from '../../shared/components/vulnerabilities_insight';
import { AlertCountInsight } from '../../shared/components/alert_count_insight';

const HOST_DETAILS_ID = 'entities-hosts-details';
const RELATED_USERS_ID = 'entities-hosts-related-users';
Expand Down Expand Up @@ -337,6 +345,28 @@ export const HostDetails: React.FC<HostDetailsProps> = ({ hostName, timestamp, s
)}
</AnomalyTableProvider>
<EuiSpacer size="s" />

<EuiHorizontalRule margin="s" />
<EuiFlexGrid responsive={false} columns={3} gutterSize="xl">
<AlertCountInsight
fieldName={'host.name'}
name={hostName}
direction="column"
data-test-subj={HOST_DETAILS_ALERT_COUNT_TEST_ID}
/>
<MisconfigurationsInsight
fieldName={'host.name'}
name={hostName}
direction="column"
data-test-subj={HOST_DETAILS_MISCONFIGURATIONS_TEST_ID}
/>
<VulnerabilitiesInsight
hostName={hostName}
direction="column"
data-test-subj={HOST_DETAILS_VULNERABILITIES_TEST_ID}
/>
</EuiFlexGrid>
<EuiSpacer size="l" />
<EuiPanel hasBorder={true}>
<EuiFlexGroup direction="row" gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export const PREVALENCE_DETAILS_TABLE_UPSELL_CELL_TEST_ID =
export const ENTITIES_DETAILS_TEST_ID = `${PREFIX}EntitiesDetails` as const;
export const USER_DETAILS_TEST_ID = `${PREFIX}UsersDetails` as const;
export const USER_DETAILS_LINK_TEST_ID = `${USER_DETAILS_TEST_ID}TitleLink` as const;
export const USER_DETAILS_ALERT_COUNT_TEST_ID = `${USER_DETAILS_TEST_ID}AlertCount` as const;
export const USER_DETAILS_MISCONFIGURATIONS_TEST_ID =
`${USER_DETAILS_TEST_ID}Misconfigurations` as const;
export const USER_DETAILS_RELATED_HOSTS_TABLE_TEST_ID =
`${USER_DETAILS_TEST_ID}RelatedHostsTable` as const;
export const USER_DETAILS_RELATED_HOSTS_LINK_TEST_ID =
Expand All @@ -53,6 +56,11 @@ export const USER_DETAILS_INFO_TEST_ID = 'user-overview' as const;

export const HOST_DETAILS_TEST_ID = `${PREFIX}HostsDetails` as const;
export const HOST_DETAILS_LINK_TEST_ID = `${HOST_DETAILS_TEST_ID}TitleLink` as const;
export const HOST_DETAILS_ALERT_COUNT_TEST_ID = `${HOST_DETAILS_TEST_ID}AlertCount` as const;
export const HOST_DETAILS_MISCONFIGURATIONS_TEST_ID =
`${HOST_DETAILS_TEST_ID}Misconfigurations` as const;
export const HOST_DETAILS_VULNERABILITIES_TEST_ID =
`${HOST_DETAILS_TEST_ID}Vulnerabilities` as const;
export const HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID =
`${HOST_DETAILS_TEST_ID}RelatedUsersTable` as const;
export const HOST_DETAILS_RELATED_USERS_LINK_TEST_ID =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import React from 'react';
import { render } from '@testing-library/react';
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
import type { Anomalies } from '../../../../common/components/ml/types';
import { TestProviders } from '../../../../common/mock';
import { DocumentDetailsContext } from '../../shared/context';
Expand All @@ -24,6 +25,8 @@ import {
USER_DETAILS_RELATED_HOSTS_TABLE_TEST_ID,
USER_DETAILS_RELATED_HOSTS_LINK_TEST_ID,
USER_DETAILS_RELATED_HOSTS_IP_LINK_TEST_ID,
USER_DETAILS_MISCONFIGURATIONS_TEST_ID,
USER_DETAILS_ALERT_COUNT_TEST_ID,
} from './test_ids';
import { EXPANDABLE_PANEL_CONTENT_TEST_ID } from '@kbn/security-solution-common';
import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_score';
Expand All @@ -35,8 +38,10 @@ import { HOST_PREVIEW_BANNER } from '../../right/components/host_entity_overview
import { UserPreviewPanelKey } from '../../../entity_details/user_right';
import { USER_PREVIEW_BANNER } from '../../right/components/user_entity_overview';
import { NetworkPanelKey, NETWORK_PREVIEW_BANNER } from '../../../network_details';
import { useSummaryChartData } from '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data';

jest.mock('@kbn/expandable-flyout');
jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview');

jest.mock('../../../../common/hooks/use_experimental_features');
const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock;
Expand Down Expand Up @@ -101,6 +106,10 @@ const mockUseUsersRelatedHosts = useUserRelatedHosts as jest.Mock;
jest.mock('../../../../entity_analytics/api/hooks/use_risk_score');
const mockUseRiskScore = useRiskScore as jest.Mock;

jest.mock(
'../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data'
);

const timestamp = '2022-07-25T08:20:18.966Z';

const defaultProps = {
Expand Down Expand Up @@ -155,6 +164,8 @@ describe('<UserDetails />', () => {
mockUseRiskScore.mockReturnValue(mockRiskScoreResponse);
mockUseUsersRelatedHosts.mockReturnValue(mockRelatedHostsResponse);
mockUseIsExperimentalFeatureEnabled.mockReturnValue(true);
(useMisconfigurationPreview as jest.Mock).mockReturnValue({});
(useSummaryChartData as jest.Mock).mockReturnValue({ isLoading: false, items: [] });
});

it('should render user details correctly', () => {
Expand Down Expand Up @@ -278,4 +289,31 @@ describe('<UserDetails />', () => {
});
});
});

describe('distribution bar insights', () => {
it('should not render if no data is available', () => {
const { queryByTestId } = renderUserDetails(mockContextValue);
expect(queryByTestId(USER_DETAILS_MISCONFIGURATIONS_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(USER_DETAILS_ALERT_COUNT_TEST_ID)).not.toBeInTheDocument();
});

it('should render alert count when data is available', () => {
(useSummaryChartData as jest.Mock).mockReturnValue({
isLoading: false,
items: [{ key: 'high', value: 78, label: 'High' }],
});

const { getByTestId } = renderUserDetails(mockContextValue);
expect(getByTestId(USER_DETAILS_ALERT_COUNT_TEST_ID)).toBeInTheDocument();
});

it('should render misconfiguration when data is available', () => {
(useMisconfigurationPreview as jest.Mock).mockReturnValue({
data: { count: { passed: 1, failed: 2 } },
});

const { getByTestId } = renderUserDetails(mockContextValue);
expect(getByTestId(USER_DETAILS_MISCONFIGURATIONS_TEST_ID)).toBeInTheDocument();
});
});
});
Loading

0 comments on commit e435c47

Please sign in to comment.