From a3da12562dbf476dad4867de331546fcc0a8dd54 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 10 Oct 2024 07:17:00 +1100 Subject: [PATCH] [8.x] [Inventory] Fixing entity links (#195625) (#195667) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Backport This will backport the following commits from `main` to `8.x`: - [[Inventory] Fixing entity links (#195625)](https://github.com/elastic/kibana/pull/195625) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: CauĂȘ Marcondes <55978943+cauemarcondes@users.noreply.github.com> --- .../inventory/common/entities.ts | 9 +- .../entity_name/entity_name.test.tsx | 152 ++++++++++++++++++ .../entities_grid/entity_name/index.tsx | 16 +- 3 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/entity_name.test.tsx diff --git a/x-pack/plugins/observability_solution/inventory/common/entities.ts b/x-pack/plugins/observability_solution/inventory/common/entities.ts index 218e3d50905a9..40fae48cb9dc3 100644 --- a/x-pack/plugins/observability_solution/inventory/common/entities.ts +++ b/x-pack/plugins/observability_solution/inventory/common/entities.ts @@ -78,26 +78,27 @@ interface BaseEntity { [ENTITY_TYPE]: EntityType; [ENTITY_DISPLAY_NAME]: string; [ENTITY_DEFINITION_ID]: string; - [ENTITY_IDENTITY_FIELDS]: string[]; + [ENTITY_IDENTITY_FIELDS]: string | string[]; + [key: string]: any; } /** * These types are based on service, host and container from the built in definition. */ -interface ServiceEntity extends BaseEntity { +export interface ServiceEntity extends BaseEntity { [ENTITY_TYPE]: 'service'; [SERVICE_NAME]: string; [SERVICE_ENVIRONMENT]?: string | string[] | null; [AGENT_NAME]: string | string[] | null; } -interface HostEntity extends BaseEntity { +export interface HostEntity extends BaseEntity { [ENTITY_TYPE]: 'host'; [HOST_NAME]: string; [CLOUD_PROVIDER]: string | string[] | null; } -interface ContainerEntity extends BaseEntity { +export interface ContainerEntity extends BaseEntity { [ENTITY_TYPE]: 'container'; [CONTAINER_ID]: string; [CLOUD_PROVIDER]: string | string[] | null; diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/entity_name.test.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/entity_name.test.tsx new file mode 100644 index 0000000000000..36aad3d8e3a97 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/entity_name.test.tsx @@ -0,0 +1,152 @@ +/* + * 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 KibanaReactContextValue } from '@kbn/kibana-react-plugin/public'; +import * as useKibana from '../../../hooks/use_kibana'; +import { EntityName } from '.'; +import { ContainerEntity, HostEntity, ServiceEntity } from '../../../../common/entities'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { ASSET_DETAILS_LOCATOR_ID } from '@kbn/observability-shared-plugin/common/locators/infra/asset_details_locator'; + +describe('EntityName', () => { + jest.spyOn(useKibana, 'useKibana').mockReturnValue({ + services: { + share: { + url: { + locators: { + get: (locatorId: string) => { + return { + getRedirectUrl: (params: { [key: string]: any }) => { + if (locatorId === ASSET_DETAILS_LOCATOR_ID) { + return `assets_url/${params.assetType}/${params.assetId}`; + } + return `services_url/${params.serviceName}?environment=${params.environment}`; + }, + }; + }, + }, + }, + }, + }, + } as unknown as KibanaReactContextValue); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('returns host link', () => { + const entity: HostEntity = { + 'entity.lastSeenTimestamp': 'foo', + 'entity.id': '1', + 'entity.type': 'host', + 'entity.displayName': 'foo', + 'entity.identityFields': 'host.name', + 'host.name': 'foo', + 'entity.definitionId': 'host', + 'cloud.provider': null, + }; + render(); + expect(screen.queryByTestId('entityNameLink')?.getAttribute('href')).toEqual( + 'assets_url/host/foo' + ); + expect(screen.queryByTestId('entityNameDisplayName')?.textContent).toEqual('foo'); + }); + + it('returns container link', () => { + const entity: ContainerEntity = { + 'entity.lastSeenTimestamp': 'foo', + 'entity.id': '1', + 'entity.type': 'container', + 'entity.displayName': 'foo', + 'entity.identityFields': 'container.id', + 'container.id': 'foo', + 'entity.definitionId': 'container', + 'cloud.provider': null, + }; + render(); + expect(screen.queryByTestId('entityNameLink')?.getAttribute('href')).toEqual( + 'assets_url/container/foo' + ); + expect(screen.queryByTestId('entityNameDisplayName')?.textContent).toEqual('foo'); + }); + + it('returns service link without environment', () => { + const entity: ServiceEntity = { + 'entity.lastSeenTimestamp': 'foo', + 'entity.id': '1', + 'entity.type': 'service', + 'entity.displayName': 'foo', + 'entity.identityFields': 'service.name', + 'service.name': 'foo', + 'entity.definitionId': 'service', + 'agent.name': 'bar', + }; + render(); + expect(screen.queryByTestId('entityNameLink')?.getAttribute('href')).toEqual( + 'services_url/foo?environment=undefined' + ); + expect(screen.queryByTestId('entityNameDisplayName')?.textContent).toEqual('foo'); + }); + + it('returns service link with environment', () => { + const entity: ServiceEntity = { + 'entity.lastSeenTimestamp': 'foo', + 'entity.id': '1', + 'entity.type': 'service', + 'entity.displayName': 'foo', + 'entity.identityFields': 'service.name', + 'service.name': 'foo', + 'entity.definitionId': 'service', + 'agent.name': 'bar', + 'service.environment': 'baz', + }; + render(); + expect(screen.queryByTestId('entityNameLink')?.getAttribute('href')).toEqual( + 'services_url/foo?environment=baz' + ); + expect(screen.queryByTestId('entityNameDisplayName')?.textContent).toEqual('foo'); + }); + + it('returns service link with first environment when it is an array', () => { + const entity: ServiceEntity = { + 'entity.lastSeenTimestamp': 'foo', + 'entity.id': '1', + 'entity.type': 'service', + 'entity.displayName': 'foo', + 'entity.identityFields': 'service.name', + 'service.name': 'foo', + 'entity.definitionId': 'service', + 'agent.name': 'bar', + 'service.environment': ['baz', 'bar', 'foo'], + }; + render(); + expect(screen.queryByTestId('entityNameLink')?.getAttribute('href')).toEqual( + 'services_url/foo?environment=baz' + ); + expect(screen.queryByTestId('entityNameDisplayName')?.textContent).toEqual('foo'); + }); + + it('returns service link identity fields is an array', () => { + const entity: ServiceEntity = { + 'entity.lastSeenTimestamp': 'foo', + 'entity.id': '1', + 'entity.type': 'service', + 'entity.displayName': 'foo', + 'entity.identityFields': ['service.name', 'service.environment'], + 'service.name': 'foo', + 'entity.definitionId': 'service', + 'agent.name': 'bar', + 'service.environment': 'baz', + }; + render(); + expect(screen.queryByTestId('entityNameLink')?.getAttribute('href')).toEqual( + 'services_url/foo?environment=baz' + ); + expect(screen.queryByTestId('entityNameDisplayName')?.textContent).toEqual('foo'); + }); +}); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/index.tsx index debe91d52dec1..f3488dfddbc4e 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/index.tsx @@ -36,33 +36,37 @@ export function EntityName({ entity }: EntityNameProps) { const getEntityRedirectUrl = useCallback(() => { const type = entity[ENTITY_TYPE]; // For service, host and container type there is only one identity field - const identityField = entity[ENTITY_IDENTITY_FIELDS][0]; + const identityField = Array.isArray(entity[ENTITY_IDENTITY_FIELDS]) + ? entity[ENTITY_IDENTITY_FIELDS][0] + : entity[ENTITY_IDENTITY_FIELDS]; + const identityValue = entity[identityField]; - // Any unrecognised types will always return undefined switch (type) { case 'host': case 'container': return assetDetailsLocator?.getRedirectUrl({ - assetId: identityField, + assetId: identityValue, assetType: type, }); case 'service': return serviceOverviewLocator?.getRedirectUrl({ - serviceName: identityField, + serviceName: identityValue, environment: [entity[SERVICE_ENVIRONMENT] || undefined].flat()[0], }); } }, [entity, assetDetailsLocator, serviceOverviewLocator]); return ( - + - {entity[ENTITY_DISPLAY_NAME]} + + {entity[ENTITY_DISPLAY_NAME]} +