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..55f2e0a1218b5 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,8 @@ export interface RiskStats { multipliers: string[]; calculated_level: RiskSeverity; inputs?: RiskInputs; + category_1_score: number; + category_1_count: number; } export { RiskSeverity }; 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..11e2efb131949 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/common.tsx @@ -0,0 +1,116 @@ +/* + * 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 { SimpleRiskInput } from '../../../../common/entity_analytics/risk_engine'; +import { assertUnreachable } from '../../../../common/utility_types'; +import type { + HostRiskScore, + RiskStats, + UserRiskScore, +} from '../../../../common/search_strategy/security_solution/risk_score'; + +interface TableItem { + category: string; + count: number; +} + +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: entityData?.risk.category_1_score ?? 0, + count: entityData?.risk.category_1_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 normalizeRiskScore = (score: SimpleRiskInput['risk_score']) => { + if (!score) { + return 0; + } + if (typeof score === 'number') { + return score; + } + if (typeof score === 'string') { + return parseFloat(score); + } + return assertUnreachable(score); +}; + +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.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.tsx index c8d4f093fdc4d..6dfcfe6777536 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, { useMemo } from 'react'; -import type { EuiBasicTableColumn } from '@elastic/eui'; + import { useEuiTheme, EuiAccordion, @@ -20,12 +20,9 @@ import { import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; import { euiThemeVars } from '@kbn/ui-theme'; -import { i18n } from '@kbn/i18n'; + 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'; @@ -34,6 +31,14 @@ 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 { + buildColumns, + getEntityData, + getItems, + isUserRiskData, + LAST_30_DAYS, + LENS_VISUALIZATION_HEIGHT, +} from './common'; export interface RiskSummaryProps { riskScoreData: RiskScoreState; @@ -41,31 +46,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' }; - -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, @@ -88,50 +68,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: entityData?.risk.inputs?.length ?? 0, - }, - ], - [entityData?.risk.inputs?.length] - ); + const columns = useMemo(buildColumns, []); + const rows = useMemo(() => getItems(entityData), [entityData]); return ( ({ data-test-subj="risk-summary-table" responsive={false} columns={columns} - items={items} + items={rows} compressed />