From a9ac946114f05cbc96b99f19fe1c98eff5686233 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 16 Feb 2025 18:13:17 +0200 Subject: [PATCH 01/31] init --- .../src/entity/index.ts | 2 +- .../hooks/use_dynamic_entity_flyout.test.ts | 12 ++-- .../hooks/use_dynamic_entity_flyout.ts | 60 ++++--------------- 3 files changed, 22 insertions(+), 52 deletions(-) diff --git a/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts b/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts index f9ba173a38c58..122f6da88ea56 100644 --- a/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts +++ b/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts @@ -11,6 +11,6 @@ export interface EntityEcs { id: string; name: string; - type: 'universal' | 'user' | 'host' | 'service'; + type: 's3' | 'user' | 'host' | 'service'; timestamp: Date; } diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts index 5dabdbac38e8d..274a78653a2c3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts @@ -30,10 +30,10 @@ jest.mock('../../flyout/shared/hooks/use_on_expandable_flyout_close', () => ({ useOnExpandableFlyoutClose: jest.fn(), })); -const entity = { +const entity: EntityEcs = { id: '123', name: 'test-entity', - type: 'universal', + type: 's3', timestamp: new Date(), }; @@ -66,16 +66,17 @@ describe('useDynamicEntityFlyout', () => { act(() => { result.current.openDynamicFlyout({ - entity: { ...entity, type: 'universal', name: 'testUniversal' }, + entity: { ...entity, type: 's3', name: 'testUniversal' }, scopeId: 'scope1', contextId: 'context1', }); }); + expect(openFlyoutMock).toHaveBeenCalledTimes(1) expect(openFlyoutMock).toHaveBeenCalledWith({ right: { id: UniversalEntityPanelKey, - params: { entity: { ...entity, type: 'universal', name: 'testUniversal' } }, + params: { entity: { ...entity, type: 's3', name: 'testUniversal' } }, }, }); }); @@ -93,6 +94,7 @@ describe('useDynamicEntityFlyout', () => { }); }); + expect(openFlyoutMock).toHaveBeenCalledTimes(1) expect(openFlyoutMock).toHaveBeenCalledWith({ right: { id: UserPanelKey, @@ -114,6 +116,7 @@ describe('useDynamicEntityFlyout', () => { }); }); + expect(openFlyoutMock).toHaveBeenCalledTimes(1) expect(openFlyoutMock).toHaveBeenCalledWith({ right: { id: HostPanelKey, @@ -135,6 +138,7 @@ describe('useDynamicEntityFlyout', () => { }); }); + expect(openFlyoutMock).toHaveBeenCalledTimes(1) expect(openFlyoutMock).toHaveBeenCalledWith({ right: { id: ServicePanelKey, diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts index c0f14b9627ca2..6ce2457f1a8ae 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts @@ -29,57 +29,22 @@ interface InventoryFlyoutProps { contextId?: string; } -interface SecurityFlyoutPanelsCommonParams { - scopeId?: string; - contextId?: string; - [key: string]: unknown; -} - -type FlyoutParams = - | { - id: typeof UniversalEntityPanelKey; - params: { entity: EntityEcs }; - } - | { id: typeof UserPanelKey; params: { userName: string } & SecurityFlyoutPanelsCommonParams } - | { id: typeof HostPanelKey; params: { hostName: string } & SecurityFlyoutPanelsCommonParams } - | { - id: typeof ServicePanelKey; - params: { serviceName: string } & SecurityFlyoutPanelsCommonParams; - }; - -const getFlyoutParamsByEntity = ({ - entity, - scopeId, - contextId, -}: InventoryFlyoutProps): FlyoutParams => { - const entitiesFlyoutParams: Record = { - universal: { id: UniversalEntityPanelKey, params: { entity } }, - user: { id: UserPanelKey, params: { userName: entity.name, scopeId, contextId } }, - host: { id: HostPanelKey, params: { hostName: entity.name, scopeId, contextId } }, - service: { id: ServicePanelKey, params: { serviceName: entity.name, scopeId, contextId } }, - } as const; - - return entitiesFlyoutParams[entity.type]; -}; - export const useDynamicEntityFlyout = ({ onFlyoutClose }: { onFlyoutClose: () => void }) => { const { openFlyout, closeFlyout } = useExpandableFlyoutApi(); const { notifications } = useKibana().services; useOnExpandableFlyoutClose({ callback: onFlyoutClose }); const openDynamicFlyout = ({ entity, scopeId, contextId }: InventoryFlyoutProps) => { - const entityFlyoutParams = getFlyoutParamsByEntity({ entity, scopeId, contextId }); - // User, Host, and Service entity flyouts rely on entity name to fetch required data - if (entity.type !== 'universal' && !entity.name) { + if (['user', 'host', 'service'].includes(entity.type) && !entity.name) { notifications.toasts.addDanger({ title: i18n.translate( - 'xpack.securitySolution.assetInventory.openFlyout.missingEntityNameTitle', - { defaultMessage: 'Missing Entity Name' } + 'xpack.securitySolution.assetInventory.openFlyout.missingEntityNameTitle', + { defaultMessage: 'Missing Entity Name' } ), text: i18n.translate( - 'xpack.securitySolution.assetInventory.openFlyout.missingEntityNameText', - { defaultMessage: 'Entity name is required for User, Host, and Service entities' } + 'xpack.securitySolution.assetInventory.openFlyout.missingEntityNameText', + { defaultMessage: 'Entity name is required for User, Host, and Service entities' } ), }); @@ -88,14 +53,15 @@ export const useDynamicEntityFlyout = ({ onFlyoutClose }: { onFlyoutClose: () => return; } - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, ASSET_INVENTORY_EXPAND_FLYOUT_SUCCESS); + switch (entity.type) { + case "user": openFlyout({ right: { id: UserPanelKey, params: { userName: entity.name, scopeId, contextId } } }); break; + case "host": openFlyout({ right: { id: HostPanelKey, params: { hostName: entity.name, scopeId, contextId } } }); break; + case "service": openFlyout({ right: { id: ServicePanelKey, params: { serviceName: entity.name, scopeId, contextId } } }); break; - openFlyout({ - right: { - id: entityFlyoutParams.id || UniversalEntityPanelKey, - params: entityFlyoutParams.params, - }, - }); + default: openFlyout({ right: { id: UniversalEntityPanelKey, params: { entity } } }); break; + } + + uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, ASSET_INVENTORY_EXPAND_FLYOUT_SUCCESS); }; const closeDynamicFlyout = () => { From a5525a54e327d24d424b37afa1920e7c6192b36b Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 16 Feb 2025 18:15:55 +0200 Subject: [PATCH 02/31] lint --- .../hooks/use_dynamic_entity_flyout.ts | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts index 6ce2457f1a8ae..e5b3934368d9f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts @@ -39,12 +39,12 @@ export const useDynamicEntityFlyout = ({ onFlyoutClose }: { onFlyoutClose: () => if (['user', 'host', 'service'].includes(entity.type) && !entity.name) { notifications.toasts.addDanger({ title: i18n.translate( - 'xpack.securitySolution.assetInventory.openFlyout.missingEntityNameTitle', - { defaultMessage: 'Missing Entity Name' } + 'xpack.securitySolution.assetInventory.openFlyout.missingEntityNameTitle', + { defaultMessage: 'Missing Entity Name' } ), text: i18n.translate( - 'xpack.securitySolution.assetInventory.openFlyout.missingEntityNameText', - { defaultMessage: 'Entity name is required for User, Host, and Service entities' } + 'xpack.securitySolution.assetInventory.openFlyout.missingEntityNameText', + { defaultMessage: 'Entity name is required for User, Host, and Service entities' } ), }); @@ -54,11 +54,25 @@ export const useDynamicEntityFlyout = ({ onFlyoutClose }: { onFlyoutClose: () => } switch (entity.type) { - case "user": openFlyout({ right: { id: UserPanelKey, params: { userName: entity.name, scopeId, contextId } } }); break; - case "host": openFlyout({ right: { id: HostPanelKey, params: { hostName: entity.name, scopeId, contextId } } }); break; - case "service": openFlyout({ right: { id: ServicePanelKey, params: { serviceName: entity.name, scopeId, contextId } } }); break; + case 'user': + openFlyout({ + right: { id: UserPanelKey, params: { userName: entity.name, scopeId, contextId } }, + }); + break; + case 'host': + openFlyout({ + right: { id: HostPanelKey, params: { hostName: entity.name, scopeId, contextId } }, + }); + break; + case 'service': + openFlyout({ + right: { id: ServicePanelKey, params: { serviceName: entity.name, scopeId, contextId } }, + }); + break; - default: openFlyout({ right: { id: UniversalEntityPanelKey, params: { entity } } }); break; + default: + openFlyout({ right: { id: UniversalEntityPanelKey, params: { entity } } }); + break; } uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, ASSET_INVENTORY_EXPAND_FLYOUT_SUCCESS); From 6932baa257c274a8d1116ad0de5c8eaf0b53be84 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 16 Feb 2025 18:18:24 +0200 Subject: [PATCH 03/31] lint --- .../hooks/use_dynamic_entity_flyout.test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts index 274a78653a2c3..88a7f14955cfa 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts @@ -72,7 +72,7 @@ describe('useDynamicEntityFlyout', () => { }); }); - expect(openFlyoutMock).toHaveBeenCalledTimes(1) + expect(openFlyoutMock).toHaveBeenCalledTimes(1); expect(openFlyoutMock).toHaveBeenCalledWith({ right: { id: UniversalEntityPanelKey, @@ -94,7 +94,7 @@ describe('useDynamicEntityFlyout', () => { }); }); - expect(openFlyoutMock).toHaveBeenCalledTimes(1) + expect(openFlyoutMock).toHaveBeenCalledTimes(1); expect(openFlyoutMock).toHaveBeenCalledWith({ right: { id: UserPanelKey, @@ -116,7 +116,7 @@ describe('useDynamicEntityFlyout', () => { }); }); - expect(openFlyoutMock).toHaveBeenCalledTimes(1) + expect(openFlyoutMock).toHaveBeenCalledTimes(1); expect(openFlyoutMock).toHaveBeenCalledWith({ right: { id: HostPanelKey, @@ -138,7 +138,7 @@ describe('useDynamicEntityFlyout', () => { }); }); - expect(openFlyoutMock).toHaveBeenCalledTimes(1) + expect(openFlyoutMock).toHaveBeenCalledTimes(1); expect(openFlyoutMock).toHaveBeenCalledWith({ right: { id: ServicePanelKey, @@ -187,6 +187,8 @@ describe('useDynamicEntityFlyout', () => { }) ); expect(onFlyoutCloseMock).toHaveBeenCalled(); + + expect(openFlyoutMock).toHaveBeenCalledTimes(0); }); it('should close the flyout when closeDynamicFlyout is called', () => { @@ -199,5 +201,6 @@ describe('useDynamicEntityFlyout', () => { }); expect(closeFlyoutMock).toHaveBeenCalled(); + expect(openFlyoutMock).toHaveBeenCalledTimes(0); }); }); From affd9635262c4c670a8ebc7e2076c143a71a5557 Mon Sep 17 00:00:00 2001 From: Jordan Date: Tue, 25 Feb 2025 18:14:54 +0200 Subject: [PATCH 04/31] working on badge counter --- .../src/entity/index.ts | 2 +- .../components/fields_selector_table.tsx | 4 +- .../asset_inventory/pages/all_assets.tsx | 95 ++++++++++++++- .../components/responsive_data_cards.tsx | 65 ++++++++++ .../universal_right/content.tsx | 32 ++++- .../entity_details/universal_right/header.tsx | 82 ++++++++++--- .../universal_right/header_data_cards.tsx | 114 ++++++++++++++++++ .../entity_details/universal_right/index.tsx | 2 + 8 files changed, 376 insertions(+), 20 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx diff --git a/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts b/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts index 122f6da88ea56..e3fd8d63ccaf1 100644 --- a/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts +++ b/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts @@ -11,6 +11,6 @@ export interface EntityEcs { id: string; name: string; - type: 's3' | 'user' | 'host' | 'service'; + type: 'user' | 'host' | 'service'; timestamp: Date; } diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/fields_selector_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/fields_selector_table.tsx index 65bcb08399a48..77b0bdd3bd412 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/fields_selector_table.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/fields_selector_table.tsx @@ -115,7 +115,7 @@ export const FieldsSelectorTable = ({ }; const fields = useMemo( () => - filterFieldsBySearch(dataView.fields.getAll(), columns, searchQuery, isFilterSelectedEnabled), + filterFieldsBySearch(dataView.fields?.getAll(), columns, searchQuery, isFilterSelectedEnabled), [dataView, columns, searchQuery, isFilterSelectedEnabled] ); @@ -171,7 +171,7 @@ export const FieldsSelectorTable = ({ ]; const error = useMemo(() => { - if (!dataView || dataView.fields.length === 0) { + if (!dataView || dataView.fields?.length === 0) { return i18n.translate('xpack.securitySolution.assetInventory.allAssets.fieldsModalError', { defaultMessage: 'No fields found in the data view', }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx index c8e9d09f3c8bd..2f5cbf4bc53cc 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useEffect } from 'react'; import _ from 'lodash'; import { type Filter } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; @@ -144,11 +144,96 @@ export interface AllAssetsProps { // TODO: Asset Inventory - adjust and remove type casting once we have real universal entity data const getEntity = (row: DataTableRecord): EntityEcs => { + return { + '@timestamp': '2025-02-17T06:47:20.921Z', + agent: { + ephemeral_id: '4b1b37fc-98d6-43f5-942d-d7b6ad81dc22', + id: '41b2dce5-f057-4bfb-b57b-f1bc64c1c5eb', + name: 'ip-172-31-47-199', + type: 'cloudbeat', + version: '8.17.1', + }, + flattened: { + 'asset.risk': 85, + 'asset.name': 'DNS-controller-azure-sec', + 'asset.criticality': 'unassigned_impact', + 'asset.source': 'cloud-sec-dev', + '@timestamp': '2025-01-01T00:00:00.000Z', + }, + asset: { + sub_type: 'sns-topic', + sub_category: 'messaging', + name: 'test-aws-topic.fifo', + raw: { + subscriptions: [ + { + Owner: '704479110758', + Endpoint: 'arn:aws:sqs:eu-west-1:704479110758:test-aws.fifo', + TopicArn: 'arn:aws:sns:eu-west-1:704479110758:test-aws-topic.fifo', + Protocol: 'sqs', + SubscriptionArn: + 'arn:aws:sns:eu-west-1:704479110758:test-aws-topic.fifo:342b284d-f16c-447c-afb8-8080d10c604d', + }, + ], + topic: { + TopicArn: 'arn:aws:sns:eu-west-1:704479110758:test-aws-topic.fifo', + }, + }, + id: ['arn:aws:sns:eu-west-1:704479110758:test-aws-topic.fifo'], + type: 'notification-service', + category: 'infrastructure', + tags: null, + }, + cloud: { + account: { + id: '704479110758', + name: 'elastic-security-cloud-security-dev', + }, + organization: {}, + provider: 'aws', + region: 'eu-west-1', + service: { + name: 'AWS SNS', + }, + }, + data_stream: { + dataset: 'cloud_asset_inventory.asset_inventory', + namespace: 'default', + type: 'logs', + }, + ecs: { + version: '8.0.0', + }, + elastic_agent: { + id: '41b2dce5-f057-4bfb-b57b-f1bc64c1c5eb', + version: '8.17.1', + snapshot: false, + }, + entity: { + category: 'infrastructure', + name: 'test-aws-topic.fifo', + sub_category: 'messaging', + sub_type: 'sns-topic', + type: 'notification-service', + tags: ['infrastructure', 'linux', 'admin', 'active'], + labels: { Group: 'cloud-sec-dev', Environment: 'Production' }, + }, + event: { + agent_id_status: 'auth_metadata_missing', + dataset: 'cloud_asset_inventory.asset_inventory', + ingested: '2025-02-17T10:54:51Z', + }, + related: { + entity: 'arn:aws:sns:eu-west-1:704479110758:test-aws-topic.fifo', + }, + resource_policies: [], + }; + return { id: (row.flattened['asset.name'] as string) || '', name: (row.flattened['asset.name'] as string) || '', timestamp: row.flattened['@timestamp'] as Date, - type: 'universal', + type: 's3', }; }; @@ -180,6 +265,12 @@ const AllAssets = ({ onFlyoutClose: () => setExpandedDoc(undefined), }); + useEffect(() => { + const entity = getEntity(); + console.log(entity); + openDynamicFlyout({ entity: entity.entity }); + }, [openDynamicFlyout]); + const onExpandDocClick = (doc?: DataTableRecord | undefined) => { if (doc) { const entity = getEntity(doc); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.tsx new file mode 100644 index 0000000000000..554bb29492417 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.tsx @@ -0,0 +1,65 @@ +/* + * 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 from 'react'; +import { css } from '@emotion/react'; +import { EuiCard, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; +import type { EuiCardProps } from '@elastic/eui/src/components/card/card'; + +interface HeaderDataCardsProps { + /** + * An array of EuiCardProps objects, defining the cards to be displayed. + */ + cards: Array>; + /** + * The width (in pixels) at which the cards should collapse from a row layout to two columns layout. + * Defaults to 750. + */ + collapseWidth?: number; +} + +/** + * A component that displays a group of data cards in a responsive layout. + * Depending on the width of the container, the cards will be displayed in a row layout or a two columns layout. + */ +export const ResponsiveDataCards = ({ cards, collapseWidth = 750 }: HeaderDataCardsProps) => { + const { euiTheme } = useEuiTheme(); + + return ( + + {cards.map((card) => ( + + + + ))} + + ); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/content.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/content.tsx index 5226ed0379a2e..e296cce64fbdc 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/content.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/content.tsx @@ -7,6 +7,8 @@ import React from 'react'; import type { EntityEcs } from '@kbn/securitysolution-ecs/src/entity'; +import { EuiBasicTable } from '@elastic/eui'; +import { ExpandableSection } from '../../document_details/right/components/expandable_section'; import { FlyoutBody } from '../../shared/components/flyout_body'; interface UniversalEntityFlyoutContentProps { @@ -14,5 +16,33 @@ interface UniversalEntityFlyoutContentProps { } export const UniversalEntityFlyoutContent = ({ entity }: UniversalEntityFlyoutContentProps) => { - return {entity.type}; + const columns = [ + { + field: 'field', + name: {'Field'}, + width: '150px', + }, + { + field: 'value', + name: {'Value'}, + }, + ]; + + // Extract values from the entity object + const items = Object.entries(entity).map(([field, value]) => ({ + field, + value: value?.value || value, // Use optional chaining to handle nested objects + })); + + return ( + + + + + + ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index 5aee5f15930d1..62038d6765b3b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -5,10 +5,20 @@ * 2.0. */ -import { EuiSpacer, EuiText, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { + EuiSpacer, + EuiText, + EuiFlexItem, + EuiFlexGroup, + EuiBadgeGroup, + EuiBadge, + useEuiTheme, +} from '@elastic/eui'; import React from 'react'; import type { EntityEcs } from '@kbn/securitysolution-ecs/src/entity'; +import { css } from '@emotion/react'; +import { HeaderDataCards } from './header_data_cards'; import { EntityIconByType } from '../../../entity_analytics/components/entity_store/helpers'; import { PreferenceFormattedDate } from '../../../common/components/formatted_date'; import { FlyoutHeader } from '../../shared/components/flyout_header'; @@ -20,18 +30,62 @@ interface UniversalEntityFlyoutHeaderProps { export const UniversalEntityFlyoutHeader = ({ entity }: UniversalEntityFlyoutHeaderProps) => { return ( - - - - - - - - - - - - - + <> + + + + + + + + + + + + + +
+ +
+
+ +
+ + ); +}; + +const HeaderTags = ({ entity }) => { + const { euiTheme } = useEuiTheme(); + + return ( + + {entity.tags?.map((tag) => ( + {tag} + ))} + {entity.labels && + Object.entries(entity.labels)?.map(([key, value]) => ( + + + {key} + + + {value} + + + ))} + ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx new file mode 100644 index 0000000000000..90b1a3eabc2a9 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx @@ -0,0 +1,114 @@ +/* + * 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. + */ + +/* + * 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 the Elastic License + * 2.0. + */ + +import React, { useState, useMemo } from 'react'; +import { + EuiButtonIcon, + EuiCopy, + EuiFlexGroup, + EuiFlexItem, + EuiSuperSelect, + EuiTextTruncate, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { AssetCriticalityBadge } from '../../../entity_analytics/components/asset_criticality'; +import { ResponsiveDataCards } from './components/responsive_data_cards'; + +const getCopyCard = ({ title, description }) => ({ + title: ( + + {title} + + + {(copy) => } + + + + ), + description: , +}); + +const options = [ + { + value: 'unassigned', + inputDisplay: ( + + ), + }, + { + value: 'low_impact', + inputDisplay: ( + + ), + }, + { + value: 'medium_impact', + inputDisplay: ( + + ), + }, + { + value: 'high_impact', + inputDisplay: ( + + ), + }, + { + value: 'extreme_impact', + inputDisplay: ( + + ), + }, +]; + +export const HeaderDataCards = ({ entity }) => { + const [selectValue, setSelectValue] = useState('low_impact'); + + const cards = useMemo( + () => [ + { + title: 'Criticality', + description: ( +
+ setSelectValue(newValue)} + /> +
+ ), + }, + getCopyCard({ title: 'ID', description: '123123123123123123123' }), + { title: 'Category', description: }, + { title: 'Type', description: }, + ], + [selectValue, entity?.category, entity?.type] + ); + + return ; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx index ed9f1a7074eec..2e01420782a7e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx @@ -13,6 +13,7 @@ import { } from '@kbn/cloud-security-posture-common/utils/ui_metrics'; import { METRIC_TYPE } from '@kbn/analytics'; import type { EntityEcs } from '@kbn/securitysolution-ecs/src/entity'; +import { EuiHorizontalRule } from '@elastic/eui'; import { UniversalEntityFlyoutHeader } from './header'; import { UniversalEntityFlyoutContent } from './content'; import { FlyoutNavigation } from '../../shared/components/flyout_navigation'; @@ -37,6 +38,7 @@ export const UniversalEntityPanel = ({ entity }: UniversalEntityPanelProps) => { <> + ); From c2062013d73ccc22bc8e7f5f1b60cfc48091f8b4 Mon Sep 17 00:00:00 2001 From: Jordan Date: Tue, 25 Feb 2025 20:02:15 +0200 Subject: [PATCH 05/31] added expandable badge group --- .../asset_inventory/pages/all_assets.tsx | 1 + .../entity_details/universal_right/header.tsx | 62 +++++++++++-------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx index 2f5cbf4bc53cc..519307dddede2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx @@ -215,6 +215,7 @@ const getEntity = (row: DataTableRecord): EntityEcs => { sub_category: 'messaging', sub_type: 'sns-topic', type: 'notification-service', + tags: ['infrastructure', 'linux'], tags: ['infrastructure', 'linux', 'admin', 'active'], labels: { Group: 'cloud-sec-dev', Environment: 'Production' }, }, diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index 62038d6765b3b..6c0cfcf0fd73c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -14,7 +14,7 @@ import { EuiBadge, useEuiTheme, } from '@elastic/eui'; -import React from 'react'; +import React, { useState } from 'react'; import type { EntityEcs } from '@kbn/securitysolution-ecs/src/entity'; import { css } from '@emotion/react'; @@ -59,33 +59,45 @@ export const UniversalEntityFlyoutHeader = ({ entity }: UniversalEntityFlyoutHea const HeaderTags = ({ entity }) => { const { euiTheme } = useEuiTheme(); + const [badgesToShow, setBadgesToShow] = useState(3); + + const tagBadges = entity.tags?.map((tag) => {tag}); + + const labelBadges = + entity.labels && + Object.entries(entity.labels)?.map(([key, value]) => ( + + + {key} + + + {value} + + + )); + + const allBadges = [...(tagBadges || []), ...(labelBadges || [])]; + const remainingCount = allBadges.length - badgesToShow; return ( - {entity.tags?.map((tag) => ( - {tag} - ))} - {entity.labels && - Object.entries(entity.labels)?.map(([key, value]) => ( - - - {key} - - - {value} - - - ))} + {badgesToShow === 'all' ? allBadges : allBadges.slice(0, badgesToShow)} + {remainingCount > 0 && badgesToShow !== 'all' && ( + setBadgesToShow('all')} + >{`+${remainingCount}`} + )} ); }; From 903be02ff9dbd38f665a0f6eff3dca38f690b08e Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 26 Feb 2025 16:39:44 +0200 Subject: [PATCH 06/31] adding scrolling --- .../public/flyout/entity_details/universal_right/header.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index 6c0cfcf0fd73c..aae6a49765180 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -59,7 +59,7 @@ export const UniversalEntityFlyoutHeader = ({ entity }: UniversalEntityFlyoutHea const HeaderTags = ({ entity }) => { const { euiTheme } = useEuiTheme(); - const [badgesToShow, setBadgesToShow] = useState(3); + const [badgesToShow, setBadgesToShow] = useState(4); const tagBadges = entity.tags?.map((tag) => {tag}); @@ -90,6 +90,7 @@ const HeaderTags = ({ entity }) => { const remainingCount = allBadges.length - badgesToShow; return ( + // TODO: add max height inner scrolling {badgesToShow === 'all' ? allBadges : allBadges.slice(0, badgesToShow)} {remainingCount > 0 && badgesToShow !== 'all' && ( From 451ae64fd12276af2cf6a8c6e00e1b33b54dd1ae Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 26 Feb 2025 19:01:20 +0200 Subject: [PATCH 07/31] added inner scrolling and created a component for expandable badge group --- .../asset_inventory/pages/all_assets.tsx | 170 +++++++++++++++++- .../components/expandable_badge_group.tsx | 56 ++++++ .../entity_details/universal_right/header.tsx | 98 +++++----- 3 files changed, 266 insertions(+), 58 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx index 519307dddede2..75bf4e112fec3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx @@ -215,8 +215,174 @@ const getEntity = (row: DataTableRecord): EntityEcs => { sub_category: 'messaging', sub_type: 'sns-topic', type: 'notification-service', - tags: ['infrastructure', 'linux'], - tags: ['infrastructure', 'linux', 'admin', 'active'], + tags: [ + 'infrastructure', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + 'linux', + 'admin', + 'active', + ], labels: { Group: 'cloud-sec-dev', Environment: 'Production' }, }, event: { diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx new file mode 100644 index 0000000000000..843f75eb43770 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx @@ -0,0 +1,56 @@ +/* + * 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, { useState } from 'react'; +import { css } from '@emotion/react'; +import type { EuiBadgeProps } from '@elastic/eui'; +import { EuiBadge, EuiBadgeGroup } from '@elastic/eui'; + +interface ExpandableBadgeGroupProps { + /** Array of EuiBadges properties to display */ + badges: EuiBadgeProps[]; + /** The initial number of badges to show before expanding. Defaults to 'all' if not set */ + initialBadgeLimit?: number; + /** The maximum height of the badge group in pixels. If not set the expandable container will not have inner scrolling */ + maxHeight?: number; +} + +/** + * A component that displays a group of badges with a limited initial display and an expansion option. + * + * The component initially shows a limited number of badges (or all if `initialBadgeLimit` is not set) and provides a "+n" badge to expand and show all badges. + * The badge group is scrollable if the badges exceed the `maxHeight`. + */ +export const ExpandableBadgeGroup = ({ + badges, + initialBadgeLimit, + maxHeight, +}: ExpandableBadgeGroupProps) => { + const [badgesToShow, setBadgesToShow] = useState(initialBadgeLimit || 'all'); + + const remainingCount = badges.length - badgesToShow; + const maxScrollHeight = maxHeight ? `${maxHeight}px` : 'initial'; + + return ( + + {badgesToShow === 'all' ? badges : badges.slice(0, badgesToShow)} + {remainingCount > 0 && badgesToShow !== 'all' && ( + setBadgesToShow('all')} + >{`+${remainingCount}`} + )} + + ); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index aae6a49765180..fccbbd07b09d2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -5,19 +5,12 @@ * 2.0. */ -import { - EuiSpacer, - EuiText, - EuiFlexItem, - EuiFlexGroup, - EuiBadgeGroup, - EuiBadge, - useEuiTheme, -} from '@elastic/eui'; -import React, { useState } from 'react'; +import { EuiSpacer, EuiText, EuiFlexItem, EuiFlexGroup, EuiBadge, useEuiTheme } from '@elastic/eui'; +import React, { useMemo } from 'react'; import type { EntityEcs } from '@kbn/securitysolution-ecs/src/entity'; import { css } from '@emotion/react'; +import { ExpandableBadgeGroup } from './components/expandable_badge_group'; import { HeaderDataCards } from './header_data_cards'; import { EntityIconByType } from '../../../entity_analytics/components/entity_store/helpers'; import { PreferenceFormattedDate } from '../../../common/components/formatted_date'; @@ -28,6 +21,45 @@ interface UniversalEntityFlyoutHeaderProps { entity: EntityEcs; } +const HeaderTags = ({ entity }) => { + const { euiTheme } = useEuiTheme(); + + const tagBadges = useMemo( + () => entity.tags?.map((tag) => {tag}), + [entity.tags] + ); + + const labelBadges = useMemo( + () => + entity.labels && + Object.entries(entity.labels)?.map(([key, value]) => ( + + + {key} + + + {value} + + + )), + [entity.labels, euiTheme.border.thick, euiTheme.colors.disabledText] + ); + + const allBadges = [...(tagBadges || []), ...(labelBadges || [])]; + + return ; +}; + export const UniversalEntityFlyoutHeader = ({ entity }: UniversalEntityFlyoutHeaderProps) => { return ( <> @@ -56,49 +88,3 @@ export const UniversalEntityFlyoutHeader = ({ entity }: UniversalEntityFlyoutHea ); }; - -const HeaderTags = ({ entity }) => { - const { euiTheme } = useEuiTheme(); - const [badgesToShow, setBadgesToShow] = useState(4); - - const tagBadges = entity.tags?.map((tag) => {tag}); - - const labelBadges = - entity.labels && - Object.entries(entity.labels)?.map(([key, value]) => ( - - - {key} - - - {value} - - - )); - - const allBadges = [...(tagBadges || []), ...(labelBadges || [])]; - const remainingCount = allBadges.length - badgesToShow; - - return ( - // TODO: add max height inner scrolling - - {badgesToShow === 'all' ? allBadges : allBadges.slice(0, badgesToShow)} - {remainingCount > 0 && badgesToShow !== 'all' && ( - setBadgesToShow('all')} - >{`+${remainingCount}`} - )} - - ); -}; From ff1b3a7741ba67d47cb5dec08f0700d644da802b Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 2 Mar 2025 14:01:40 +0200 Subject: [PATCH 08/31] clean --- .../hooks/use_dynamic_entity_flyout.ts | 6 +- .../asset_inventory/pages/all_assets.tsx | 282 +----------------- .../components/fields_table.tsx | 85 ++++++ .../universal_right/content.tsx | 36 +-- .../entity_details/universal_right/header.tsx | 56 ++-- .../universal_right/header_data_cards.tsx | 48 ++- .../entity_details/universal_right/index.tsx | 10 +- .../flyout/shared/components/flyout_title.tsx | 9 +- 8 files changed, 208 insertions(+), 324 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.tsx diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts index e5b3934368d9f..cabf6bc3f0c09 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts @@ -14,6 +14,7 @@ import { } from '@kbn/cloud-security-posture-common/utils/ui_metrics'; import { METRIC_TYPE } from '@kbn/analytics'; import { i18n } from '@kbn/i18n'; +import type { EsHitRecord } from '@kbn/discover-utils'; import { useKibana } from '../../common/lib/kibana'; import { HostPanelKey, @@ -25,6 +26,7 @@ import { useOnExpandableFlyoutClose } from '../../flyout/shared/hooks/use_on_exp interface InventoryFlyoutProps { entity: EntityEcs; + source: EsHitRecord['_source']; scopeId?: string; contextId?: string; } @@ -34,7 +36,7 @@ export const useDynamicEntityFlyout = ({ onFlyoutClose }: { onFlyoutClose: () => const { notifications } = useKibana().services; useOnExpandableFlyoutClose({ callback: onFlyoutClose }); - const openDynamicFlyout = ({ entity, scopeId, contextId }: InventoryFlyoutProps) => { + const openDynamicFlyout = ({ entity, source, scopeId, contextId }: InventoryFlyoutProps) => { // User, Host, and Service entity flyouts rely on entity name to fetch required data if (['user', 'host', 'service'].includes(entity.type) && !entity.name) { notifications.toasts.addDanger({ @@ -71,7 +73,7 @@ export const useDynamicEntityFlyout = ({ onFlyoutClose }: { onFlyoutClose: () => break; default: - openFlyout({ right: { id: UniversalEntityPanelKey, params: { entity } } }); + openFlyout({ right: { id: UniversalEntityPanelKey, params: { entity, source } } }); break; } diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx index 6f69f1c762b7e..02fcb837d5c2d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, useMemo, useEffect } from 'react'; +import React, { useState, useMemo } from 'react'; import _ from 'lodash'; import { type Filter } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; @@ -141,264 +141,19 @@ export interface AllAssetsProps { } // TODO: Asset Inventory - adjust and remove type casting once we have real universal entity data -const getEntity = (row: DataTableRecord): EntityEcs => { - return { - '@timestamp': '2025-02-17T06:47:20.921Z', - agent: { - ephemeral_id: '4b1b37fc-98d6-43f5-942d-d7b6ad81dc22', - id: '41b2dce5-f057-4bfb-b57b-f1bc64c1c5eb', - name: 'ip-172-31-47-199', - type: 'cloudbeat', - version: '8.17.1', - }, - flattened: { - 'asset.risk': 85, - 'asset.name': 'DNS-controller-azure-sec', - 'asset.criticality': 'unassigned_impact', - 'asset.source': 'cloud-sec-dev', - '@timestamp': '2025-01-01T00:00:00.000Z', - }, - asset: { - sub_type: 'sns-topic', - sub_category: 'messaging', - name: 'test-aws-topic.fifo', - raw: { - subscriptions: [ - { - Owner: '704479110758', - Endpoint: 'arn:aws:sqs:eu-west-1:704479110758:test-aws.fifo', - TopicArn: 'arn:aws:sns:eu-west-1:704479110758:test-aws-topic.fifo', - Protocol: 'sqs', - SubscriptionArn: - 'arn:aws:sns:eu-west-1:704479110758:test-aws-topic.fifo:342b284d-f16c-447c-afb8-8080d10c604d', - }, - ], - topic: { - TopicArn: 'arn:aws:sns:eu-west-1:704479110758:test-aws-topic.fifo', - }, - }, - id: ['arn:aws:sns:eu-west-1:704479110758:test-aws-topic.fifo'], - type: 'notification-service', - category: 'infrastructure', - tags: null, - }, - cloud: { - account: { - id: '704479110758', - name: 'elastic-security-cloud-security-dev', - }, - organization: {}, - provider: 'aws', - region: 'eu-west-1', - service: { - name: 'AWS SNS', - }, - }, - data_stream: { - dataset: 'cloud_asset_inventory.asset_inventory', - namespace: 'default', - type: 'logs', - }, - ecs: { - version: '8.0.0', - }, - elastic_agent: { - id: '41b2dce5-f057-4bfb-b57b-f1bc64c1c5eb', - version: '8.17.1', - snapshot: false, - }, - entity: { - category: 'infrastructure', - name: 'test-aws-topic.fifo', - sub_category: 'messaging', - sub_type: 'sns-topic', - type: 'notification-service', - tags: [ - 'infrastructure', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - 'linux', - 'admin', - 'active', - ], - labels: { Group: 'cloud-sec-dev', Environment: 'Production' }, - }, - event: { - agent_id_status: 'auth_metadata_missing', - dataset: 'cloud_asset_inventory.asset_inventory', - ingested: '2025-02-17T10:54:51Z', - }, - related: { - entity: 'arn:aws:sns:eu-west-1:704479110758:test-aws-topic.fifo', - }, - resource_policies: [], +const getEntity = (record: DataTableRecord): EntityEcs => { + const { _source } = record.raw; + + const entityMock = { + tags: ['infrastructure', 'linux', 'admin', 'active'], + labels: { Group: 'cloud-sec-dev', Environment: 'Production' }, + id: 'mock-entity-id', + criticality: 'low_impact', }; return { - id: (row.flattened['asset.name'] as string) || '', - name: (row.flattened['asset.name'] as string) || '', - timestamp: row.flattened['@timestamp'] as Date, - type: 's3', + entity: { ..._source.entity, ...entityMock }, + source: _source, }; }; @@ -427,18 +182,13 @@ const AllAssets = ({ onFlyoutClose: () => setExpandedDoc(undefined), }); - useEffect(() => { - const entity = getEntity(); - console.log(entity); - openDynamicFlyout({ entity: entity.entity }); - }, [openDynamicFlyout]); - - const onExpandDocClick = (doc?: DataTableRecord | undefined) => { - if (doc) { - const entity = getEntity(doc); - setExpandedDoc(doc); // Table is expecting the same doc ref to highlight the selected row + const onExpandDocClick = (record?: DataTableRecord | undefined) => { + if (record) { + const { entity, source } = getEntity(record); + setExpandedDoc(record); // Table is expecting the same record ref to highlight the selected row openDynamicFlyout({ entity, + source, scopeId: ASSET_INVENTORY_TABLE_ID, contextId: ASSET_INVENTORY_TABLE_ID, }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.tsx new file mode 100644 index 0000000000000..04d20d395cd97 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.tsx @@ -0,0 +1,85 @@ +/* + * 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 { EuiInMemoryTableProps } from '@elastic/eui'; +import { EuiCode, EuiCodeBlock, EuiInMemoryTable, EuiText } from '@elastic/eui'; +import React from 'react'; +import { getFlattenedObject } from '@kbn/std'; +import { i18n } from '@kbn/i18n'; + +interface FlattenedItem { + key: string; // flattened dot notation object path for an object; + value: unknown; +} + +const getDescriptionDisplay = (value: unknown) => { + if (value === undefined) return 'undefined'; + if (typeof value === 'boolean' || value === null) { + return {JSON.stringify(value)}; + } + + if (typeof value === 'object') { + return ( + + {JSON.stringify(value, null, 2)} + + ); + } + + return {value as string}; +}; + +const search: EuiInMemoryTableProps['search'] = { + box: { + incremental: true, + }, +}; + +const sorting: EuiInMemoryTableProps['sorting'] = { + sort: { + field: 'key', + direction: 'asc', + }, +}; + +const pagination: EuiInMemoryTableProps['pagination'] = { + initialPageSize: 100, + showPerPageOptions: false, +}; + +const columns: EuiInMemoryTableProps['columns'] = [ + { + field: 'key', + name: i18n.translate('xpack.assetInventory.fieldsTable.fieldColumnLabel', { + defaultMessage: 'Field', + }), + width: '25%', + }, + { + field: 'value', + name: i18n.translate('xpack.assetInventory.fieldsTable.valueColumnLabel', { + defaultMessage: 'Value', + }), + render: (value, record) =>
{getDescriptionDisplay(value)}
, + }, +]; + +const getFlattenedItems = (resource: Record) => + Object.entries(getFlattenedObject(resource)).map(([key, value]) => ({ key, value })); + +/** + * A component that displays a table of flattened fields and values from a resource object. + */ +export const FieldsTable = ({ data }: { data: Record }) => ( + +); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/content.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/content.tsx index e296cce64fbdc..0a11a907fd160 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/content.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/content.tsx @@ -6,42 +6,30 @@ */ import React from 'react'; -import type { EntityEcs } from '@kbn/securitysolution-ecs/src/entity'; -import { EuiBasicTable } from '@elastic/eui'; +import type { EsHitRecord } from '@kbn/discover-utils'; +import { i18n } from '@kbn/i18n'; +import { FieldsTable } from './components/fields_table'; import { ExpandableSection } from '../../document_details/right/components/expandable_section'; import { FlyoutBody } from '../../shared/components/flyout_body'; interface UniversalEntityFlyoutContentProps { - entity: EntityEcs; + source: EsHitRecord['_source']; } -export const UniversalEntityFlyoutContent = ({ entity }: UniversalEntityFlyoutContentProps) => { - const columns = [ - { - field: 'field', - name: {'Field'}, - width: '150px', - }, - { - field: 'value', - name: {'Value'}, - }, - ]; - - // Extract values from the entity object - const items = Object.entries(entity).map(([field, value]) => ({ - field, - value: value?.value || value, // Use optional chaining to handle nested objects - })); - +export const UniversalEntityFlyoutContent = ({ source }: UniversalEntityFlyoutContentProps) => { return ( - + ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index fccbbd07b09d2..d766658731982 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -17,22 +17,18 @@ import { PreferenceFormattedDate } from '../../../common/components/formatted_da import { FlyoutHeader } from '../../shared/components/flyout_header'; import { FlyoutTitle } from '../../shared/components/flyout_title'; -interface UniversalEntityFlyoutHeaderProps { - entity: EntityEcs; -} - -const HeaderTags = ({ entity }) => { +const HeaderTags = ({ tags, labels }: { tags: EntityEcs['tags']; labels: EntityEcs['labels'] }) => { const { euiTheme } = useEuiTheme(); const tagBadges = useMemo( - () => entity.tags?.map((tag) => {tag}), - [entity.tags] + () => tags?.map((tag) => {tag}), + [tags] ); const labelBadges = useMemo( () => - entity.labels && - Object.entries(entity.labels)?.map(([key, value]) => ( + labels && + Object.entries(labels)?.map(([key, value]) => ( { )), - [entity.labels, euiTheme.border.thick, euiTheme.colors.disabledText] + [labels, euiTheme.border.thick, euiTheme.colors.disabledText] ); const allBadges = [...(tagBadges || []), ...(labelBadges || [])]; @@ -60,14 +56,24 @@ const HeaderTags = ({ entity }) => { return ; }; -export const UniversalEntityFlyoutHeader = ({ entity }: UniversalEntityFlyoutHeaderProps) => { +interface UniversalEntityFlyoutHeaderProps { + entity: EntityEcs; + timestamp: Date; +} + +export const UniversalEntityFlyoutHeader = ({ + entity, + timestamp, +}: UniversalEntityFlyoutHeaderProps) => { + const { euiTheme } = useEuiTheme(); + return ( <> - + @@ -75,16 +81,32 @@ export const UniversalEntityFlyoutHeader = ({ entity }: UniversalEntityFlyoutHea -
- -
-
- +
+
+ {(entity.tags || entity.labels) && ( +
+ +
+ )} ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx index 90b1a3eabc2a9..39f19eac06d72 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx @@ -22,10 +22,11 @@ import { EuiTextTruncate, } from '@elastic/eui'; import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; import { AssetCriticalityBadge } from '../../../entity_analytics/components/asset_criticality'; import { ResponsiveDataCards } from './components/responsive_data_cards'; -const getCopyCard = ({ title, description }) => ({ +const getCopyCardProps = ({ title, description }) => ({ title: ( {title} @@ -75,13 +76,28 @@ const options = [ }, ]; -export const HeaderDataCards = ({ entity }) => { - const [selectValue, setSelectValue] = useState('low_impact'); +export const HeaderDataCards = ({ + criticality, + id, + category, + type, +}: { + criticality?: string; + id: string; + category: string; + type: string; +}) => { + const [selectValue, setSelectValue] = useState(criticality); const cards = useMemo( () => [ { - title: 'Criticality', + title: i18n.translate( + 'xpack.securitySolution.universalEntityFlyout.flyoutHeader.headerDataBoxes.criticalityLabel', + { + defaultMessage: 'Criticality', + } + ), description: (
{
), }, - getCopyCard({ title: 'ID', description: '123123123123123123123' }), - { title: 'Category', description: }, - { title: 'Type', description: }, + getCopyCardProps({ title: 'ID', description: id }), + { + title: i18n.translate( + 'xpack.securitySolution.universalEntityFlyout.flyoutHeader.headerDataBoxes.categoryLabel', + { + defaultMessage: 'Category', + } + ), + description: , + }, + { + title: i18n.translate( + 'xpack.securitySolution.universalEntityFlyout.flyoutHeader.headerDataBoxes.typeLabel', + { + defaultMessage: 'Type', + } + ), + description: , + }, ], - [selectValue, entity?.category, entity?.type] + [selectValue, id, category, type] ); return ; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx index 2e01420782a7e..af504a53724b4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx @@ -13,13 +13,14 @@ import { } from '@kbn/cloud-security-posture-common/utils/ui_metrics'; import { METRIC_TYPE } from '@kbn/analytics'; import type { EntityEcs } from '@kbn/securitysolution-ecs/src/entity'; -import { EuiHorizontalRule } from '@elastic/eui'; +import type { EsHitRecord } from '@kbn/discover-utils'; import { UniversalEntityFlyoutHeader } from './header'; import { UniversalEntityFlyoutContent } from './content'; import { FlyoutNavigation } from '../../shared/components/flyout_navigation'; export interface UniversalEntityPanelProps { entity: EntityEcs; + source: EsHitRecord['_source']; /** this is because FlyoutPanelProps defined params as Record {@link FlyoutPanelProps#params} */ [key: string]: unknown; } @@ -29,7 +30,7 @@ export interface UniversalEntityPanelExpandableFlyoutProps extends FlyoutPanelPr params: UniversalEntityPanelProps; } -export const UniversalEntityPanel = ({ entity }: UniversalEntityPanelProps) => { +export const UniversalEntityPanel = ({ entity, source }: UniversalEntityPanelProps) => { useEffect(() => { uiMetricService.trackUiMetric(METRIC_TYPE.COUNT, UNIVERSAL_ENTITY_FLYOUT_OPENED); }, [entity]); @@ -37,9 +38,8 @@ export const UniversalEntityPanel = ({ entity }: UniversalEntityPanelProps) => { return ( <> - - - + + ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/shared/components/flyout_title.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/shared/components/flyout_title.tsx index 89a62bc2bf6d9..68335828d794c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/shared/components/flyout_title.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/shared/components/flyout_title.tsx @@ -28,6 +28,10 @@ export interface FlyoutTitleProps { * Optional icon type. If null, no icon is displayed */ iconType?: EuiButtonEmptyProps['iconType']; + /** + * Optional icon color + */ + iconColor?: EuiButtonEmptyProps['color']; /** * Optional boolean to indicate if title is a link. If true, a popout icon is appended * and the title text is changed to link color @@ -43,12 +47,13 @@ export interface FlyoutTitleProps { * Title component with optional icon to indicate the type of document, can be used for text or a link */ export const FlyoutTitle: FC = memo( - ({ title, iconType, isLink = false, 'data-test-subj': dataTestSubj }) => { + ({ title, iconType, iconColor, isLink = false, 'data-test-subj': dataTestSubj }) => { const { euiTheme } = useEuiTheme(); const titleIcon = useMemo(() => { return iconType ? ( = memo( `} /> ) : null; - }, [dataTestSubj, iconType, euiTheme.size.xs]); + }, [iconType, iconColor, dataTestSubj, euiTheme.size.xs]); const titleComponent = useMemo(() => { return ( From b705b991cbb75f5226884dcda9e7de09c011e5f4 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 2 Mar 2025 14:43:16 +0200 Subject: [PATCH 09/31] i18n fixes --- .../universal_right/components/fields_table.tsx | 6 +++--- .../flyout/entity_details/universal_right/content.tsx | 4 +--- .../public/flyout/entity_details/universal_right/header.tsx | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.tsx index 04d20d395cd97..815340fc095c8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.tsx @@ -54,17 +54,17 @@ const pagination: EuiInMemoryTableProps['pagination'] = { const columns: EuiInMemoryTableProps['columns'] = [ { field: 'key', - name: i18n.translate('xpack.assetInventory.fieldsTable.fieldColumnLabel', { + name: i18n.translate('xpack.securitySolution.fieldsTable.fieldColumnLabel', { defaultMessage: 'Field', }), width: '25%', }, { field: 'value', - name: i18n.translate('xpack.assetInventory.fieldsTable.valueColumnLabel', { + name: i18n.translate('xpack.securitySolution.fieldsTable.valueColumnLabel', { defaultMessage: 'Value', }), - render: (value, record) =>
{getDescriptionDisplay(value)}
, + render: (value) =>
{getDescriptionDisplay(value)}
, }, ]; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/content.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/content.tsx index 0a11a907fd160..c516ff8a0d61d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/content.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/content.tsx @@ -22,9 +22,7 @@ export const UniversalEntityFlyoutContent = ({ source }: UniversalEntityFlyoutCo {key} {value} From 7fb95e9ecd1cb1f302a31b4300a12c50806c612c Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Sun, 2 Mar 2025 13:04:51 +0000 Subject: [PATCH 10/31] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../asset_inventory/components/fields_selector_table.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/fields_selector_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/fields_selector_table.tsx index 77b0bdd3bd412..fdb67f9cc5ed6 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/fields_selector_table.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/fields_selector_table.tsx @@ -115,7 +115,12 @@ export const FieldsSelectorTable = ({ }; const fields = useMemo( () => - filterFieldsBySearch(dataView.fields?.getAll(), columns, searchQuery, isFilterSelectedEnabled), + filterFieldsBySearch( + dataView.fields?.getAll(), + columns, + searchQuery, + isFilterSelectedEnabled + ), [dataView, columns, searchQuery, isFilterSelectedEnabled] ); From 7c1444d35ce94de0fdd06ed05995942c8d3ddbc4 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 2 Mar 2025 18:03:30 +0200 Subject: [PATCH 11/31] types --- .../src/entity/index.ts | 5 +++- .../onboarding/asset_inventory_onboarding.tsx | 2 ++ .../hooks/use_dynamic_entity_flyout.test.ts | 19 ++++++++++--- .../asset_inventory/pages/all_assets.tsx | 9 +++--- .../components/expandable_badge_group.tsx | 24 ++++++++++------ .../components/fields_table.tsx | 6 ++-- .../components/responsive_data_cards.tsx | 4 +-- .../universal_right/content.tsx | 14 ++++++---- .../entity_details/universal_right/header.tsx | 2 +- .../universal_right/header_data_cards.tsx | 28 +++++++++---------- 10 files changed, 69 insertions(+), 44 deletions(-) diff --git a/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts b/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts index e3fd8d63ccaf1..9691f60933c81 100644 --- a/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts +++ b/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts @@ -12,5 +12,8 @@ export interface EntityEcs { id: string; name: string; type: 'user' | 'host' | 'service'; - timestamp: Date; + tags: string[]; + labels: Record; + criticality: string; + category: string; } diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/onboarding/asset_inventory_onboarding.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/onboarding/asset_inventory_onboarding.tsx index 7b795e61af505..6fe22d99f2bff 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/onboarding/asset_inventory_onboarding.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/onboarding/asset_inventory_onboarding.tsx @@ -23,6 +23,8 @@ export const AssetInventoryOnboarding: FC = ({ children }) => return ; } + return children; + const { status, privileges } = data; // Render different screens based on the onboarding status. diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts index 88a7f14955cfa..553884b5386ef 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts @@ -34,7 +34,14 @@ const entity: EntityEcs = { id: '123', name: 'test-entity', type: 's3', - timestamp: new Date(), + tags: ['tag1', 'tag2'], + labels: { label1: 'value1', label2: 'value2' }, + criticality: 'high_impact', + category: 'test', +}; + +const source = { + '@timestamp': '2021-10-01T12:00:00.000Z', }; describe('useDynamicEntityFlyout', () => { @@ -67,6 +74,7 @@ describe('useDynamicEntityFlyout', () => { act(() => { result.current.openDynamicFlyout({ entity: { ...entity, type: 's3', name: 'testUniversal' }, + source, scopeId: 'scope1', contextId: 'context1', }); @@ -89,6 +97,7 @@ describe('useDynamicEntityFlyout', () => { act(() => { result.current.openDynamicFlyout({ entity: { ...entity, type: 'user', name: 'testUser' }, + source, scopeId: 'scope1', contextId: 'context1', }); @@ -111,6 +120,7 @@ describe('useDynamicEntityFlyout', () => { act(() => { result.current.openDynamicFlyout({ entity: { ...entity, type: 'host', name: 'testHost' }, + source, scopeId: 'scope1', contextId: 'context1', }); @@ -133,6 +143,7 @@ describe('useDynamicEntityFlyout', () => { act(() => { result.current.openDynamicFlyout({ entity: { ...entity, type: 'service', name: 'testService' }, + source, scopeId: 'scope1', contextId: 'context1', }); @@ -153,7 +164,7 @@ describe('useDynamicEntityFlyout', () => { ); act(() => { - result.current.openDynamicFlyout({ entity: { type: 'user' } as EntityEcs }); + result.current.openDynamicFlyout({ entity: { type: 'user' } as EntityEcs, source }); }); expect(toastsMock.addDanger).toHaveBeenCalledWith( @@ -165,7 +176,7 @@ describe('useDynamicEntityFlyout', () => { expect(onFlyoutCloseMock).toHaveBeenCalled(); act(() => { - result.current.openDynamicFlyout({ entity: { type: 'host' } as EntityEcs }); + result.current.openDynamicFlyout({ entity: { type: 'host' } as EntityEcs, source }); }); expect(toastsMock.addDanger).toHaveBeenCalledWith( @@ -177,7 +188,7 @@ describe('useDynamicEntityFlyout', () => { expect(onFlyoutCloseMock).toHaveBeenCalled(); act(() => { - result.current.openDynamicFlyout({ entity: { type: 'service' } as EntityEcs }); + result.current.openDynamicFlyout({ entity: { type: 'service' } as EntityEcs, source }); }); expect(toastsMock.addDanger).toHaveBeenCalledWith( diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx index c931b6772df95..771ff63888217 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx @@ -143,7 +143,7 @@ export interface AllAssetsProps { } // TODO: Asset Inventory - adjust and remove type casting once we have real universal entity data -const getEntity = (record: DataTableRecord): EntityEcs => { +const getEntity = (record: DataTableRecord) => { const { _source } = record.raw; const entityMock = { @@ -151,11 +151,11 @@ const getEntity = (record: DataTableRecord): EntityEcs => { labels: { Group: 'cloud-sec-dev', Environment: 'Production' }, id: 'mock-entity-id', criticality: 'low_impact', - }; + } as EntityEcs; return { - entity: { ..._source.entity, ...entityMock }, - source: _source, + entity: { ...(_source.entity || {}), ...entityMock }, + source: _source || {}, }; }; @@ -201,6 +201,7 @@ export const AllAssets = ({ }; // ----------------------------------------------------------------------------------------- + const { filters, pageSize, diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx index 843f75eb43770..84af562a239ca 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx @@ -32,7 +32,8 @@ export const ExpandableBadgeGroup = ({ }: ExpandableBadgeGroupProps) => { const [badgesToShow, setBadgesToShow] = useState(initialBadgeLimit || 'all'); - const remainingCount = badges.length - badgesToShow; + // Calculate the number of remaining badges. If 'all' badges are shown, the remaining count is 0. + const remainingCount = badgesToShow === 'all' ? 0 : badges.length - badgesToShow; const maxScrollHeight = maxHeight ? `${maxHeight}px` : 'initial'; return ( @@ -44,13 +45,20 @@ export const ExpandableBadgeGroup = ({ `} responsive={false} > - {badgesToShow === 'all' ? badges : badges.slice(0, badgesToShow)} - {remainingCount > 0 && badgesToShow !== 'all' && ( - setBadgesToShow('all')} - >{`+${remainingCount}`} - )} + { + // Show all badges if 'all' is set, otherwise show the first `badgesToShow` badges + badgesToShow === 'all' ? badges : badges.slice(0, badgesToShow) + } + { + // Show the expand badge if there are remaining badges to show + remainingCount > 0 && badgesToShow !== 'all' && ( + setBadgesToShow('all')} + onClickAriaLabel="Expand Remaining Badges" + >{`+${remainingCount}`} + ) + } ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.tsx index 815340fc095c8..8653afa2f2d81 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.tsx @@ -64,7 +64,7 @@ const columns: EuiInMemoryTableProps['columns'] = [ name: i18n.translate('xpack.securitySolution.fieldsTable.valueColumnLabel', { defaultMessage: 'Value', }), - render: (value) =>
{getDescriptionDisplay(value)}
, + render: (value: unknown) =>
{getDescriptionDisplay(value)}
, }, ]; @@ -74,9 +74,9 @@ const getFlattenedItems = (resource: Record) => /** * A component that displays a table of flattened fields and values from a resource object. */ -export const FieldsTable = ({ data }: { data: Record }) => ( +export const FieldsTable = ({ document }: { document: Record }) => ( - {cards.map((card) => ( + {cards.map((card, index) => ( + } expanded localStorageKey={'universal_flyout:overview:fields_table'} > - + ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index bf51e3f7ceb06..a61bea999868b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -48,7 +48,7 @@ const HeaderTags = ({ tags, labels }: { tags: EntityEcs['tags']; labels: EntityE
)), - [labels, euiTheme.border.thick, euiTheme.colors.disabledText] + [labels, euiTheme.colors.disabledText, euiTheme.border.thick, euiTheme.size.xs] ); const allBadges = [...(tagBadges || []), ...(labelBadges || [])]; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx index 39f19eac06d72..b52fe4ab9977d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx @@ -26,20 +26,6 @@ import { i18n } from '@kbn/i18n'; import { AssetCriticalityBadge } from '../../../entity_analytics/components/asset_criticality'; import { ResponsiveDataCards } from './components/responsive_data_cards'; -const getCopyCardProps = ({ title, description }) => ({ - title: ( - - {title} - - - {(copy) => } - - - - ), - description: , -}); - const options = [ { value: 'unassigned', @@ -119,7 +105,19 @@ export const HeaderDataCards = ({
), }, - getCopyCardProps({ title: 'ID', description: id }), + { + title: ( + + {'ID'} + + + {(copy) => } + + + + ), + description: , + }, { title: i18n.translate( 'xpack.securitySolution.universalEntityFlyout.flyoutHeader.headerDataBoxes.categoryLabel', From b2ae6dfa3b20864bd0e54006b9aa72ee543be80a Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 2 Mar 2025 19:09:01 +0200 Subject: [PATCH 12/31] remove override --- .../components/onboarding/asset_inventory_onboarding.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/onboarding/asset_inventory_onboarding.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/onboarding/asset_inventory_onboarding.tsx index 6fe22d99f2bff..7b795e61af505 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/onboarding/asset_inventory_onboarding.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/onboarding/asset_inventory_onboarding.tsx @@ -23,8 +23,6 @@ export const AssetInventoryOnboarding: FC = ({ children }) => return ; } - return children; - const { status, privileges } = data; // Render different screens based on the onboarding status. From 9aa6489571da6cb0388a0f4ac1f5e8169302a339 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 2 Mar 2025 19:32:40 +0200 Subject: [PATCH 13/31] clean --- .../asset_inventory/pages/all_assets.tsx | 4 +-- .../components/expandable_badge_group.tsx | 5 ++- .../entity_details/universal_right/header.tsx | 32 ++++++++----------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx index 771ff63888217..ed8337a5a3ee1 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx @@ -151,10 +151,10 @@ const getEntity = (record: DataTableRecord) => { labels: { Group: 'cloud-sec-dev', Environment: 'Production' }, id: 'mock-entity-id', criticality: 'low_impact', - } as EntityEcs; + } as unknown as EntityEcs; return { - entity: { ...(_source.entity || {}), ...entityMock }, + entity: { ...(_source?.entity || {}), ...entityMock }, source: _source || {}, }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx index 84af562a239ca..a2273d16b8783 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx @@ -7,12 +7,11 @@ import React, { useState } from 'react'; import { css } from '@emotion/react'; -import type { EuiBadgeProps } from '@elastic/eui'; import { EuiBadge, EuiBadgeGroup } from '@elastic/eui'; interface ExpandableBadgeGroupProps { - /** Array of EuiBadges properties to display */ - badges: EuiBadgeProps[]; + /** Array of EuiBadges to display */ + badges: EuiBadge[]; /** The initial number of badges to show before expanding. Defaults to 'all' if not set */ initialBadgeLimit?: number; /** The maximum height of the badge group in pixels. If not set the expandable container will not have inner scrolling */ diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index a61bea999868b..88659b06172eb 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -5,11 +5,10 @@ * 2.0. */ -import { EuiSpacer, EuiText, EuiFlexItem, EuiFlexGroup, EuiBadge, useEuiTheme } from '@elastic/eui'; import React, { useMemo } from 'react'; - -import type { EntityEcs } from '@kbn/securitysolution-ecs/src/entity'; import { css } from '@emotion/react'; +import { EuiSpacer, EuiText, EuiFlexItem, EuiFlexGroup, EuiBadge, useEuiTheme } from '@elastic/eui'; +import type { EntityEcs } from '@kbn/securitysolution-ecs/src/entity'; import { ExpandableBadgeGroup } from './components/expandable_badge_group'; import { HeaderDataCards } from './header_data_cards'; import { EntityIconByType } from '../../../entity_analytics/components/entity_store/helpers'; @@ -20,12 +19,12 @@ import { FlyoutTitle } from '../../shared/components/flyout_title'; const HeaderTags = ({ tags, labels }: { tags: EntityEcs['tags']; labels: EntityEcs['labels'] }) => { const { euiTheme } = useEuiTheme(); - const tagBadges = useMemo( + const tagBadges: EuiBadge[] | undefined = useMemo( () => tags?.map((tag) => {tag}), [tags] ); - const labelBadges = useMemo( + const labelBadges: EuiBadge[] | undefined = useMemo( () => labels && Object.entries(labels)?.map(([key, value]) => ( @@ -58,7 +57,7 @@ const HeaderTags = ({ tags, labels }: { tags: EntityEcs['tags']; labels: EntityE interface UniversalEntityFlyoutHeaderProps { entity: EntityEcs; - timestamp: Date; + timestamp?: Date; } export const UniversalEntityFlyoutHeader = ({ @@ -69,11 +68,11 @@ export const UniversalEntityFlyoutHeader = ({ return ( <> - + - - + + {timestamp && } @@ -97,16 +96,13 @@ export const UniversalEntityFlyoutHeader = ({ category={entity.category} criticality={entity.criticality} /> + {(entity.tags || entity.labels) && ( + <> + + + + )} - {(entity.tags || entity.labels) && ( -
- -
- )} ); }; From 33d7468de398c19ceebbb7227486b16e8670a635 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 2 Mar 2025 19:37:47 +0200 Subject: [PATCH 14/31] clean --- .../entity_details/universal_right/header.tsx | 14 ++++++++------ .../entity_details/universal_right/index.tsx | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index 88659b06172eb..4dbd8c448805c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -70,12 +70,14 @@ export const UniversalEntityFlyoutHeader = ({ <> - - - {timestamp && } - - - + {timestamp && ( + + + + + + + )} - + ); From afcceed7378e294cc93220a5a7685e53e4d849ce Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 2 Mar 2025 19:44:01 +0200 Subject: [PATCH 15/31] clean --- .../asset_inventory/hooks/use_dynamic_entity_flyout.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts index 553884b5386ef..81d6342171c6c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts @@ -84,7 +84,7 @@ describe('useDynamicEntityFlyout', () => { expect(openFlyoutMock).toHaveBeenCalledWith({ right: { id: UniversalEntityPanelKey, - params: { entity: { ...entity, type: 's3', name: 'testUniversal' } }, + params: { entity: { ...entity, type: 's3', name: 'testUniversal' }, source }, }, }); }); @@ -108,6 +108,7 @@ describe('useDynamicEntityFlyout', () => { right: { id: UserPanelKey, params: { userName: 'testUser', scopeId: 'scope1', contextId: 'context1' }, + source, }, }); }); @@ -120,7 +121,6 @@ describe('useDynamicEntityFlyout', () => { act(() => { result.current.openDynamicFlyout({ entity: { ...entity, type: 'host', name: 'testHost' }, - source, scopeId: 'scope1', contextId: 'context1', }); @@ -143,7 +143,6 @@ describe('useDynamicEntityFlyout', () => { act(() => { result.current.openDynamicFlyout({ entity: { ...entity, type: 'service', name: 'testService' }, - source, scopeId: 'scope1', contextId: 'context1', }); From d303c29d4ca969ebf668cc0d55f340f0c2c7fba7 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 2 Mar 2025 19:46:27 +0200 Subject: [PATCH 16/31] types --- .../shared/kbn-securitysolution-ecs/src/entity/index.ts | 2 +- .../asset_inventory/hooks/use_dynamic_entity_flyout.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts b/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts index 9691f60933c81..97317b8d5c855 100644 --- a/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts +++ b/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts @@ -11,7 +11,7 @@ export interface EntityEcs { id: string; name: string; - type: 'user' | 'host' | 'service'; + type: 's3' | 'user' | 'host' | 'service'; tags: string[]; labels: Record; criticality: string; diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts index 81d6342171c6c..1125338f5579c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts @@ -41,7 +41,7 @@ const entity: EntityEcs = { }; const source = { - '@timestamp': '2021-10-01T12:00:00.000Z', + '@timestamp': '2025-10-01T12:00:00.000Z', }; describe('useDynamicEntityFlyout', () => { From 3659ae1e709b2a85192d1536aee1836fa6f92e24 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 2 Mar 2025 20:05:15 +0200 Subject: [PATCH 17/31] clean --- .../hooks/use_dynamic_entity_flyout.test.ts | 6 +++--- .../entity_details/universal_right/header.tsx | 14 +++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts index 1125338f5579c..36e1d754b502b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts @@ -33,7 +33,7 @@ jest.mock('../../flyout/shared/hooks/use_on_expandable_flyout_close', () => ({ const entity: EntityEcs = { id: '123', name: 'test-entity', - type: 's3', + type: 'container', tags: ['tag1', 'tag2'], labels: { label1: 'value1', label2: 'value2' }, criticality: 'high_impact', @@ -73,7 +73,7 @@ describe('useDynamicEntityFlyout', () => { act(() => { result.current.openDynamicFlyout({ - entity: { ...entity, type: 's3', name: 'testUniversal' }, + entity: { ...entity, type: 'container', name: 'testUniversal' }, source, scopeId: 'scope1', contextId: 'context1', @@ -84,7 +84,7 @@ describe('useDynamicEntityFlyout', () => { expect(openFlyoutMock).toHaveBeenCalledWith({ right: { id: UniversalEntityPanelKey, - params: { entity: { ...entity, type: 's3', name: 'testUniversal' }, source }, + params: { entity: { ...entity, type: 'container', name: 'testUniversal' }, source }, }, }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index 4dbd8c448805c..13445988a3223 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -7,8 +7,10 @@ import React, { useMemo } from 'react'; import { css } from '@emotion/react'; +import type { IconType } from '@elastic/eui'; import { EuiSpacer, EuiText, EuiFlexItem, EuiFlexGroup, EuiBadge, useEuiTheme } from '@elastic/eui'; import type { EntityEcs } from '@kbn/securitysolution-ecs/src/entity'; +import type { EntityType } from '../../../../common/entity_analytics/types'; import { ExpandableBadgeGroup } from './components/expandable_badge_group'; import { HeaderDataCards } from './header_data_cards'; import { EntityIconByType } from '../../../entity_analytics/components/entity_store/helpers'; @@ -60,6 +62,16 @@ interface UniversalEntityFlyoutHeaderProps { timestamp?: Date; } +// TODO: Asset Inventory - move this to a shared location, for now it's here as a mock since we dont have generic entities yet +enum GenericEntityType { + container = 'container', +} + +export const UniversalEntityIconByType: Record = { + ...EntityIconByType, + container: 'container', +}; + export const UniversalEntityFlyoutHeader = ({ entity, timestamp, @@ -81,7 +93,7 @@ export const UniversalEntityFlyoutHeader = ({ From 6d44bbcab553f36e1da827f66460a39d224bcea9 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 2 Mar 2025 20:17:25 +0200 Subject: [PATCH 18/31] clean --- .../shared/kbn-securitysolution-ecs/src/entity/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts b/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts index 97317b8d5c855..a07cd16a9df3b 100644 --- a/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts +++ b/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts @@ -11,7 +11,7 @@ export interface EntityEcs { id: string; name: string; - type: 's3' | 'user' | 'host' | 'service'; + type: 'container' | 'user' | 'host' | 'service'; tags: string[]; labels: Record; criticality: string; From f07b7abd84cccedd4950cc18a58b13c0aeeae688 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 2 Mar 2025 20:42:17 +0200 Subject: [PATCH 19/31] added tests for components --- .../expandable_badge_group.test.tsx | 49 +++++++++++++++++ .../components/expandable_badge_group.tsx | 1 - .../components/fields_table.test.tsx | 53 +++++++++++++++++++ .../components/responsive_data_cards.test.tsx | 34 ++++++++++++ .../components/responsive_data_cards.tsx | 1 + 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.test.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.test.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.test.tsx diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.test.tsx new file mode 100644 index 0000000000000..155ee9ff04d37 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.test.tsx @@ -0,0 +1,49 @@ +/* + * 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 from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { EuiBadge } from '@elastic/eui'; +import { ExpandableBadgeGroup } from './expandable_badge_group'; + +const badges = [ + {'Badge 1'}, + {'Badge 2'}, + {'Badge 3'}, + {'Badge 4'}, +]; + +describe('ExpandableBadgeGroup', () => { + it('renders all badges when initialBadgeLimit is not set', () => { + render(); + badges.forEach((badge) => { + expect(screen.getByText(badge.props.children)).toBeInTheDocument(); + }); + }); + + it('renders limited badges and expand button when initialBadgeLimit is set', () => { + render(); + expect(screen.getByText('Badge 1')).toBeInTheDocument(); + expect(screen.getByText('Badge 2')).toBeInTheDocument(); + expect(screen.queryByText('Badge 3')).not.toBeInTheDocument(); + expect(screen.queryByText('Badge 4')).not.toBeInTheDocument(); + expect(screen.getByText('+2')).toBeInTheDocument(); + }); + + it('expands to show all badges when expand button is clicked', () => { + render(); + fireEvent.click(screen.getByText('+2')); + badges.forEach((badge) => { + expect(screen.getByText(badge.props.children)).toBeInTheDocument(); + }); + }); + + it('applies maxHeight style when maxHeight is set', () => { + const { container } = render(); + expect(container.firstChild).toHaveStyle('max-height: 100px'); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx index a2273d16b8783..6fc5abb11e693 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx @@ -42,7 +42,6 @@ export const ExpandableBadgeGroup = ({ max-height: ${maxScrollHeight}; overflow-y: auto; `} - responsive={false} > { // Show all badges if 'all' is set, otherwise show the first `badgesToShow` badges diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.test.tsx new file mode 100644 index 0000000000000..5753c9a153d15 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/fields_table.test.tsx @@ -0,0 +1,53 @@ +/* + * 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 from 'react'; +import { render, screen } from '@testing-library/react'; +import { FieldsTable } from './fields_table'; + +const mockDocument = { + field1: 'value1', + field2: true, + field3: null, + field4: { + nestedField1: 'nestedValue1', + nestedField2: 123, + }, +}; + +describe('FieldsTable', () => { + it('renders the table with flattened fields and values', () => { + render(); + + expect(screen.getByText('field1')).toBeInTheDocument(); + expect(screen.getByText('value1')).toBeInTheDocument(); + expect(screen.getByText('field2')).toBeInTheDocument(); + expect(screen.getByText('true')).toBeInTheDocument(); + expect(screen.getByText('field3')).toBeInTheDocument(); + expect(screen.getByText('null')).toBeInTheDocument(); + expect(screen.getByText('field4.nestedField1')).toBeInTheDocument(); + expect(screen.getByText('nestedValue1')).toBeInTheDocument(); + expect(screen.getByText('field4.nestedField2')).toBeInTheDocument(); + expect(screen.getByText('123')).toBeInTheDocument(); + }); + + it('renders undefined values correctly', () => { + const documentWithUndefined = { field1: undefined }; + render(); + + expect(screen.getByText('field1')).toBeInTheDocument(); + expect(screen.getByText('undefined')).toBeInTheDocument(); + }); + + it('renders object values correctly', () => { + const documentWithObject = { field1: { nestedField: 'nestedValue' } }; + render(); + + expect(screen.getByText('field1.nestedField')).toBeInTheDocument(); + expect(screen.getByText('nestedValue')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.test.tsx new file mode 100644 index 0000000000000..61cd94b0ee357 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.test.tsx @@ -0,0 +1,34 @@ +/* + * 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 from 'react'; +import { render, screen } from '@testing-library/react'; +import type { EuiCardProps } from '@elastic/eui'; +import { ResponsiveDataCards } from './responsive_data_cards'; + +const cards: Array> = [ + { title: 'Card 1', description: 'Description 1' }, + { title: 'Card 2', description: 'Description 2' }, + { title: 'Card 3', description: 'Description 3' }, + { title: 'Card 4', description: 'Description 4' }, +]; + +describe('ResponsiveDataCards', () => { + it('renders the correct number of cards', () => { + render(); + const renderedCards = screen.getAllByTestId('responsive-data-card'); + expect(renderedCards).toHaveLength(cards.length); + }); + + it('renders card titles and descriptions correctly', () => { + render(); + cards.forEach(({ title, description }) => { + expect(screen.getByText(title)).toBeInTheDocument(); + expect(screen.getByText(description)).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.tsx index 761335f06d0df..81e0c42d62254 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.tsx @@ -52,6 +52,7 @@ export const ResponsiveDataCards = ({ cards, collapseWidth = 750 }: HeaderDataCa `} > Date: Mon, 3 Mar 2025 11:31:38 +0200 Subject: [PATCH 20/31] types --- .../asset_inventory/hooks/use_dynamic_entity_flyout.ts | 2 +- .../universal_right/components/expandable_badge_group.tsx | 2 +- .../components/responsive_data_cards.test.tsx | 3 +-- .../flyout/entity_details/universal_right/header.tsx | 4 ++-- .../public/flyout/entity_details/universal_right/index.tsx | 7 ++++++- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts index cabf6bc3f0c09..0078c40588bbd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.ts @@ -26,7 +26,7 @@ import { useOnExpandableFlyoutClose } from '../../flyout/shared/hooks/use_on_exp interface InventoryFlyoutProps { entity: EntityEcs; - source: EsHitRecord['_source']; + source?: EsHitRecord['_source']; scopeId?: string; contextId?: string; } diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx index 6fc5abb11e693..69cceae753d2e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx @@ -11,7 +11,7 @@ import { EuiBadge, EuiBadgeGroup } from '@elastic/eui'; interface ExpandableBadgeGroupProps { /** Array of EuiBadges to display */ - badges: EuiBadge[]; + badges: Array; /** The initial number of badges to show before expanding. Defaults to 'all' if not set */ initialBadgeLimit?: number; /** The maximum height of the badge group in pixels. If not set the expandable container will not have inner scrolling */ diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.test.tsx index 61cd94b0ee357..252b087348199 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.test.tsx @@ -7,10 +7,9 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import type { EuiCardProps } from '@elastic/eui'; import { ResponsiveDataCards } from './responsive_data_cards'; -const cards: Array> = [ +const cards = [ { title: 'Card 1', description: 'Description 1' }, { title: 'Card 2', description: 'Description 2' }, { title: 'Card 3', description: 'Description 3' }, diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index 13445988a3223..fb01c29c35464 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -21,12 +21,12 @@ import { FlyoutTitle } from '../../shared/components/flyout_title'; const HeaderTags = ({ tags, labels }: { tags: EntityEcs['tags']; labels: EntityEcs['labels'] }) => { const { euiTheme } = useEuiTheme(); - const tagBadges: EuiBadge[] | undefined = useMemo( + const tagBadges: Array | undefined = useMemo( () => tags?.map((tag) => {tag}), [tags] ); - const labelBadges: EuiBadge[] | undefined = useMemo( + const labelBadges: Array | undefined = useMemo( () => labels && Object.entries(labels)?.map(([key, value]) => ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx index 4be13038f0950..459245b69769b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx @@ -30,6 +30,8 @@ export interface UniversalEntityPanelExpandableFlyoutProps extends FlyoutPanelPr params: UniversalEntityPanelProps; } +const isDate = (value: unknown): value is Date => value instanceof Date; + export const UniversalEntityPanel = ({ entity, source }: UniversalEntityPanelProps) => { useEffect(() => { uiMetricService.trackUiMetric(METRIC_TYPE.COUNT, UNIVERSAL_ENTITY_FLYOUT_OPENED); @@ -38,7 +40,10 @@ export const UniversalEntityPanel = ({ entity, source }: UniversalEntityPanelPro return ( <> - + ); From fe227621668448483a3060a25338d329e77dab84 Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 3 Mar 2025 12:22:29 +0200 Subject: [PATCH 21/31] types --- .../components/expandable_badge_group.test.tsx | 2 +- .../flyout/entity_details/universal_right/header.tsx | 8 ++++---- .../flyout/entity_details/universal_right/index.tsx | 7 +++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.test.tsx index 155ee9ff04d37..72ebad0fc46cb 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.test.tsx @@ -10,7 +10,7 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { EuiBadge } from '@elastic/eui'; import { ExpandableBadgeGroup } from './expandable_badge_group'; -const badges = [ +const badges: Array = [ {'Badge 1'}, {'Badge 2'}, {'Badge 3'}, diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index fb01c29c35464..e95cd5ba5a811 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -21,15 +21,15 @@ import { FlyoutTitle } from '../../shared/components/flyout_title'; const HeaderTags = ({ tags, labels }: { tags: EntityEcs['tags']; labels: EntityEcs['labels'] }) => { const { euiTheme } = useEuiTheme(); - const tagBadges: Array | undefined = useMemo( - () => tags?.map((tag) => {tag}), + const tagBadges = useMemo( + () => tags?.map>((tag) => {tag}), [tags] ); - const labelBadges: Array | undefined = useMemo( + const labelBadges = useMemo( () => labels && - Object.entries(labels)?.map(([key, value]) => ( + Object.entries(labels)?.map>(([key, value]) => ( - + ); From 1ad34910840c9fe7aa7dfd3e786919b2dbb09ed3 Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 3 Mar 2025 14:01:55 +0200 Subject: [PATCH 22/31] types --- .../public/flyout/entity_details/universal_right/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx index a36aa1bf4de4d..dc6a3a13d1b08 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/index.tsx @@ -37,7 +37,8 @@ export const UniversalEntityPanel = ({ entity, source }: UniversalEntityPanelPro uiMetricService.trackUiMetric(METRIC_TYPE.COUNT, UNIVERSAL_ENTITY_FLYOUT_OPENED); }, [entity]); - const timestamp = isDate(source['@timestamp']) ? source['@timestamp'] : undefined; + const docTimestamp = source?.['@timestamp']; + const timestamp = isDate(docTimestamp) ? docTimestamp : undefined; return ( <> From c906c8618605518e9d3c1a87bd2283d7a8e62e74 Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 3 Mar 2025 16:25:07 +0200 Subject: [PATCH 23/31] types --- .../expandable_badge_group.test.tsx | 41 ++++++++++----- .../components/expandable_badge_group.tsx | 12 +++-- .../entity_details/universal_right/header.tsx | 51 +++++++++++-------- 3 files changed, 65 insertions(+), 39 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.test.tsx index 72ebad0fc46cb..f31fe1ded8913 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.test.tsx @@ -7,26 +7,32 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; -import { EuiBadge } from '@elastic/eui'; import { ExpandableBadgeGroup } from './expandable_badge_group'; -const badges: Array = [ - {'Badge 1'}, - {'Badge 2'}, - {'Badge 3'}, - {'Badge 4'}, +const badgeProps = [ + { color: 'hollow', children: 'Badge 1' }, + { color: 'hollow', children: 'Badge 2' }, + { color: 'hollow', children: 'Badge 3' }, + { color: 'hollow', children: 'Badge 4' }, +]; + +const badgePropsWithElement = [ + { color: 'hollow', children: {'Badge 1 with element'} }, + { color: 'hollow', children: {'Badge 2 with element'} }, + { color: 'hollow', children: {'Badge 3 with element'} }, + { color: 'hollow', children: {'Badge 4 with element'} }, ]; describe('ExpandableBadgeGroup', () => { it('renders all badges when initialBadgeLimit is not set', () => { - render(); - badges.forEach((badge) => { - expect(screen.getByText(badge.props.children)).toBeInTheDocument(); + render(); + badgeProps.forEach((badge) => { + expect(screen.getByText(badge.children)).toBeInTheDocument(); }); }); it('renders limited badges and expand button when initialBadgeLimit is set', () => { - render(); + render(); expect(screen.getByText('Badge 1')).toBeInTheDocument(); expect(screen.getByText('Badge 2')).toBeInTheDocument(); expect(screen.queryByText('Badge 3')).not.toBeInTheDocument(); @@ -35,15 +41,22 @@ describe('ExpandableBadgeGroup', () => { }); it('expands to show all badges when expand button is clicked', () => { - render(); + render(); fireEvent.click(screen.getByText('+2')); - badges.forEach((badge) => { - expect(screen.getByText(badge.props.children)).toBeInTheDocument(); + badgeProps.forEach((badge) => { + expect(screen.getByText(badge.children)).toBeInTheDocument(); }); }); it('applies maxHeight style when maxHeight is set', () => { - const { container } = render(); + const { container } = render(); expect(container.firstChild).toHaveStyle('max-height: 100px'); }); + + it('renders badges with children as React elements', () => { + render(); + badgePropsWithElement.forEach((badge) => { + expect(screen.getByText(badge.children.props.children)).toBeInTheDocument(); + }); + }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx index 69cceae753d2e..79970ed3de585 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx @@ -5,13 +5,14 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { css } from '@emotion/react'; import { EuiBadge, EuiBadgeGroup } from '@elastic/eui'; +import type { EuiBadgeProps } from '@elastic/eui'; interface ExpandableBadgeGroupProps { /** Array of EuiBadges to display */ - badges: Array; + badges: EuiBadgeProps[]; /** The initial number of badges to show before expanding. Defaults to 'all' if not set */ initialBadgeLimit?: number; /** The maximum height of the badge group in pixels. If not set the expandable container will not have inner scrolling */ @@ -35,6 +36,11 @@ export const ExpandableBadgeGroup = ({ const remainingCount = badgesToShow === 'all' ? 0 : badges.length - badgesToShow; const maxScrollHeight = maxHeight ? `${maxHeight}px` : 'initial'; + const badgeElements = useMemo( + () => badges.map((badge, index) => ), + [badges] + ); + return ( { // Show all badges if 'all' is set, otherwise show the first `badgesToShow` badges - badgesToShow === 'all' ? badges : badges.slice(0, badgesToShow) + badgesToShow === 'all' ? badgeElements : badgeElements.slice(0, badgesToShow) } { // Show the expand badge if there are remaining badges to show diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index e95cd5ba5a811..cce0a1ffcd179 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -8,7 +8,7 @@ import React, { useMemo } from 'react'; import { css } from '@emotion/react'; import type { IconType } from '@elastic/eui'; -import { EuiSpacer, EuiText, EuiFlexItem, EuiFlexGroup, EuiBadge, useEuiTheme } from '@elastic/eui'; +import { EuiSpacer, EuiText, EuiFlexItem, EuiFlexGroup, useEuiTheme } from '@elastic/eui'; import type { EntityEcs } from '@kbn/securitysolution-ecs/src/entity'; import type { EntityType } from '../../../../common/entity_analytics/types'; import { ExpandableBadgeGroup } from './components/expandable_badge_group'; @@ -22,33 +22,40 @@ const HeaderTags = ({ tags, labels }: { tags: EntityEcs['tags']; labels: EntityE const { euiTheme } = useEuiTheme(); const tagBadges = useMemo( - () => tags?.map>((tag) => {tag}), + () => + tags?.map((tag) => ({ + color: 'hollow', + children: tag, + })), [tags] ); const labelBadges = useMemo( () => labels && - Object.entries(labels)?.map>(([key, value]) => ( - - - {key} - - - {value} - - - )), + Object.entries(labels)?.map(([key, value]) => ({ + color: 'hollow', + children: ( + <> + + {key} + + + {value} + + + ), + })), [labels, euiTheme.colors.disabledText, euiTheme.border.thick, euiTheme.size.xs] ); From bd6dc8bd4b44d7bd02b651f47e257593357c3297 Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 3 Mar 2025 16:36:33 +0200 Subject: [PATCH 24/31] comments --- .../universal_right/components/expandable_badge_group.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx index 79970ed3de585..f6d714f13b614 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx @@ -9,6 +9,7 @@ import React, { useState, useMemo } from 'react'; import { css } from '@emotion/react'; import { EuiBadge, EuiBadgeGroup } from '@elastic/eui'; import type { EuiBadgeProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; interface ExpandableBadgeGroupProps { /** Array of EuiBadges to display */ @@ -59,7 +60,10 @@ export const ExpandableBadgeGroup = ({ setBadgesToShow('all')} - onClickAriaLabel="Expand Remaining Badges" + onClickAriaLabel={i18n.translate( + 'xpack.securitySolution.expandableBadgeGroup.expandBadgeAriaLabel', + { defaultMessage: 'Expand Remaining Badges' } + )} >{`+${remainingCount}`} ) } From ffef5975e230fb5efd3fb41ab13814bc150440ca Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 3 Mar 2025 18:05:30 +0200 Subject: [PATCH 25/31] comments --- .../components/expandable_badge_group.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx index f6d714f13b614..cbadc8c3ab8dd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx @@ -6,7 +6,6 @@ */ import React, { useState, useMemo } from 'react'; -import { css } from '@emotion/react'; import { EuiBadge, EuiBadgeGroup } from '@elastic/eui'; import type { EuiBadgeProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -45,10 +44,10 @@ export const ExpandableBadgeGroup = ({ return ( { // Show all badges if 'all' is set, otherwise show the first `badgesToShow` badges From 80e40df5ac3c213deb01bf81c151b195e36de512 Mon Sep 17 00:00:00 2001 From: Jordan Date: Tue, 4 Mar 2025 11:08:32 +0200 Subject: [PATCH 26/31] fix tests --- .../asset_inventory/hooks/use_dynamic_entity_flyout.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts index 36e1d754b502b..873fb395ff1ef 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_dynamic_entity_flyout.test.ts @@ -108,7 +108,6 @@ describe('useDynamicEntityFlyout', () => { right: { id: UserPanelKey, params: { userName: 'testUser', scopeId: 'scope1', contextId: 'context1' }, - source, }, }); }); From 88cdae11b42c55ba76e0cc7a1da23dc3eaffea1a Mon Sep 17 00:00:00 2001 From: Jordan Date: Tue, 4 Mar 2025 13:21:49 +0200 Subject: [PATCH 27/31] comments --- .../universal_right/components/responsive_data_cards.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.tsx index 81e0c42d62254..c7c933c75e822 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/responsive_data_cards.tsx @@ -10,7 +10,7 @@ import { css } from '@emotion/react'; import { EuiCard, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; import type { EuiCardProps } from '@elastic/eui/src/components/card/card'; -interface HeaderDataCardsProps { +interface ResponsiveDataCardsProps { /** * An array of EuiCardProps objects, defining the cards to be displayed. */ @@ -26,7 +26,7 @@ interface HeaderDataCardsProps { * A component that displays a group of data cards in a responsive layout. * Depending on the width of the container, the cards will be displayed in a row layout or a two columns layout. */ -export const ResponsiveDataCards = ({ cards, collapseWidth = 750 }: HeaderDataCardsProps) => { +export const ResponsiveDataCards = ({ cards, collapseWidth = 750 }: ResponsiveDataCardsProps) => { const { euiTheme } = useEuiTheme(); return ( From c13543f06c2d2c1ace3960362c97ffeb65ad78aa Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 5 Mar 2025 12:04:18 +0200 Subject: [PATCH 28/31] review comments --- .../asset_criticality_selector.tsx | 16 ++++---- .../components/expandable_badge_group.tsx | 12 +++--- .../universal_right/header_data_cards.tsx | 40 +------------------ 3 files changed, 17 insertions(+), 51 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx index 60ecd975697c7..0ebb5486f47f3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx @@ -280,13 +280,15 @@ const option = ( ), }); -const options: Array> = [ - option('unassigned'), - option('low_impact'), - option('medium_impact'), - option('high_impact'), - option('extreme_impact'), -]; + +export const assetCriticalityOptions: Array> = + [ + option('unassigned'), + option('low_impact'), + option('medium_impact'), + option('high_impact'), + option('extreme_impact'), + ]; export const AssetCriticalityAccordion = React.memo(AssetCriticalityAccordionComponent); AssetCriticalityAccordion.displayName = 'AssetCriticalityAccordion'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx index cbadc8c3ab8dd..01e7ab64c6a33 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/components/expandable_badge_group.tsx @@ -27,13 +27,13 @@ interface ExpandableBadgeGroupProps { */ export const ExpandableBadgeGroup = ({ badges, - initialBadgeLimit, + initialBadgeLimit = 'all', maxHeight, }: ExpandableBadgeGroupProps) => { - const [badgesToShow, setBadgesToShow] = useState(initialBadgeLimit || 'all'); + const [visibleBadgesCount, setVisibleBadgesCount] = useState(initialBadgeLimit); // Calculate the number of remaining badges. If 'all' badges are shown, the remaining count is 0. - const remainingCount = badgesToShow === 'all' ? 0 : badges.length - badgesToShow; + const remainingCount = visibleBadgesCount === 'all' ? 0 : badges.length - visibleBadgesCount; const maxScrollHeight = maxHeight ? `${maxHeight}px` : 'initial'; const badgeElements = useMemo( @@ -51,14 +51,14 @@ export const ExpandableBadgeGroup = ({ > { // Show all badges if 'all' is set, otherwise show the first `badgesToShow` badges - badgesToShow === 'all' ? badgeElements : badgeElements.slice(0, badgesToShow) + visibleBadgesCount === 'all' ? badgeElements : badgeElements.slice(0, visibleBadgesCount) } { // Show the expand badge if there are remaining badges to show - remainingCount > 0 && badgesToShow !== 'all' && ( + remainingCount > 0 && visibleBadgesCount !== 'all' && ( setBadgesToShow('all')} + onClick={() => setVisibleBadgesCount('all')} onClickAriaLabel={i18n.translate( 'xpack.securitySolution.expandableBadgeGroup.expandBadgeAriaLabel', { defaultMessage: 'Expand Remaining Badges' } diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx index b52fe4ab9977d..ae90388e3f9ee 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx @@ -23,45 +23,9 @@ import { } from '@elastic/eui'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; -import { AssetCriticalityBadge } from '../../../entity_analytics/components/asset_criticality'; +import { assetCriticalityOptions } from '../../../entity_analytics/components/asset_criticality/asset_criticality_selector'; import { ResponsiveDataCards } from './components/responsive_data_cards'; -const options = [ - { - value: 'unassigned', - inputDisplay: ( - - ), - }, - { - value: 'low_impact', - inputDisplay: ( - - ), - }, - { - value: 'medium_impact', - inputDisplay: ( - - ), - }, - { - value: 'high_impact', - inputDisplay: ( - - ), - }, - { - value: 'extreme_impact', - inputDisplay: ( - - ), - }, -]; - export const HeaderDataCards = ({ criticality, id, @@ -98,7 +62,7 @@ export const HeaderDataCards = ({ fullWidth={false} compressed hasDividers - options={options} + options={assetCriticalityOptions} valueOfSelected={selectValue} onChange={(newValue) => setSelectValue(newValue)} /> From ecbe2ca41297a98d8ea525e7160ec90d4e3f075e Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 5 Mar 2025 15:43:10 +0200 Subject: [PATCH 29/31] types --- .../asset_criticality/asset_criticality_selector.tsx | 2 +- .../universal_right/components/expandable_badge_group.tsx | 2 +- .../entity_details/universal_right/header_data_cards.tsx | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx index 0ebb5486f47f3..f7561cd75ade9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx @@ -235,7 +235,7 @@ const AssetCriticalityModal: React.FC = ({ { - const [selectValue, setSelectValue] = useState(criticality); + const [selectValue, setSelectValue] = useState( + criticality || 'unassigned' + ); const cards = useMemo( () => [ From 78c6856ef0e9b6d17b974ff8e7b8428383f717bf Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 5 Mar 2025 16:53:14 +0200 Subject: [PATCH 30/31] types --- .../shared/kbn-securitysolution-ecs/src/entity/index.ts | 2 +- .../flyout/entity_details/universal_right/header_data_cards.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts b/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts index a07cd16a9df3b..ebaf5536a69fc 100644 --- a/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts +++ b/src/platform/packages/shared/kbn-securitysolution-ecs/src/entity/index.ts @@ -14,6 +14,6 @@ export interface EntityEcs { type: 'container' | 'user' | 'host' | 'service'; tags: string[]; labels: Record; - criticality: string; + criticality: 'low_impact' | 'medium_impact' | 'high_impact' | 'extreme_impact' | 'unassigned'; category: string; } diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx index b11ae43b86355..7bc0bc16e6ea6 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header_data_cards.tsx @@ -33,7 +33,7 @@ export const HeaderDataCards = ({ category, type, }: { - criticality?: string; + criticality?: CriticalityLevelWithUnassigned; id: string; category: string; type: string; From 4e4e3e1cedd6114c1bacbd9af3cf8b259b20141a Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 5 Mar 2025 17:26:47 +0200 Subject: [PATCH 31/31] review comments --- .../flyout/entity_details/universal_right/header.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx index cce0a1ffcd179..4ee8428480144 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/flyout/entity_details/universal_right/header.tsx @@ -18,6 +18,9 @@ import { PreferenceFormattedDate } from '../../../common/components/formatted_da import { FlyoutHeader } from '../../shared/components/flyout_header'; import { FlyoutTitle } from '../../shared/components/flyout_title'; +const initialBadgeLimit = 3; +const maxBadgeContainerHeight = 180; + const HeaderTags = ({ tags, labels }: { tags: EntityEcs['tags']; labels: EntityEcs['labels'] }) => { const { euiTheme } = useEuiTheme(); @@ -61,7 +64,13 @@ const HeaderTags = ({ tags, labels }: { tags: EntityEcs['tags']; labels: EntityE const allBadges = [...(tagBadges || []), ...(labelBadges || [])]; - return ; + return ( + + ); }; interface UniversalEntityFlyoutHeaderProps {