From 96df8755cb7b9d7ad5313aede6374eb46f6cafc0 Mon Sep 17 00:00:00 2001 From: Maxim Kholod Date: Tue, 31 Mar 2026 16:58:30 +0200 Subject: [PATCH 1/2] [Entity Analytics] Add entity resolution UI to service flyout Wire entity resolution components (ResolutionSection and ResolutionGroupTab) into the service entity flyout, achieving parity with host and user flyouts. Fixes #260398 --- .../service_details_left/index.tsx | 4 +++- .../service_details_left/tabs.tsx | 21 +++++++++++++++---- .../entity_details/service_right/content.tsx | 9 ++++++++ .../hooks/use_navigate_to_service_details.ts | 13 +++++++++++- .../entity_details/service_right/index.tsx | 18 +++++++++++++--- 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_details_left/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_details_left/index.tsx index d8eafaaa5ab82..ebd786b9e6460 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_details_left/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_details_left/index.tsx @@ -25,6 +25,7 @@ export interface ServiceDetailsPanelProps extends Record { identityFields: IdentityFields; path?: PanelPath; scopeId: string; + entityStoreEntityId?: string; } export interface ServiceDetailsExpandableFlyoutProps extends FlyoutPanelProps { key: 'service_details'; @@ -37,12 +38,13 @@ export const ServiceDetailsPanel = ({ identityFields, path, scopeId, + entityStoreEntityId, }: ServiceDetailsPanelProps) => { const serviceName = useMemo( () => getServiceNameFromEntityIdentifiers(identityFields ?? {}), [identityFields] ); - const tabs = useTabs(serviceName, scopeId); + const tabs = useTabs(serviceName, scopeId, entityStoreEntityId); const { selectedTabId, setSelectedTabId } = useSelectedTab( isRiskScoreExist, diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_details_left/tabs.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_details_left/tabs.tsx index b8ebe2cffeabc..f79db9578706b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_details_left/tabs.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_details_left/tabs.tsx @@ -6,17 +6,30 @@ */ import { useMemo } from 'react'; -import { getRiskInputTab } from '../../../entity_analytics/components/entity_details_flyout'; +import { + getRiskInputTab, + getResolutionGroupTab, +} from '../../../entity_analytics/components/entity_details_flyout'; import { EntityType } from '../../../../common/entity_analytics/types'; import type { LeftPanelTabsType } from '../shared/components/left_panel/left_panel_header'; -export const useTabs = (name: string, scopeId: string): LeftPanelTabsType => +export const useTabs = ( + name: string, + scopeId: string, + entityStoreEntityId?: string +): LeftPanelTabsType => useMemo(() => { - return [ + const riskTab = [ getRiskInputTab({ entityName: name, entityType: EntityType.service, scopeId, }), ]; - }, [name, scopeId]); + + const resolutionTab = entityStoreEntityId + ? [getResolutionGroupTab({ entityId: entityStoreEntityId, entityType: 'service' })] + : []; + + return [...riskTab, ...resolutionTab]; + }, [name, scopeId, entityStoreEntityId]); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/content.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/content.tsx index fa656afa5e0f3..b0f6083ff3a86 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/content.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/content.tsx @@ -19,6 +19,7 @@ import { ObservedEntity } from '../shared/components/observed_entity'; import type { ObservedEntityData } from '../shared/components/observed_entity/types'; import { useObservedServiceItems } from './hooks/use_observed_service_items'; import type { EntityDetailsPath } from '../shared/components/left_panel/left_panel_header'; +import { ResolutionSection } from '../../../entity_analytics/components/entity_resolution/resolution_section'; export const OBSERVED_SERVICE_QUERY_ID = 'observedServiceDetailsQuery'; @@ -32,6 +33,7 @@ interface ServicePanelContentProps { onAssetCriticalityChange: () => void; openDetailsPanel: (path: EntityDetailsPath) => void; entityRecord?: Entity; + entityStoreEntityId?: string; } export const ServicePanelContent = ({ @@ -44,6 +46,7 @@ export const ServicePanelContent = ({ scopeId, openDetailsPanel, onAssetCriticalityChange, + entityStoreEntityId, }: ServicePanelContentProps) => { const observedFields = useObservedServiceItems(observedService); @@ -63,6 +66,12 @@ export const ServicePanelContent = ({ )} + {entityStoreEntityId && ( + <> + + + + )} void) => { const { telemetry } = useKibana().services; const { openLeftPanel } = useExpandableFlyoutApi(); @@ -42,10 +44,19 @@ export const useNavigateToServiceDetails = ({ scopeId, entityId, serviceName, + entityStoreEntityId, path, }, }); }, - [isRiskScoreExist, openLeftPanel, scopeId, entityId, serviceName, telemetry] + [ + isRiskScoreExist, + openLeftPanel, + scopeId, + entityId, + serviceName, + entityStoreEntityId, + telemetry, + ] ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/index.tsx index b029a2f6ba0cc..538a2c5f87f53 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/index.tsx @@ -107,19 +107,30 @@ export const ServicePanel = ({ contextID, scopeId, entityId, serviceName }: Serv setQuery, }); + const entityStoreEntityId = entityStoreV2Enabled + ? entityFromStoreResult.entityRecord?.entity?.id + : undefined; + const openDetailsPanel = useNavigateToServiceDetails({ serviceName, entityId, scopeId, isRiskScoreExist, + entityStoreEntityId, }); + const defaultTab = useMemo(() => { + if (isRiskScoreExist) return EntityDetailsLeftPanelTab.RISK_INPUTS; + if (entityStoreEntityId) return EntityDetailsLeftPanelTab.RESOLUTION_GROUP; + return EntityDetailsLeftPanelTab.RISK_INPUTS; + }, [isRiskScoreExist, entityStoreEntityId]); + const openPanelFirstTab = useCallback( () => openDetailsPanel({ - tab: EntityDetailsLeftPanelTab.RISK_INPUTS, + tab: defaultTab, }), - [openDetailsPanel] + [openDetailsPanel, defaultTab] ); if (observedService.isLoading) { @@ -129,7 +140,7 @@ export const ServicePanel = ({ contextID, scopeId, entityId, serviceName }: Serv return ( <> @@ -144,6 +155,7 @@ export const ServicePanel = ({ contextID, scopeId, entityId, serviceName }: Serv contextID={contextID} scopeId={scopeId} openDetailsPanel={openDetailsPanel} + entityStoreEntityId={entityStoreEntityId} /> ); From be096d54d91de262843761fa4f187a024b14931e Mon Sep 17 00:00:00 2001 From: Maxim Kholod Date: Wed, 1 Apr 2026 13:36:05 +0200 Subject: [PATCH 2/2] Add identityFields fallback to service panel useEntityFromStore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the service flyout is opened without entityId (e.g., from the Entity Analytics data grid), fall back to querying by service.name identity fields — matching the existing host panel pattern. Fixes #260398 --- .../public/flyout/entity_details/service_right/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/index.tsx index 538a2c5f87f53..a43361e053f8f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/index.tsx @@ -51,8 +51,13 @@ const FIRST_RECORD_PAGINATION = { export const ServicePanel = ({ contextID, scopeId, entityId, serviceName }: ServicePanelProps) => { const entityStoreV2Enabled = useUiSetting(FF_ENABLE_ENTITY_STORE_V2, false); + const serviceStoreIdentityFields = useMemo( + () => (!entityId && serviceName ? { 'service.name': serviceName } : undefined), + [entityId, serviceName] + ); const entityFromStoreResult = useEntityFromStore({ entityId, + identityFields: serviceStoreIdentityFields, entityType: 'service', skip: !entityStoreV2Enabled, });