diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts index 7810c29fbfbfe..94aebe40caf9b 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts @@ -31,6 +31,10 @@ export interface RiskStats { multipliers: string[]; calculated_level: RiskSeverity; inputs?: RiskInputs; + category_1_score: number; + category_1_count: number; + category_2_score: number; + category_2_count: number; } export { RiskSeverity }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/common/get_alerts_query_for_risk_score.test.ts b/x-pack/plugins/security_solution/public/entity_analytics/common/get_alerts_query_for_risk_score.test.ts index ec4a99c4becc7..6018a1df3d99b 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/common/get_alerts_query_for_risk_score.test.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/common/get_alerts_query_for_risk_score.test.ts @@ -12,6 +12,10 @@ const risk = { calculated_score_norm: 70, rule_risks: [], multipliers: [], + category_1_score: 0, + category_1_count: 0, + category_2_score: 0, + category_2_count: 0, }; describe('getAlertsQueryForRiskScore', () => { diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/common.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/common.tsx new file mode 100644 index 0000000000000..7ee80b8b695c9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/common.tsx @@ -0,0 +1,112 @@ +/* + * 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 type { EuiBasicTableColumn } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +import type { + HostRiskScore, + RiskStats, + UserRiskScore, +} from '../../../../common/search_strategy/security_solution/risk_score'; + +interface TableItem { + category: string; + count: number; + score: string; +} + +interface EntityData { + name: string; + risk: RiskStats; +} + +export const buildColumns: () => Array> = () => [ + { + field: 'category', + name: ( + + ), + truncateText: false, + mobileOptions: { show: true }, + sortable: true, + }, + { + field: 'score', + name: ( + + ), + truncateText: false, + mobileOptions: { show: true }, + sortable: true, + dataType: 'number', + }, + { + field: 'count', + name: ( + + ), + truncateText: false, + mobileOptions: { show: true }, + sortable: true, + dataType: 'number', + }, +]; + +export const getItems: (entityData: EntityData | undefined) => TableItem[] = (entityData) => { + return [ + { + category: i18n.translate('xpack.securitySolution.flyout.entityDetails.alertsGroupLabel', { + defaultMessage: 'Alerts', + }), + score: displayNumber(entityData?.risk.category_1_score ?? 0), + count: entityData?.risk.category_1_count ?? 0, + }, + { + category: i18n.translate('xpack.securitySolution.flyout.entityDetails.contextGroupLabel', { + defaultMessage: 'Contexts', + }), + score: displayNumber(entityData?.risk.category_2_score ?? 0), + count: entityData?.risk.category_2_count ?? 0, + }, + ]; +}; + +export function isUserRiskData( + riskData: UserRiskScore | HostRiskScore | undefined +): riskData is UserRiskScore { + return !!riskData && (riskData as UserRiskScore).user !== undefined; +} + +export const getEntityData = ( + riskData: UserRiskScore | HostRiskScore | undefined +): EntityData | undefined => { + if (!riskData) { + return; + } + + if (isUserRiskData(riskData)) { + return riskData.user; + } + + return riskData.host; +}; + +const displayNumber = (num: number) => num.toFixed(2); + +export const LENS_VISUALIZATION_HEIGHT = 126; // Static height in pixels specified by design +export const LAST_30_DAYS = { from: 'now-30d', to: 'now' }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx index bd833bc68b016..485ced7dea46c 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx @@ -18,17 +18,6 @@ import type { VisualizationEmbeddableProps, } from '../../../common/components/visualization_actions/types'; -const mockContributingAlerts = Array(6).fill({}); -const expectedRiskInputsLength = mockContributingAlerts.length; - -const mockUseRiskContributingAlerts = jest - .fn() - .mockReturnValue({ loading: false, data: mockContributingAlerts }); - -jest.mock('../../hooks/use_risk_contributing_alerts', () => ({ - useRiskContributingAlerts: () => mockUseRiskContributingAlerts(), -})); - const mockVisualizationEmbeddable = jest .fn() .mockReturnValue(
); @@ -56,9 +45,17 @@ describe('RiskSummary', () => { expect(getByTestId('risk-summary-table')).toBeInTheDocument(); expect(getByTestId('risk-summary-table')).toHaveTextContent( - `Inputs${expectedRiskInputsLength}` + `Inputs${mockHostRiskScoreState.data?.[0].host.risk.category_1_count ?? 0}` + ); + expect(getByTestId('risk-summary-table')).toHaveTextContent( + `AlertsScore${mockHostRiskScoreState.data?.[0].host.risk.category_1_score ?? 0}` + ); + expect(getByTestId('risk-summary-table')).toHaveTextContent( + `Inputs${mockHostRiskScoreState.data?.[0].host.risk.category_2_count ?? 0}` + ); + expect(getByTestId('risk-summary-table')).toHaveTextContent( + `ContextsScore${mockHostRiskScoreState.data?.[0].host.risk.category_2_score ?? 0}` ); - expect(getByTestId('risk-summary-table')).toHaveTextContent('CategoryAlerts'); }); it('renders risk summary table when riskScoreData is empty', () => { diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx index 5190fb8955427..9731ef7d7542f 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx @@ -6,7 +6,7 @@ */ import React, { useCallback, useMemo } from 'react'; -import type { EuiBasicTableColumn } from '@elastic/eui'; + import { useEuiTheme, EuiAccordion, @@ -20,13 +20,11 @@ import { import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; import { euiThemeVars } from '@kbn/ui-theme'; -import { i18n } from '@kbn/i18n'; + import { useKibana } from '../../../common/lib/kibana/kibana_react'; + import { EntityDetailsLeftPanelTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header'; -import type { - HostRiskScore, - UserRiskScore, -} from '../../../../common/search_strategy/security_solution/risk_score'; + import { InspectButton, InspectButtonContainer } from '../../../common/components/inspect'; import { ONE_WEEK_IN_HOURS } from '../../../timelines/components/side_panel/new_user_detail/constants'; import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; @@ -35,7 +33,15 @@ import { VisualizationEmbeddable } from '../../../common/components/visualizatio import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel'; import type { RiskScoreState } from '../../api/hooks/use_risk_score'; import { getRiskScoreSummaryAttributes } from '../../lens_attributes/risk_score_summary'; -import { useRiskContributingAlerts } from '../../hooks/use_risk_contributing_alerts'; + +import { + buildColumns, + getEntityData, + getItems, + isUserRiskData, + LAST_30_DAYS, + LENS_VISUALIZATION_HEIGHT, +} from './common'; export interface RiskSummaryProps { riskScoreData: RiskScoreState; @@ -43,32 +49,6 @@ export interface RiskSummaryProps { openDetailsPanel: (tab: EntityDetailsLeftPanelTab) => void; } -interface TableItem { - category: string; - count: number; -} -const LENS_VISUALIZATION_HEIGHT = 126; // Static height in pixels specified by design -const LAST_30_DAYS = { from: 'now-30d', to: 'now' }; -const ALERTS_FIELDS: string[] = []; - -function isUserRiskData( - riskData: UserRiskScore | HostRiskScore | undefined -): riskData is UserRiskScore { - return !!riskData && (riskData as UserRiskScore).user !== undefined; -} - -const getEntityData = (riskData: UserRiskScore | HostRiskScore | undefined) => { - if (!riskData) { - return; - } - - if (isUserRiskData(riskData)) { - return riskData.user; - } - - return riskData.host; -}; - const RiskSummaryComponent = ({ riskScoreData, queryId, @@ -79,10 +59,7 @@ const RiskSummaryComponent = ({ const riskData = data && data.length > 0 ? data[0] : undefined; const entityData = getEntityData(riskData); const { euiTheme } = useEuiTheme(); - const { data: alertsData } = useRiskContributingAlerts({ - riskScore: riskData, - fields: ALERTS_FIELDS, - }); + const lensAttributes = useMemo(() => { const entityName = entityData?.name ?? ''; const fieldName = isUserRiskData(riskData) ? 'user.name' : 'host.name'; @@ -95,50 +72,10 @@ const RiskSummaryComponent = ({ }); }, [entityData?.name, entityData?.risk?.calculated_level, riskData]); - const columns: Array> = useMemo( - () => [ - { - field: 'category', - name: ( - - ), - truncateText: false, - mobileOptions: { show: true }, - sortable: true, - }, - { - field: 'count', - name: ( - - ), - truncateText: false, - mobileOptions: { show: true }, - sortable: true, - dataType: 'number', - }, - ], - [] - ); - const xsFontSize = useEuiFontSize('xxs').fontSize; - const items: TableItem[] = useMemo( - () => [ - { - category: i18n.translate('xpack.securitySolution.flyout.entityDetails.alertsGroupLabel', { - defaultMessage: 'Alerts', - }), - count: alertsData?.length ?? 0, - }, - ], - [alertsData?.length] - ); + const columns = useMemo(buildColumns, []); + const rows = useMemo(() => getItems(entityData), [entityData]); const onToggle = useCallback( (isOpen) => { @@ -280,7 +217,7 @@ const RiskSummaryComponent = ({ data-test-subj="risk-summary-table" responsive={false} columns={columns} - items={items} + items={rows} compressed />
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/user_risk_score_table/index.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/user_risk_score_table/index.test.tsx index 87905b248a7d9..578544ea691c4 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/user_risk_score_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/user_risk_score_table/index.test.tsx @@ -27,6 +27,10 @@ describe('UserRiskScoreTable', () => { calculated_score_norm: 71, calculated_level: RiskSeverity.high, multipliers: [], + category_1_count: 0, + category_2_count: 0, + category_1_score: 0, + category_2_score: 0, }, }, }, diff --git a/x-pack/plugins/security_solution/public/entity_analytics/lens_attributes/risk_score_summary.ts b/x-pack/plugins/security_solution/public/entity_analytics/lens_attributes/risk_score_summary.ts index 544badd18b5e9..f60911cd87343 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/lens_attributes/risk_score_summary.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/lens_attributes/risk_score_summary.ts @@ -90,7 +90,7 @@ export const getRiskScoreSummaryAttributes: ( format: { id: 'number', params: { - decimals: 0, + decimals: 2, compact: false, }, }, diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/mocks/index.ts b/x-pack/plugins/security_solution/public/flyout/entity_details/mocks/index.ts index 9c7671917bbd9..4f0103552ad56 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/mocks/index.ts +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/mocks/index.ts @@ -37,6 +37,10 @@ const userRiskScore: UserRiskScore = { timestamp: '2021-08-19T18:55:59.000Z', }, ], + category_1_count: 5, + category_1_score: 20, + category_2_count: 1, + category_2_score: 10, }, }, alertsCount: 0, @@ -62,6 +66,10 @@ const hostRiskScore: HostRiskScore = { timestamp: '2021-08-19T18:55:59.000Z', }, ], + category_1_count: 5, + category_1_score: 20, + category_2_count: 1, + category_2_score: 10, }, }, alertsCount: 0, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts index b1e0d69f9c3c9..5af5ac8bb1a14 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts @@ -45,6 +45,10 @@ export const mockSearchStrategyResponse: IEsSearchResponse = { calculated_level: RiskSeverity.high, calculated_score_norm: 75, multipliers: [], + category_1_count: 10, + category_1_score: 20, + category_2_count: 1, + category_2_score: 10, }, }, },