diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/constants.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/constants.ts index 38e5f1ed1974e..cb8741eb00714 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/constants.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/constants.ts @@ -21,6 +21,7 @@ export const ALERT_WORKFLOW_STATUS_FIELD_NAME = 'kibana.alert.workflow_status'; export const HOST_NAME_FIELD = 'host.name'; export const HOST_HOSTNAME_FIELD = 'host.hostname'; +export const USER_NAME_FIELD = 'user.name'; // Also see: x-pack/solutions/security/plugins/security_solution/public/one_discover/cell_renderers/cell_renderers.tsx export const ALLOWED_CELL_RENDER_FIELDS = [ @@ -29,4 +30,5 @@ export const ALLOWED_CELL_RENDER_FIELDS = [ LEGACY_SIGNAL_RULE_NAME_FIELD_NAME, HOST_NAME_FIELD, HOST_HOSTNAME_FIELD, + USER_NAME_FIELD, ]; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.test.ts b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.test.ts index 0bed03d6649d2..141182a7fd95b 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.test.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profile_providers/security/security_root_profile/profile.test.ts @@ -103,6 +103,17 @@ describe('createSecurityRootProfileProvider', () => { expect(result['kibana.alert.workflow_status']).toBeDefined(); }); + it('should add user.name cell renderer for alerts index', async () => { + const { provider, context } = await resolveSecurityContext((fieldName) => + fieldName === 'user.name' ? MockComponent : undefined + ); + const getCellRenderers = provider.profile.getCellRenderers!(() => ({}), { context }); + const result = getCellRenderers({ + dataView: createMockDataView(`${ALERTS_INDEX_PATTERN}default`), + } as Parameters[0]); + expect(result['user.name']).toBeDefined(); + }); + it('should add cell renderers for IP fields without overriding existing ones', async () => { const ExistingRenderer: FunctionComponent = () => null; const { provider, context } = await resolveSecurityContext(() => MockComponent); diff --git a/x-pack/platform/plugins/private/translations/translations/de-DE.json b/x-pack/platform/plugins/private/translations/translations/de-DE.json index d331badd629c6..6cef29061668d 100644 --- a/x-pack/platform/plugins/private/translations/translations/de-DE.json +++ b/x-pack/platform/plugins/private/translations/translations/de-DE.json @@ -43789,7 +43789,6 @@ "xpack.securitySolution.flyout.entityDetails.service.stateLabel": "Zustand", "xpack.securitySolution.flyout.entityDetails.service.typeLabel": "Typ", "xpack.securitySolution.flyout.entityDetails.service.versionLabel": "Version", - "xpack.securitySolution.flyout.entityDetails.showAssetDocument": "Asset-Details anzeigen", "xpack.securitySolution.flyout.entityDetails.summaryView": "Zusammenfassung", "xpack.securitySolution.flyout.entityDetails.table.documentFieldsCaption": "Entitätsfelder", "xpack.securitySolution.flyout.entityDetails.table.fieldCellLabel": "Feld", @@ -46498,7 +46497,6 @@ "xpack.securitySolution.timeline.toggleEventDetailsTitle": "Ereignisdetails erweitern", "xpack.securitySolution.timeline.unsavedWorkMessage": "Möchten Sie die Zeitleiste mit nicht gespeicherter Arbeit verlassen?", "xpack.securitySolution.timeline.unsavedWorkTitle": "Nicht gespeicherte Änderungen", - "xpack.securitySolution.timeline.userDetails.updatedTime": "Aktualisiert {time}", "xpack.securitySolution.timeline.youAreInAnEventRendererScreenReaderOnly": "Sie befinden sich in einem Ereignis-Renderer für Zeile {row}. Drücken Sie die Aufwärtspfeiltaste, um die Bearbeitung zu beenden und zur aktuellen Zeile zurückzukehren, oder die Abwärtspfeiltaste, um die Bearbeitung zu beenden und zur nächsten Zeile zu gelangen.", "xpack.securitySolution.timelines.allTimelines.errorFetchingTimelinesTitle": "Fehler beim Abfragen aller Zeitleistendaten", "xpack.securitySolution.timelines.allTimelines.importTimelineTitle": "Import", diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 27cd2edb90809..b7f939b4359c9 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -43641,7 +43641,6 @@ "xpack.securitySolution.flyout.entityDetails.service.stateLabel": "État", "xpack.securitySolution.flyout.entityDetails.service.typeLabel": "Type", "xpack.securitySolution.flyout.entityDetails.service.versionLabel": "Version", - "xpack.securitySolution.flyout.entityDetails.showAssetDocument": "Montrer les détails de ressource", "xpack.securitySolution.flyout.entityDetails.summaryView": "résumé", "xpack.securitySolution.flyout.entityDetails.table.documentFieldsCaption": "Champs d'entités", "xpack.securitySolution.flyout.entityDetails.table.fieldCellLabel": "Champ", @@ -46335,7 +46334,6 @@ "xpack.securitySolution.timeline.toggleEventDetailsTitle": "Développer les détails de l'événement", "xpack.securitySolution.timeline.unsavedWorkMessage": "Quitter Timeline avec un travail non enregistré ?", "xpack.securitySolution.timeline.unsavedWorkTitle": "Modifications non enregistrées", - "xpack.securitySolution.timeline.userDetails.updatedTime": "Mis à jour le {time}", "xpack.securitySolution.timeline.youAreInAnEventRendererScreenReaderOnly": "Vous êtes dans un outil de rendu d'événement pour la ligne : {row}. Appuyez sur la touche fléchée vers le haut pour quitter et revenir à la ligne en cours, ou sur la touche fléchée vers le bas pour quitter et passer à la ligne suivante.", "xpack.securitySolution.timelines.allTimelines.errorFetchingTimelinesTitle": "Impossible d'interroger les données de toutes les chronologies", "xpack.securitySolution.timelines.allTimelines.importTimelineTitle": "Importer", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index 9e8c0043bed8f..2f4b46905e556 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -43954,7 +43954,6 @@ "xpack.securitySolution.flyout.entityDetails.service.stateLabel": "ステータス", "xpack.securitySolution.flyout.entityDetails.service.typeLabel": "型", "xpack.securitySolution.flyout.entityDetails.service.versionLabel": "バージョン", - "xpack.securitySolution.flyout.entityDetails.showAssetDocument": "アセット詳細を表示", "xpack.securitySolution.flyout.entityDetails.summaryView": "まとめ", "xpack.securitySolution.flyout.entityDetails.table.documentFieldsCaption": "エンティティフィールド", "xpack.securitySolution.flyout.entityDetails.table.fieldCellLabel": "フィールド", @@ -46671,7 +46670,6 @@ "xpack.securitySolution.timeline.toggleEventDetailsTitle": "イベントの詳細を展開", "xpack.securitySolution.timeline.unsavedWorkMessage": "作業を保存せずにタイムラインから移動しますか?", "xpack.securitySolution.timeline.unsavedWorkTitle": "保存されていない変更", - "xpack.securitySolution.timeline.userDetails.updatedTime": "更新日時{time}", "xpack.securitySolution.timeline.youAreInAnEventRendererScreenReaderOnly": "行{row}のイベントレンダラーを表示しています。上矢印キーを押すと、終了して現在の行に戻ります。下矢印キーを押すと、終了して次の行に進みます。", "xpack.securitySolution.timelines.allTimelines.errorFetchingTimelinesTitle": "すべてのタイムラインデータをクエリできませんでした", "xpack.securitySolution.timelines.allTimelines.importTimelineTitle": "インポート", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 2189936c33c99..cee3531d37290 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -43947,7 +43947,6 @@ "xpack.securitySolution.flyout.entityDetails.service.stateLabel": "状态", "xpack.securitySolution.flyout.entityDetails.service.typeLabel": "类型", "xpack.securitySolution.flyout.entityDetails.service.versionLabel": "版本", - "xpack.securitySolution.flyout.entityDetails.showAssetDocument": "显示资产详情", "xpack.securitySolution.flyout.entityDetails.summaryView": "摘要", "xpack.securitySolution.flyout.entityDetails.table.documentFieldsCaption": "实体字段", "xpack.securitySolution.flyout.entityDetails.table.fieldCellLabel": "字段", @@ -46661,7 +46660,6 @@ "xpack.securitySolution.timeline.toggleEventDetailsTitle": "展开事件详情", "xpack.securitySolution.timeline.unsavedWorkMessage": "离开有未保存工作的时间线?", "xpack.securitySolution.timeline.unsavedWorkTitle": "未保存的更改", - "xpack.securitySolution.timeline.userDetails.updatedTime": "更新于 {time}", "xpack.securitySolution.timeline.youAreInAnEventRendererScreenReaderOnly": "您正处于第 {row} 行的事件呈现器中。按向上箭头键退出并返回当前行,或按向下箭头键退出并前进到下一行。", "xpack.securitySolution.timelines.allTimelines.errorFetchingTimelinesTitle": "无法查询所有时间线数据", "xpack.securitySolution.timelines.allTimelines.importTimelineTitle": "导入", diff --git a/x-pack/solutions/security/plugins/security_solution/public/agent_builder/components/entity_card_flyout_overview_canvas.tsx b/x-pack/solutions/security/plugins/security_solution/public/agent_builder/components/entity_card_flyout_overview_canvas.tsx index 1d0a7ce794b67..d7adf9edbe09d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/agent_builder/components/entity_card_flyout_overview_canvas.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/agent_builder/components/entity_card_flyout_overview_canvas.tsx @@ -67,11 +67,11 @@ import { } from '../../flyout/entity_details/shared/constants'; import type { EntityDetailsPath } from '../../flyout/entity_details/shared/components/left_panel/left_panel_header'; import { EntityEventTypes } from '../../common/lib/telemetry'; -import { UserPanelContent } from '../../flyout/entity_details/user_right/content'; -import { UserPanelHeader } from '../../flyout/entity_details/user_right/header'; -import { useObservedUser } from '../../flyout/entity_details/user_right/hooks/use_observed_user'; +import { Content as UserPanelContent } from '../../flyout_v2/entity/user/main/content'; +import { Header as UserPanelHeader } from '../../flyout_v2/entity/user/main/header'; +import { useObservedUser } from '../../flyout_v2/entity/user/main/hooks/use_observed_user'; import { useManagedUser } from '../../flyout/entity_details/shared/hooks/use_managed_user'; -import { USER_PANEL_RISK_SCORE_QUERY_ID } from '../../flyout/entity_details/user_right/constants'; +import { USER_PANEL_RISK_SCORE_QUERY_ID } from '../../flyout_v2/entity/user/main/constants'; import { UserDetailsPanelKey } from '../../flyout/entity_details/user_details_left'; import { ServicePanelContent } from '../../flyout/entity_details/service_right/content'; import { ServicePanelHeader } from '../../flyout/entity_details/service_right/header'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/pages/details/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/pages/details/index.tsx index 208c53c1e588e..f38c897982838 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/pages/details/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/explore/hosts/pages/details/index.tsx @@ -80,7 +80,7 @@ import { useEntityFromStore, type EntityStoreRecord, } from '../../../../flyout/entity_details/shared/hooks/use_entity_from_store'; -import { ObservedDataSection as HostObservedDataSection } from '../../../../flyout_v2/entity/host/main/components/observed_data_section'; +import { ObservedDataSection as HostObservedDataSection } from '../../../../flyout_v2/entity/shared/components/observed_data_section'; import { HOST_PANEL_OBSERVED_HOST_QUERY_ID } from '../../../../flyout/entity_details/host_right'; import { useObservedHost } from '../../../../flyout_v2/entity/host/main/hooks/use_observed_host'; import { buildRiskScoreStateFromEntityRecord } from '../../../../flyout/entity_details/shared/entity_store_risk_utils'; @@ -403,8 +403,9 @@ const HostDetailsComponent: React.FC = ({ /> = ({ /> void; + /** Called when the user clicks a tab that opens a CSP insight sub-panel. */ + onShowDetailsPanel?: (subTab: CspInsightLeftPanelSubTab) => void; + /** Custom renderer for entity link fields; receives the field name, raw value, and optional children. */ + linkRenderer?: FieldLinkRenderer; +} + +export interface EntitySectionOverrideBuilders { + buildUserOverrides?: (entity: { name: string; entityId?: string }) => EntitySectionOverrides; + buildHostOverrides?: (entity: { name: string; entityId?: string }) => EntitySectionOverrides; +} + const resolveUserDisplayForEntities = ( identityFields: IdentityFields | undefined, getFieldsData: GetFieldsData @@ -44,7 +60,10 @@ const resolveHostDisplayForEntities = ( /** * Entities displayed in the document details expandable flyout left section under the Insights tab */ -export const EntitiesDetails: React.FC = () => { +export const EntitiesDetails: React.FC = ({ + buildUserOverrides, + buildHostOverrides, +}) => { const { getFieldsData, scopeId, dataAsNestedObject } = useDocumentDetailsContext(); const timestamp = getField(getFieldsData('@timestamp')); @@ -103,6 +122,25 @@ export const EntitiesDetails: React.FC = () => { const userDisplayName = userEntityFromStore.entityRecord?.entity?.name ?? resolvedUserName; const hostDisplayName = hostEntityFromStore.entityRecord?.entity?.name ?? resolvedHostName; + const userStoreEntityId = userEntityFromStore?.entityRecord?.entity?.id; + const hostStoreEntityId = hostEntityFromStore?.entityRecord?.entity?.id; + + const userOverrides = useMemo( + () => + userDisplayName != null + ? buildUserOverrides?.({ name: userDisplayName, entityId: userStoreEntityId }) + : undefined, + [buildUserOverrides, userDisplayName, userStoreEntityId] + ); + + const hostOverrides = useMemo( + () => + hostDisplayName != null + ? buildHostOverrides?.({ name: hostDisplayName, entityId: hostStoreEntityId }) + : undefined, + [buildHostOverrides, hostDisplayName, hostStoreEntityId] + ); + const showUserDetails = timestamp != null && userDisplayName != null; const showHostDetails = hostEntityIdentifiers != null && timestamp != null && hostDisplayName != null; @@ -125,9 +163,10 @@ export const EntitiesDetails: React.FC = () => { )} @@ -145,10 +184,11 @@ export const EntitiesDetails: React.FC = () => { )} diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx index d2c85c409251c..672481cd98c24 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type { FieldLinkRenderer } from '../../../entity_details/shared/components/entity_table/types'; import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { v4 as uuid } from 'uuid'; @@ -140,6 +141,22 @@ export interface HostDetailsProps { * Set for attack flyout entity panels; omit in document details flyout. */ isAttackDetails?: boolean; + /** + * When provided, opens the host flyout using the v2 system-flyout pattern instead of + * the expandable-flyout preview panel. Use when this component is rendered inside a v2 + * system flyout where the expandable-flyout API dispatches into an isolated provider. + */ + onPreviewEntity?: () => void; + /** + * When provided, opens the CSP detail panel (alerts / misconfigurations / vulnerabilities) + * using the v2 system-flyout pattern instead of `openLeftPanel`. Use alongside `onPreviewEntity`. + */ + onShowDetailsPanel?: (subTab: CspInsightLeftPanelSubTab) => void; + /** + * When provided, wraps user-name and IP cell values in the Related Users table using + * this renderer instead of the v1 `PreviewLink`. Use alongside `onPreviewEntity`. + */ + linkRenderer?: FieldLinkRenderer; } /** @@ -153,6 +170,9 @@ export const HostDetails: React.FC = ({ expandedOnFirstRender = true, hostEntityFromStoreResult, isAttackDetails = false, + onPreviewEntity, + onShowDetailsPanel, + linkRenderer: LinkRenderer, }) => { const EntityCellActions = isAttackDetails ? AttackDetailsCellActions : DocumentDetailsCellActions; @@ -194,6 +214,10 @@ export const HostDetails: React.FC = ({ ); const openHostPreview = useCallback(() => { + if (onPreviewEntity) { + onPreviewEntity(); + return; + } openPreviewPanel({ id: HostPreviewPanelKey, params: { @@ -207,7 +231,7 @@ export const HostDetails: React.FC = ({ location: scopeId, panel: 'preview', }); - }, [openPreviewPanel, hostName, entityId, scopeId, telemetry]); + }, [onPreviewEntity, openPreviewPanel, hostName, entityId, scopeId, telemetry]); const entityStoreV2Enabled = useUiSetting(FF_ENABLE_ENTITY_STORE_V2); const euidApi = useEntityStoreEuidApi(); @@ -358,13 +382,19 @@ export const HostDetails: React.FC = ({ render: (user: string) => ( - + {LinkRenderer ? ( + + {user} + + ) : ( + + )} ), @@ -386,6 +416,10 @@ export const HostDetails: React.FC = ({ render={(ip) => ip == null ? ( getEmptyTagValue() + ) : LinkRenderer ? ( + + {ip} + ) : ( = ({ ] : []), ], - [EntityCellActions, isEntityAnalyticsAuthorized, scopeId, entityId] + [EntityCellActions, isEntityAnalyticsAuthorized, scopeId, entityId, LinkRenderer] ); const relatedUsersCount = useMemo( @@ -517,6 +551,7 @@ export const HostDetails: React.FC = ({ deleteQuery={deleteQuery} scopeId={scopeId} isFlyoutOpen={true} + linkRenderer={LinkRenderer} riskScoreState={effectiveRiskScoreState} firstSeenFromEntityStore={ entityStoreV2Enabled ? observedHost.firstSeen?.date ?? undefined : undefined @@ -539,10 +574,12 @@ export const HostDetails: React.FC = ({ queryId={`${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}-document-details-host-entities`} direction="column" onShowAlertCountDetails={() => - openDetailsPanel({ - tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, - subTab: CspInsightLeftPanelSubTab.ALERTS, - }) + onShowDetailsPanel + ? onShowDetailsPanel(CspInsightLeftPanelSubTab.ALERTS) + : openDetailsPanel({ + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab: CspInsightLeftPanelSubTab.ALERTS, + }) } data-test-subj={HOST_DETAILS_ALERT_COUNT_TEST_ID} /> @@ -550,10 +587,12 @@ export const HostDetails: React.FC = ({ identityFields={hostInsightsIdentityFields} direction="column" onShowMisconfigurationsDetails={() => - openDetailsPanel({ - tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, - subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS, - }) + onShowDetailsPanel + ? onShowDetailsPanel(CspInsightLeftPanelSubTab.MISCONFIGURATIONS) + : openDetailsPanel({ + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS, + }) } data-test-subj={HOST_DETAILS_MISCONFIGURATIONS_TEST_ID} telemetryKey={MISCONFIGURATION_INSIGHT_HOST_DETAILS} @@ -562,10 +601,12 @@ export const HostDetails: React.FC = ({ identityFields={hostInsightsIdentityFields} direction="column" onShowVulnerabilitiesDetails={() => - openDetailsPanel({ - tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, - subTab: CspInsightLeftPanelSubTab.VULNERABILITIES, - }) + onShowDetailsPanel + ? onShowDetailsPanel(CspInsightLeftPanelSubTab.VULNERABILITIES) + : openDetailsPanel({ + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab: CspInsightLeftPanelSubTab.VULNERABILITIES, + }) } data-test-subj={HOST_DETAILS_VULNERABILITIES_TEST_ID} telemetryKey={VULNERABILITIES_INSIGHT_HOST_DETAILS} diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/user_details.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/user_details.test.tsx index 8af179d919ae7..582c0a5d739c7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/user_details.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/user_details.test.tsx @@ -14,7 +14,7 @@ import { DocumentDetailsContext } from '../../shared/context'; import { UserDetails } from './user_details'; import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; import { mockAnomalies } from '../../../../common/components/ml/mock'; -import { useObservedUser } from '../../../entity_details/user_right/hooks/use_observed_user'; +import { useObservedUser } from '../../../../flyout_v2/entity/user/main/hooks/use_observed_user'; import { useUserRelatedHosts } from '../../../../common/containers/related_entities/related_hosts'; import { RiskSeverity } from '../../../../../common/search_strategy'; import { @@ -91,7 +91,7 @@ jest.mock('../../../../common/components/ml/anomaly/anomaly_table_provider', () jest.mock('../../../../helper_hooks', () => ({ useHasSecurityCapability: () => true })); -jest.mock('../../../entity_details/user_right/hooks/use_observed_user'); +jest.mock('../../../../flyout_v2/entity/user/main/hooks/use_observed_user'); const mockUseObservedUser = useObservedUser as jest.Mock; jest.mock('../../../../common/containers/related_entities/related_hosts'); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx index 6dcb46a8d16e2..359f039875d0d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type { FieldLinkRenderer } from '../../../entity_details/shared/components/entity_table/types'; import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { v4 as uuid } from 'uuid'; @@ -78,7 +79,7 @@ import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_sc import { useSelectedPatterns } from '../../../../data_view_manager/hooks/use_selected_patterns'; import { useSecurityDefaultPatterns } from '../../../../data_view_manager/hooks/use_security_default_patterns'; import { useEntityFromStore } from '../../../entity_details/shared/hooks/use_entity_from_store'; -import { useObservedUser } from '../../../entity_details/user_right/hooks/use_observed_user'; +import { useObservedUser } from '../../../../flyout_v2/entity/user/main/hooks/use_observed_user'; import { buildRiskScoreStateFromEntityRecord, getRiskFromEntityRecord, @@ -123,6 +124,22 @@ export interface UserDetailsProps { * Set for attack flyout entity panels; omit in document details flyout. */ isAttackDetails?: boolean; + /** + * When provided, opens the user flyout using the v2 system-flyout pattern instead of + * the expandable-flyout preview panel. Use when this component is rendered inside a v2 + * system flyout where the expandable-flyout API dispatches into an isolated provider. + */ + onPreviewEntity?: () => void; + /** + * When provided, opens the CSP detail panel (alerts / misconfigurations) using the v2 + * system-flyout pattern instead of `openLeftPanel`. Use alongside `onPreviewEntity`. + */ + onShowDetailsPanel?: (subTab: CspInsightLeftPanelSubTab) => void; + /** + * When provided, wraps host-name and IP cell values in the Related Hosts table using + * this renderer instead of the v1 `PreviewLink`. Use alongside `onPreviewEntity`. + */ + linkRenderer?: FieldLinkRenderer; } /** @@ -135,6 +152,9 @@ export const UserDetails: React.FC = ({ scopeId, expandedOnFirstRender = true, isAttackDetails = false, + onPreviewEntity, + onShowDetailsPanel, + linkRenderer: LinkRenderer, }) => { const EntityCellActions = isAttackDetails ? AttackDetailsCellActions : DocumentDetailsCellActions; @@ -179,6 +199,10 @@ export const UserDetails: React.FC = ({ const euidApi = useEntityStoreEuidApi(); const openUserPreview = useCallback(() => { + if (onPreviewEntity) { + onPreviewEntity(); + return; + } openPreviewPanel({ id: UserPreviewPanelKey, params: { @@ -192,7 +216,7 @@ export const UserDetails: React.FC = ({ location: scopeId, panel: 'preview', }); - }, [openPreviewPanel, userName, entityId, scopeId, telemetry]); + }, [onPreviewEntity, openPreviewPanel, userName, entityId, scopeId, telemetry]); const entityFromStoreResult = useEntityFromStore({ entityId, @@ -318,13 +342,19 @@ export const UserDetails: React.FC = ({ render: (host: string) => ( - + {LinkRenderer ? ( + + {host} + + ) : ( + + )} ), @@ -346,6 +376,10 @@ export const UserDetails: React.FC = ({ render={(ip) => ip == null ? ( getEmptyTagValue() + ) : LinkRenderer ? ( + + {ip} + ) : ( = ({ ] : []), ], - [EntityCellActions, isEntityAnalyticsAuthorized, scopeId, entityId] + [EntityCellActions, isEntityAnalyticsAuthorized, scopeId, entityId, LinkRenderer] ); const relatedHostsCount = useMemo( @@ -475,6 +509,7 @@ export const UserDetails: React.FC = ({ jobNameById={jobNameById} scopeId={scopeId} isFlyoutOpen={true} + linkRenderer={LinkRenderer} riskScoreState={userRiskScoreStateFromEntityStore} firstSeenFromEntityStore={ entityStoreV2Enabled ? observedUser.firstSeen?.date ?? undefined : undefined @@ -495,10 +530,12 @@ export const UserDetails: React.FC = ({ queryId={`${USER_DETAILS_INSIGHTS_ID}-alerts-by-status`} direction="column" onShowAlertCountDetails={() => - openDetailsPanel({ - tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, - subTab: CspInsightLeftPanelSubTab.ALERTS, - }) + onShowDetailsPanel + ? onShowDetailsPanel(CspInsightLeftPanelSubTab.ALERTS) + : openDetailsPanel({ + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab: CspInsightLeftPanelSubTab.ALERTS, + }) } data-test-subj={USER_DETAILS_ALERT_COUNT_TEST_ID} /> @@ -506,10 +543,12 @@ export const UserDetails: React.FC = ({ identityFields={userIdentityFields ?? {}} direction="column" onShowMisconfigurationsDetails={() => - openDetailsPanel({ - tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, - subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS, - }) + onShowDetailsPanel + ? onShowDetailsPanel(CspInsightLeftPanelSubTab.MISCONFIGURATIONS) + : openDetailsPanel({ + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS, + }) } data-test-subj={USER_DETAILS_MISCONFIGURATIONS_TEST_ID} telemetryKey={MISCONFIGURATION_INSIGHT_USER_DETAILS} diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/mocks/index.ts b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/mocks/index.ts index ac7ac4fc410af..430f439143f20 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/mocks/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/mocks/index.ts @@ -17,7 +17,7 @@ import type { } from '../../../../common/search_strategy'; import { HostPolicyResponseActionStatus, RiskSeverity } from '../../../../common/search_strategy'; import { RiskCategories } from '../../../../common/entity_analytics/risk_engine'; -import type { ObservedEntityData } from '../shared/components/observed_entity/types'; +import type { ObservedEntityData } from '../../../flyout_v2/entity/shared/components/observed_entity/types'; import type { HostEntity } from '../../../../common/api/entity_analytics'; const userRiskScore: UserRiskScore = { 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 0f89b0c86bb48..031f12aba843f 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 @@ -14,8 +14,8 @@ import { FlyoutRiskSummary } from '../../../entity_analytics/components/risk_sum import type { RiskScoreState } from '../../../entity_analytics/api/hooks/use_risk_score'; import { EntityType } from '../../../../common/entity_analytics/types'; import { SERVICE_PANEL_RISK_SCORE_QUERY_ID } from '.'; -import { ObservedEntity } from '../shared/components/observed_entity'; -import type { ObservedEntityData } from '../shared/components/observed_entity/types'; +import { ObservedEntity } from '../../../flyout_v2/entity/shared/components/observed_entity'; +import type { ObservedEntityData } from '../../../flyout_v2/entity/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 { VisualizationsSection } from '../shared/components/right/visualizations_section'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/header.tsx index 446e005e9b981..9af0a1b7d7264 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/header.tsx @@ -16,7 +16,7 @@ import type { ServiceItem } from '../../../../common/search_strategy/security_so import { PreferenceFormattedDate } from '../../../common/components/formatted_date'; import { FlyoutHeader } from '../../shared/components/flyout_header'; import { FlyoutTitle } from '../../../flyout_v2/shared/components/flyout_title'; -import type { ObservedEntityData } from '../shared/components/observed_entity/types'; +import type { ObservedEntityData } from '../../../flyout_v2/entity/shared/components/observed_entity/types'; import { EntitySourceBadge } from '../shared/components/entity_source_badge'; import { RiskLevelBadge } from '../shared/components/risk_level_badge'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/hooks/use_observed_service.ts b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/hooks/use_observed_service.ts index 17d278f2ae988..296be8539efed 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/hooks/use_observed_service.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/hooks/use_observed_service.ts @@ -9,7 +9,7 @@ import { useMemo } from 'react'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { inputsSelectors } from '../../../../common/store'; import { useQueryInspector } from '../../../../common/components/page/manage_query'; -import type { ObservedEntityData } from '../../shared/components/observed_entity/types'; +import type { ObservedEntityData } from '../../../../flyout_v2/entity/shared/components/observed_entity/types'; import type { ServiceItem } from '../../../../../common/search_strategy'; import { Direction, NOT_EVENT_KIND_ASSET_FILTER } from '../../../../../common/search_strategy'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/hooks/use_observed_service_items.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/hooks/use_observed_service_items.tsx index 6a994cefb6a36..0c83d95f43ed4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/hooks/use_observed_service_items.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/hooks/use_observed_service_items.tsx @@ -9,7 +9,7 @@ import React from 'react'; import type { ServiceItem } from '../../../../../common/search_strategy'; import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; import * as i18n from './translations'; -import type { ObservedEntityData } from '../../shared/components/observed_entity/types'; +import type { ObservedEntityData } from '../../../../flyout_v2/entity/shared/components/observed_entity/types'; import type { EntityTableRows } from '../../shared/components/entity_table/types'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/mocks/index.ts b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/mocks/index.ts index 38e35e7b9f5e2..0fb31407b4e24 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/mocks/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/service_right/mocks/index.ts @@ -6,7 +6,7 @@ */ import type { ServiceItem } from '../../../../../common/search_strategy'; -import type { ObservedEntityData } from '../../shared/components/observed_entity/types'; +import type { ObservedEntityData } from '../../../../flyout_v2/entity/shared/components/observed_entity/types'; const observedServiceDetails: ServiceItem = { service: { diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/common.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/common.tsx index 95d4758c2c449..cac7d7a0e93bc 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/common.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/common.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import type { ObservedEntityData } from './components/observed_entity/types'; +import type { ObservedEntityData } from '../../../flyout_v2/entity/shared/components/observed_entity/types'; import type { MlCapabilitiesProvider } from '../../../common/components/ml/permissions/ml_capabilities_provider'; import { hasMlUserPermissions } from '../../../../common/machine_learning/has_ml_user_permissions'; import { getEmptyTagValue } from '../../../common/components/empty_value'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/anomalies_field.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/anomalies_field.tsx index b050e2a55ed52..e8dca4a9f54cd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/anomalies_field.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/anomalies_field.tsx @@ -8,7 +8,7 @@ import React, { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import type { NarrowDateRange } from '../../../../common/components/ml/types'; -import type { EntityAnomalies } from './observed_entity/types'; +import type { EntityAnomalies } from '../../../../flyout_v2/entity/shared/components/observed_entity/types'; import { AnomalyScores } from '../../../../common/components/ml/score/anomaly_scores'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { scoreIntervalToDateTime } from '../../../../common/components/ml/score/score_interval_to_datetime'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/columns.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/columns.tsx index 386823c5924ea..a59863dcc8e52 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/columns.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/columns.tsx @@ -14,7 +14,7 @@ import { toFieldRendererItems, } from '../../../../../timelines/components/field_renderers/default_renderer'; import { getEmptyTagValue } from '../../../../../common/components/empty_value'; -import type { BasicEntityData, EntityTableColumns, EntityTableLinkRenderer } from './types'; +import type { BasicEntityData, EntityTableColumns, FieldLinkRenderer } from './types'; import { isFlyoutLink } from '../../../../shared/utils/link_utils'; import { PreviewLink } from '../../../../shared/components/preview_link'; @@ -22,7 +22,7 @@ export const getEntityTableColumns = ( contextID: string, scopeId: string, data: T, - linkRenderer?: EntityTableLinkRenderer + linkRenderer?: FieldLinkRenderer ): EntityTableColumns => [ { name: ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/index.tsx index 4c25ffe90a7b3..f79869c0e15f3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/index.tsx @@ -8,14 +8,14 @@ import React, { useMemo } from 'react'; import { BasicTable } from '../../../../../common/components/ml/tables/basic_table'; import { getEntityTableColumns } from './columns'; -import type { BasicEntityData, EntityTableLinkRenderer, EntityTableRows } from './types'; +import type { BasicEntityData, FieldLinkRenderer, EntityTableRows } from './types'; interface EntityTableProps { contextID: string; scopeId: string; data: T; entityFields: EntityTableRows; - linkRenderer?: EntityTableLinkRenderer; + linkRenderer?: FieldLinkRenderer; } export const EntityTable = ({ diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/types.ts b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/types.ts index 9f1a440a03fcc..cc620a45495ff 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/shared/components/entity_table/types.ts @@ -14,7 +14,7 @@ import type { XOR } from '../../../../../../common/utility_types'; * (e.g., a v2 flyout link). Receives the field name and value, and may render `children` * as the visible content. */ -export type EntityTableLinkRenderer = ComponentType<{ +export type FieldLinkRenderer = ComponentType<{ field: string; value: string; children?: ReactNode; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/components/managed_user_accordion.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/components/managed_user_accordion.test.tsx deleted file mode 100644 index d7a0732050af7..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/components/managed_user_accordion.test.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 { TestProviders } from '../../../../common/mock'; -import { render } from '@testing-library/react'; -import React from 'react'; -import { ManagedUserAccordion } from './managed_user_accordion'; -import { mockEntraUserFields } from '../mocks'; -import { UserAssetTableType } from '../../../../explore/users/store/model'; - -describe('ManagedUserAccordion', () => { - it('it renders children', () => { - const { getByTestId } = render( - - {}} - isPreviewMode={false} - > -
- - - ); - - expect(getByTestId('test-children')).toBeInTheDocument(); - expect(getByTestId('managed-user-accordion-userAssetEntraTitleLink')).toBeInTheDocument(); - expect(getByTestId('managed-user-accordion-userAssetEntraTitleIcon')).toBeInTheDocument(); - }); - - it('renders link without icon when in preview mode', () => { - const { getByTestId, queryByTestId } = render( - - {}} - isPreviewMode - > -
- - - ); - - expect(getByTestId('managed-user-accordion-userAssetEntraTitleLink')).toBeInTheDocument(); - expect(queryByTestId('managed-user-accordion-userAssetEntraTitleIcon')).not.toBeInTheDocument(); - }); -}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/components/managed_user_accordion.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/components/managed_user_accordion.tsx deleted file mode 100644 index 8e6c8b4f1a4d9..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/components/managed_user_accordion.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 { useEuiFontSize } from '@elastic/eui'; -import React, { useCallback, useMemo } from 'react'; -import { css } from '@emotion/react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { get } from 'lodash/fp'; -import type { EntityDetailsPath } from '../../shared/components/left_panel/left_panel_header'; -import { EntityDetailsLeftPanelTab } from '../../shared/components/left_panel/left_panel_header'; -import { ExpandablePanel } from '../../../../flyout_v2/shared/components/expandable_panel'; -import type { ManagedUserFields } from '../../../../../common/search_strategy/security_solution/users/managed_details'; -import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; -import { ONE_WEEK_IN_HOURS } from '../../shared/constants'; -import { UserAssetTableType } from '../../../../explore/users/store/model'; - -interface ManagedUserAccordionProps { - children: React.ReactNode; - title: string; - managedUser: ManagedUserFields; - tableType: UserAssetTableType; - openDetailsPanel: (path: EntityDetailsPath) => void; - isPreviewMode: boolean; -} - -export const ManagedUserAccordion: React.FC = ({ - children, - title, - managedUser, - tableType, - openDetailsPanel, - isPreviewMode, -}) => { - const xsFontSize = useEuiFontSize('xxs').fontSize; - const timestamp = get('@timestamp[0]', managedUser) as unknown as string | undefined; - - const goToEntityInsightTab = useCallback( - () => - openDetailsPanel({ - tab: - tableType === UserAssetTableType.assetOkta - ? EntityDetailsLeftPanelTab.OKTA - : EntityDetailsLeftPanelTab.ENTRA, - }), - [openDetailsPanel, tableType] - ); - - const link = useMemo( - () => ({ - callback: goToEntityInsightTab, - tooltip: ( - - ), - }), - [goToEntityInsightTab] - ); - - return ( - - - ), - }} - /> - - ), - link, - }} - expand={{ expandable: false }} - > - {children} - - ); -}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/components/observed_data_section.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/components/observed_data_section.tsx deleted file mode 100644 index f10415f664fe3..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/components/observed_data_section.tsx +++ /dev/null @@ -1,219 +0,0 @@ -/* - * 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 { - EuiAccordion, - EuiLoadingSpinner, - EuiTitle, - useEuiFontSize, - useEuiTheme, -} from '@elastic/eui'; - -import React, { memo, useMemo } from 'react'; -import { css } from '@emotion/react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useEntityStoreEuidApi } from '@kbn/entity-store/public'; -import { useInstalledSecurityJobNameById } from '../../../../common/components/ml/hooks/use_installed_security_jobs'; -import { ONE_WEEK_IN_HOURS } from '../../shared/constants'; -import { ObservedEntity } from '../../shared/components/observed_entity'; -import { useObservedUserItems } from '../hooks/use_observed_user_items'; -import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; -import { InspectButton, InspectButtonContainer } from '../../../../common/components/inspect'; -import { buildAnomaliesTableInfluencersFilterQuery } from '../../../../common/components/ml/anomaly/anomaly_table_euid'; -import { useAnomaliesTableData } from '../../../../common/components/ml/anomaly/use_anomalies_table_data'; -import { useGlobalTime } from '../../../../common/containers/use_global_time'; -import { getCriteriaFromUsersType } from '../../../../common/components/ml/criteria/get_criteria_from_users_type'; -import { UsersType } from '../../../../explore/users/store/model'; -import type { ObservedUserData } from '../content'; -import type { IdentityFields } from '../../../document_details/shared/utils'; -import type { EntityStoreRecord } from '../../shared/hooks/use_entity_from_store'; - -export const ObservedDataSection = memo( - ({ - userName, - identityFields, - entityRecord, - observedUser, - contextID, - scopeId, - queryId, - }: { - userName: string; - identityFields: IdentityFields; - entityRecord?: EntityStoreRecord | null; - observedUser: ObservedUserData; - contextID: string; - scopeId: string; - queryId: string; - }) => { - const { euiTheme } = useEuiTheme(); - const xsFontSize = useEuiFontSize('xxs').fontSize; - - const buttonContent = ( - -

- -

-
- ); - - const extraAction = ( - <> - - - } - /> - - {observedUser.lastSeen.date && ( - - - ), - }} - /> - - )} - - ); - - return ( - - - {observedUser.isLoading ? ( - - ) : ( - - )} - - - ); - } -); -ObservedDataSection.displayName = 'ObservedDataSection'; - -const ObservedDataSectionContent = memo( - ({ - userName, - identityFields, - entityRecord, - observedUser, - contextID, - scopeId, - queryId, - }: { - userName: string; - identityFields: IdentityFields; - entityRecord?: EntityStoreRecord | null; - observedUser: ObservedUserData; - contextID: string; - scopeId: string; - queryId: string; - }) => { - const { to, from, isInitializing } = useGlobalTime(); - - const { jobNameById } = useInstalledSecurityJobNameById(); - const jobIds = useMemo(() => Object.keys(jobNameById), [jobNameById]); - const euidApi = useEntityStoreEuidApi(); - const euid = euidApi?.euid; - const [isLoadingAnomaliesData, anomaliesData] = useAnomaliesTableData({ - criteriaFields: getCriteriaFromUsersType({ - type: UsersType.details, - userName, - entityRecord, - identityFields, - euid, - }), - filterQuery: buildAnomaliesTableInfluencersFilterQuery({ - euid, - entityType: 'user', - entityRecord, - isScopedToEntity: true, - identityFields, - fallbackDisplayName: userName, - }), - startDate: from, - endDate: to, - skip: isInitializing, - jobIds, - aggregationInterval: 'auto', - }); - - const observedUserWithAnomalies = useMemo( - () => ({ - ...observedUser, - entityId: observedUser.entityRecord?.entity.id, - anomalies: { - isLoading: isLoadingAnomaliesData, - anomalies: anomaliesData, - jobNameById, - }, - }), - [observedUser, isLoadingAnomaliesData, anomaliesData, jobNameById] - ); - const observedFields = useObservedUserItems(observedUserWithAnomalies); - - return ( - - ); - } -); -ObservedDataSectionContent.displayName = 'ObservedDataSectionContent'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/footer.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/footer.tsx deleted file mode 100644 index 53a184c3291ed..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/footer.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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, { useMemo } from 'react'; -import { EuiFlyoutFooter, EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { useEntityStoreEuidApi } from '@kbn/entity-store/public'; -import { TakeAction } from '../shared/components/take_action'; -import type { IdentityFields } from '../../document_details/shared/utils'; -import type { EntityStoreRecord } from '../shared/hooks/use_entity_from_store'; -import { AiAssistantButton } from '../../../entity_analytics/components/ai_assistant_button/ai_assistant_button'; -import { EntityType } from '../../../../common/entity_analytics/types'; - -export const UserPanelFooter = ({ - identityFields, - entity, -}: { - identityFields: IdentityFields; - /** When entity store v2 is enabled: entity record from the store. */ - entity?: EntityStoreRecord; -}) => { - const userName = useMemo( - () => identityFields['user.name'] || Object.values(identityFields)[0] || '', - [identityFields] - ); - - const euidApi = useEntityStoreEuidApi(); - const euidEntityFilter = useMemo((): string | undefined => { - if (!euidApi?.euid || !entity) { - return undefined; - } - return euidApi.euid.kql.getEuidFilterBasedOnDocument('user', entity); - }, [euidApi?.euid, entity]); - - return ( - - - - - - - - - - - - - ); -}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/hooks/use_observed_user_items.test.ts b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/hooks/use_observed_user_items.test.ts deleted file mode 100644 index 82814100f295f..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/hooks/use_observed_user_items.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 { renderHook } from '@testing-library/react'; -import { mockObservedUser } from '../mocks'; -import { TestProviders } from '../../../../common/mock'; -import { useObservedUserItems } from './use_observed_user_items'; - -describe('useObservedUserItems', () => { - it('returns observed user fields', () => { - const { result } = renderHook(() => useObservedUserItems(mockObservedUser), { - wrapper: TestProviders, - }); - - expect(result.current).toEqual([ - { - field: 'user.id', - label: 'User ID', - getValues: expect.any(Function), - }, - { - field: 'user.domain', - label: 'Domain', - getValues: expect.any(Function), - }, - { - label: 'First seen', - render: expect.any(Function), - }, - { - label: 'Last seen', - render: expect.any(Function), - }, - { - field: 'host.os.name', - label: 'Operating system', - getValues: expect.any(Function), - }, - { - field: 'host.os.family', - label: 'Family', - - getValues: expect.any(Function), - }, - { - field: 'host.ip', - label: 'IP addresses', - - getValues: expect.any(Function), - }, - { - label: 'Max anomaly score by job', - isVisible: expect.any(Function), - render: expect.any(Function), - }, - ]); - - expect(result.current.map(({ getValues }) => getValues && getValues(mockObservedUser))).toEqual( - [ - ['1234', '321'], // id - ['test domain', 'another test domain'], // domain - undefined, // First seen doesn't implement getValues - undefined, // Last seen doesn't implement getValues - ['testOs'], // OS name - ['testFamily'], // os family - ['10.0.0.1', '127.0.0.1'], // IP addresses - undefined, // Max anomaly score by job doesn't implement getValues - ] - ); - }); -}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/index.test.tsx index a7238c4a6ff6c..a72c7c3beaf3f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/index.test.tsx @@ -44,7 +44,7 @@ jest.mock('../shared/hooks/use_managed_user', () => ({ useManagedUser: () => mockedUseManagedUser(), })); -jest.mock('./hooks/use_observed_user', () => ({ +jest.mock('../../../flyout_v2/entity/user/main/hooks/use_observed_user', () => ({ useObservedUser: () => mockedUseObservedUser(), })); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx index 9d8478b1f412b..0b0560555e77a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx @@ -10,7 +10,7 @@ import type { FlyoutPanelProps } from '@kbn/expandable-flyout'; import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations'; import { TableId } from '@kbn/securitysolution-data-table'; import { FF_ENABLE_ENTITY_STORE_V2, useEntityStoreEuidApi } from '@kbn/entity-store/public'; -import { EuiSpacer } from '@elastic/eui'; +import { EuiFlyoutFooter, EuiSpacer } from '@elastic/eui'; import { useAssetCriticalityPrivileges } from '../../../entity_analytics/components/asset_criticality/use_asset_criticality'; import { useUpdateAssetCriticality } from '../../../entity_analytics/api/hooks/use_update_asset_criticality'; import { buildEuidCspPreviewOptions } from '../../../cloud_security_posture/utils/build_euid_csp_preview_options'; @@ -27,15 +27,16 @@ import { useManagedUser } from '../shared/hooks/use_managed_user'; import { useQueryInspector } from '../../../common/components/page/manage_query'; import { useGlobalTime } from '../../../common/containers/use_global_time'; import { FlyoutNavigation } from '../../shared/components/flyout_navigation'; -import { UserPanelFooter } from './footer'; -import { UserPanelContent } from './content'; -import { UserPanelHeader } from './header'; +import { FlyoutHeader } from '../../shared/components/flyout_header'; +import { Footer } from '../../../flyout_v2/entity/user/main/footer'; +import { Content } from '../../../flyout_v2/entity/user/main/content'; +import { Header } from '../../../flyout_v2/entity/user/main/header'; import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header'; import { UserPreviewPanelFooter } from '../user_preview/footer'; import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../../overview/components/detection_response/alerts_by_status/types'; import { useNavigateToUserDetails } from './hooks/use_navigate_to_user_details'; import { EntityType } from '../../../../common/entity_analytics/types'; -import { useObservedUser } from './hooks/use_observed_user'; +import { useObservedUser } from '../../../flyout_v2/entity/user/main/hooks/use_observed_user'; import { useEntityFromStore, type EntityStoreRecord } from '../shared/hooks/use_entity_from_store'; import type { CriticalityLevelWithUnassigned } from '../../../../common/entity_analytics/asset_criticality/types'; import { @@ -47,7 +48,10 @@ import { mergeLegacyIdentityWhenStoreEntityMissing, type IdentityFields, } from '../../document_details/shared/utils'; -import { USER_PANEL_RISK_SCORE_QUERY_ID, USER_PANEL_OBSERVED_USER_QUERY_ID } from './constants'; +import { + USER_PANEL_RISK_SCORE_QUERY_ID, + USER_PANEL_OBSERVED_USER_QUERY_ID, +} from '../../../flyout_v2/entity/user/main/constants'; import { FlyoutBody } from '../../shared/components/flyout_body'; import { useEntityPanelTabs, TABLE_TAB_ID } from '../shared/hooks/use_entity_panel_tabs'; import { EntityPanelHeaderTabs } from '../shared/components/entity_panel_tabs'; @@ -296,20 +300,22 @@ export const UserPanel = memo(function UserPanel({ isPreviewMode={isPreviewMode} isRulePreview={scopeId === TableId.rulePreview} /> - + +
+ {entityFromStoreResult.entityRecord && ( ) : ( - {!isPreviewMode && assetInventoryEnabled && ( - + +