From 4b3179c1367faf62a8fc7d1a65d4f2d6c90ba242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:35:19 +0100 Subject: [PATCH] [Inventory] List k8s entities in the grid (#197292) closes https://github.com/elastic/kibana/issues/196155 Blocked by https://github.com/elastic/kibana/pull/196916 (K8s entities alias patterns don't exist yet.) ``` node scripts/synthtrace many_entities.ts --clean --live node scripts/synthtrace k8s_entities.ts --clean --live ``` https://github.com/user-attachments/assets/5861ebc7-8386-4a4b-a68b-50adc5244d43 (cherry picked from commit c6f41783bf853f2828787cc4c48a96511fec2eec) # Conflicts: # x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx --- .../src/scenarios/many_entities.ts | 185 ++++++++++++++++++ .../inventory/common/entities.ts | 58 +----- .../inventory/common/entitites.test.ts | 48 +---- ...parse_identity_field_values_to_kql.test.ts | 23 +-- .../inventory/e2e/cypress/e2e/home.cy.ts | 6 +- .../alerts_badge/alerts_badge.test.tsx | 8 +- .../entities_grid/entities_grid.stories.tsx | 5 +- .../entity_name/entity_name.test.tsx | 14 +- .../public/components/entities_grid/index.tsx | 7 +- .../entities_grid/mock/entities_mock.ts | 13 +- .../public/components/entity_icon/index.tsx | 62 +++--- .../components/search_bar/discover_button.tsx | 4 +- .../search_bar/entity_types_controls.tsx | 12 +- .../public/components/search_bar/index.tsx | 3 +- .../index.tsx | 3 +- .../public/pages/inventory_page/index.tsx | 4 +- .../utils/get_entity_type_label.test.ts | 31 --- .../public/utils/get_entity_type_label.ts | 30 --- .../routes/entities/get_entity_types.ts | 9 +- .../entities/get_identify_fields.test.ts | 13 +- .../get_identity_fields_per_entity_type.ts | 4 +- .../routes/entities/get_latest_entities.ts | 36 ++-- .../entities/get_latest_entities_alerts.ts | 8 +- .../server/routes/entities/query_helper.ts | 12 +- .../inventory/server/routes/entities/route.ts | 14 +- .../server/routes/has_data/get_has_data.ts | 8 +- 26 files changed, 329 insertions(+), 291 deletions(-) create mode 100644 packages/kbn-apm-synthtrace/src/scenarios/many_entities.ts delete mode 100644 x-pack/plugins/observability_solution/inventory/public/utils/get_entity_type_label.test.ts delete mode 100644 x-pack/plugins/observability_solution/inventory/public/utils/get_entity_type_label.ts diff --git a/packages/kbn-apm-synthtrace/src/scenarios/many_entities.ts b/packages/kbn-apm-synthtrace/src/scenarios/many_entities.ts new file mode 100644 index 0000000000000..8b0d2afa5a971 --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/scenarios/many_entities.ts @@ -0,0 +1,185 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { EntityFields, entities, generateShortId } from '@kbn/apm-synthtrace-client'; +import { Schema } from '@kbn/apm-synthtrace-client/src/lib/entities'; +import { Scenario } from '../cli/scenario'; +import { withClient } from '../lib/utils/with_client'; + +const CLUSTER_NAME = 'cluster_foo'; + +const CLUSTER_ENTITY_ID = generateShortId(); +const POD_ENTITY_ID = generateShortId(); +const POD_UID = generateShortId(); +const REPLICA_SET_ENTITY_ID = generateShortId(); +const REPLICA_SET_UID = generateShortId(); +const DEPLOYMENT_ENTITY_ID = generateShortId(); +const DEPLOYMENT_UID = generateShortId(); +const STATEFUL_SET_ENTITY_ID = generateShortId(); +const STATEFUL_SET_UID = generateShortId(); +const DAEMON_SET_ENTITY_ID = generateShortId(); +const DAEMON_SET_UID = generateShortId(); +const JOB_SET_ENTITY_ID = generateShortId(); +const JOB_SET_UID = generateShortId(); +const CRON_JOB_ENTITY_ID = generateShortId(); +const CRON_JOB_UID = generateShortId(); +const NODE_ENTITY_ID = generateShortId(); +const NODE_UID = generateShortId(); +const SYNTH_JAVA_TRACE_ENTITY_ID = generateShortId(); +const SYNTH_HOST_FOO_LOGS_ENTITY_ID = generateShortId(); +const SYNTH_CONTAINER_FOO_LOGS_ENTITY_ID = generateShortId(); + +const scenario: Scenario> = async (runOptions) => { + const { logger } = runOptions; + + return { + bootstrap: async ({ entitiesKibanaClient }) => { + await entitiesKibanaClient.installEntityIndexPatterns(); + }, + generate: ({ range, clients: { entitiesEsClient } }) => { + const rangeInterval = range.interval('1m').rate(1); + const getK8sEntitiesEvents = (schema: Schema) => + rangeInterval.generator((timestamp) => { + return [ + entities.k8s + .k8sClusterJobEntity({ + schema, + name: CLUSTER_NAME, + entityId: CLUSTER_ENTITY_ID, + }) + .timestamp(timestamp), + entities.k8s + .k8sPodEntity({ + schema, + clusterName: CLUSTER_NAME, + name: 'pod_foo', + uid: POD_UID, + entityId: POD_ENTITY_ID, + }) + .timestamp(timestamp), + entities.k8s + .k8sReplicaSetEntity({ + clusterName: CLUSTER_NAME, + name: 'replica_set_foo', + schema, + uid: REPLICA_SET_UID, + entityId: REPLICA_SET_ENTITY_ID, + }) + .timestamp(timestamp), + entities.k8s + .k8sDeploymentEntity({ + clusterName: CLUSTER_NAME, + name: 'deployment_foo', + schema, + uid: DEPLOYMENT_UID, + entityId: DEPLOYMENT_ENTITY_ID, + }) + .timestamp(timestamp), + entities.k8s + .k8sStatefulSetEntity({ + clusterName: CLUSTER_NAME, + name: 'stateful_set_foo', + schema, + uid: STATEFUL_SET_UID, + entityId: STATEFUL_SET_ENTITY_ID, + }) + .timestamp(timestamp), + entities.k8s + .k8sDaemonSetEntity({ + clusterName: CLUSTER_NAME, + name: 'daemon_set_foo', + schema, + uid: DAEMON_SET_UID, + entityId: DAEMON_SET_ENTITY_ID, + }) + .timestamp(timestamp), + entities.k8s + .k8sJobSetEntity({ + clusterName: CLUSTER_NAME, + name: 'job_set_foo', + schema, + uid: JOB_SET_UID, + entityId: JOB_SET_ENTITY_ID, + }) + .timestamp(timestamp), + entities.k8s + .k8sCronJobEntity({ + clusterName: CLUSTER_NAME, + name: 'cron_job_foo', + schema, + uid: CRON_JOB_UID, + entityId: CRON_JOB_ENTITY_ID, + }) + .timestamp(timestamp), + entities.k8s + .k8sNodeEntity({ + clusterName: CLUSTER_NAME, + name: 'node_job_foo', + schema, + uid: NODE_UID, + entityId: NODE_ENTITY_ID, + }) + .timestamp(timestamp), + entities.k8s + .k8sContainerEntity({ + id: '123', + schema, + entityId: NODE_ENTITY_ID, + }) + .timestamp(timestamp), + ]; + }); + + const ecsEntities = getK8sEntitiesEvents('ecs'); + const otelEntities = getK8sEntitiesEvents('semconv'); + const synthJavaTraces = entities.serviceEntity({ + serviceName: 'synth_java', + agentName: ['java'], + dataStreamType: ['traces'], + environment: 'production', + entityId: SYNTH_JAVA_TRACE_ENTITY_ID, + }); + const synthHostFooLogs = entities.hostEntity({ + hostName: 'synth_host_foo', + agentName: ['macbook'], + dataStreamType: ['logs'], + entityId: SYNTH_HOST_FOO_LOGS_ENTITY_ID, + }); + const synthContainerFooLogs = entities.containerEntity({ + containerId: 'synth_container_foo', + agentName: ['macbook'], + dataStreamType: ['logs'], + entityId: SYNTH_CONTAINER_FOO_LOGS_ENTITY_ID, + }); + + const otherEvents = rangeInterval.generator((timestamp) => [ + synthJavaTraces.timestamp(timestamp), + synthHostFooLogs.timestamp(timestamp), + synthContainerFooLogs.timestamp(timestamp), + ]); + + return [ + withClient( + entitiesEsClient, + logger.perf('generating_entities_k8s_ecs_events', () => ecsEntities) + ), + withClient( + entitiesEsClient, + logger.perf('generating_entities_k8s_otel_events', () => otelEntities) + ), + withClient( + entitiesEsClient, + logger.perf('generating_entities_other_events', () => otherEvents) + ), + ]; + }, + }; +}; + +export default scenario; diff --git a/x-pack/plugins/observability_solution/inventory/common/entities.ts b/x-pack/plugins/observability_solution/inventory/common/entities.ts index 7df71559aa97a..d8a056074e339 100644 --- a/x-pack/plugins/observability_solution/inventory/common/entities.ts +++ b/x-pack/plugins/observability_solution/inventory/common/entities.ts @@ -6,12 +6,6 @@ */ import { ENTITY_LATEST, entitiesAliasPattern } from '@kbn/entities-schema'; import { - HOST_NAME, - SERVICE_ENVIRONMENT, - SERVICE_NAME, - AGENT_NAME, - CLOUD_PROVIDER, - CONTAINER_ID, ENTITY_DEFINITION_ID, ENTITY_DISPLAY_NAME, ENTITY_ID, @@ -22,12 +16,6 @@ import { import { isRight } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; -export const entityTypeRt = t.union([ - t.literal('service'), - t.literal('host'), - t.literal('container'), -]); - export const entityColumnIdsRt = t.union([ t.literal(ENTITY_DISPLAY_NAME), t.literal(ENTITY_LAST_SEEN), @@ -37,8 +25,6 @@ export const entityColumnIdsRt = t.union([ export type EntityColumnIds = t.TypeOf; -export type EntityType = t.TypeOf; - export const defaultEntitySortField: EntityColumnIds = 'alertsCount'; export const MAX_NUMBER_OF_ENTITIES = 500; @@ -48,20 +34,8 @@ export const ENTITIES_LATEST_ALIAS = entitiesAliasPattern({ dataset: ENTITY_LATEST, }); -const BUILTIN_SERVICES_FROM_ECS_DATA = 'builtin_services_from_ecs_data'; -const BUILTIN_HOSTS_FROM_ECS_DATA = 'builtin_hosts_from_ecs_data'; -const BUILTIN_CONTAINERS_FROM_ECS_DATA = 'builtin_containers_from_ecs_data'; - -export const defaultEntityDefinitions = [ - BUILTIN_SERVICES_FROM_ECS_DATA, - BUILTIN_HOSTS_FROM_ECS_DATA, - BUILTIN_CONTAINERS_FROM_ECS_DATA, -]; - -export const defaultEntityTypes: EntityType[] = ['service', 'host', 'container']; - -const entityArrayRt = t.array(entityTypeRt); -export const entityTypesRt = new t.Type( +const entityArrayRt = t.array(t.string); +export const entityTypesRt = new t.Type( 'entityTypesRt', entityArrayRt.is, (input, context) => { @@ -83,37 +57,13 @@ export const entityTypesRt = new t.Type( (arr) => arr.join() ); -interface BaseEntity { +export interface Entity { [ENTITY_LAST_SEEN]: string; [ENTITY_ID]: string; - [ENTITY_TYPE]: EntityType; + [ENTITY_TYPE]: string; [ENTITY_DISPLAY_NAME]: string; [ENTITY_DEFINITION_ID]: string; [ENTITY_IDENTITY_FIELDS]: string | string[]; alertsCount?: number; [key: string]: any; } - -/** - * These types are based on service, host and container from the built in definition. - */ -export interface ServiceEntity extends BaseEntity { - [ENTITY_TYPE]: 'service'; - [SERVICE_NAME]: string; - [SERVICE_ENVIRONMENT]?: string | string[] | null; - [AGENT_NAME]: string | string[] | null; -} - -export interface HostEntity extends BaseEntity { - [ENTITY_TYPE]: 'host'; - [HOST_NAME]: string; - [CLOUD_PROVIDER]: string | string[] | null; -} - -export interface ContainerEntity extends BaseEntity { - [ENTITY_TYPE]: 'container'; - [CONTAINER_ID]: string; - [CLOUD_PROVIDER]: string | string[] | null; -} - -export type Entity = ServiceEntity | HostEntity | ContainerEntity; diff --git a/x-pack/plugins/observability_solution/inventory/common/entitites.test.ts b/x-pack/plugins/observability_solution/inventory/common/entitites.test.ts index 38da7beab8d4f..c923bda530746 100644 --- a/x-pack/plugins/observability_solution/inventory/common/entitites.test.ts +++ b/x-pack/plugins/observability_solution/inventory/common/entitites.test.ts @@ -5,7 +5,7 @@ * 2.0. */ import { isLeft, isRight } from 'fp-ts/lib/Either'; -import { type EntityType, entityTypesRt } from './entities'; +import { entityTypesRt } from './entities'; const validate = (input: unknown) => entityTypesRt.decode(input); @@ -28,36 +28,12 @@ describe('entityTypesRt codec', () => { } }); - it('should fail validation when the string contains invalid entity types', () => { - const input = 'service,invalidType,host'; - const result = validate(input); - expect(isLeft(result)).toBe(true); - }); - - it('should fail validation when the array contains invalid entity types', () => { - const input = ['service', 'invalidType', 'host']; - const result = validate(input); - expect(isLeft(result)).toBe(true); - }); - it('should fail validation when input is not a string or array', () => { const input = 123; const result = validate(input); expect(isLeft(result)).toBe(true); }); - it('should fail validation when the array contains non-string elements', () => { - const input = ['service', 123, 'host']; - const result = validate(input); - expect(isLeft(result)).toBe(true); - }); - - it('should fail validation an empty string', () => { - const input = ''; - const result = validate(input); - expect(isLeft(result)).toBe(true); - }); - it('should validate an empty array as valid', () => { const input: unknown[] = []; const result = validate(input); @@ -67,32 +43,14 @@ describe('entityTypesRt codec', () => { } }); - it('should fail validation when the string contains only commas', () => { - const input = ',,,'; - const result = validate(input); - expect(isLeft(result)).toBe(true); - }); - - it('should fail validation for partial valid entities in a string', () => { - const input = 'service,invalidType'; - const result = validate(input); - expect(isLeft(result)).toBe(true); - }); - - it('should fail validation for partial valid entities in an array', () => { - const input = ['service', 'invalidType']; - const result = validate(input); - expect(isLeft(result)).toBe(true); - }); - it('should serialize a valid array back to a string', () => { - const input: EntityType[] = ['service', 'host']; + const input = ['service', 'host']; const serialized = entityTypesRt.encode(input); expect(serialized).toBe('service,host'); }); it('should serialize an empty array back to an empty string', () => { - const input: EntityType[] = []; + const input: string[] = []; const serialized = entityTypesRt.encode(input); expect(serialized).toBe(''); }); diff --git a/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.test.ts b/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.test.ts index b8d6219e6cd46..8703e995b4446 100644 --- a/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.test.ts +++ b/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.test.ts @@ -9,9 +9,10 @@ import { ENTITY_DEFINITION_ID, ENTITY_DISPLAY_NAME, ENTITY_ID, + ENTITY_IDENTITY_FIELDS, ENTITY_LAST_SEEN, } from '@kbn/observability-shared-plugin/common'; -import { HostEntity, ServiceEntity } from '../entities'; +import type { Entity } from '../entities'; import { parseIdentityFieldValuesToKql } from './parse_identity_field_values_to_kql'; const commonEntityFields = { @@ -24,9 +25,9 @@ const commonEntityFields = { describe('parseIdentityFieldValuesToKql', () => { it('should return the value when identityFields is a single string', () => { - const entity: ServiceEntity = { + const entity: Entity = { 'agent.name': 'node', - 'entity.identity_fields': 'service.name', + [ENTITY_IDENTITY_FIELDS]: 'service.name', 'service.name': 'my-service', 'entity.type': 'service', ...commonEntityFields, @@ -37,9 +38,9 @@ describe('parseIdentityFieldValuesToKql', () => { }); it('should return values when identityFields is an array of strings', () => { - const entity: ServiceEntity = { + const entity: Entity = { 'agent.name': 'node', - 'entity.identity_fields': ['service.name', 'service.environment'], + [ENTITY_IDENTITY_FIELDS]: ['service.name', 'service.environment'], 'service.name': 'my-service', 'entity.type': 'service', 'service.environment': 'staging', @@ -51,9 +52,9 @@ describe('parseIdentityFieldValuesToKql', () => { }); it('should return an empty string if identityFields is empty string', () => { - const entity: ServiceEntity = { + const entity: Entity = { 'agent.name': 'node', - 'entity.identity_fields': '', + [ENTITY_IDENTITY_FIELDS]: '', 'service.name': 'my-service', 'entity.type': 'service', ...commonEntityFields, @@ -63,9 +64,9 @@ describe('parseIdentityFieldValuesToKql', () => { expect(result).toEqual(''); }); it('should return an empty array if identityFields is empty array', () => { - const entity: ServiceEntity = { + const entity: Entity = { 'agent.name': 'node', - 'entity.identity_fields': [], + [ENTITY_IDENTITY_FIELDS]: [], 'service.name': 'my-service', 'entity.type': 'service', ...commonEntityFields, @@ -76,8 +77,8 @@ describe('parseIdentityFieldValuesToKql', () => { }); it('should ignore fields that are not present in the entity', () => { - const entity: HostEntity = { - 'entity.identity_fields': ['host.name', 'foo.bar'], + const entity: Entity = { + [ENTITY_IDENTITY_FIELDS]: ['host.name', 'foo.bar'], 'host.name': 'my-host', 'entity.type': 'host', 'cloud.provider': null, diff --git a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts index 16d14446ef240..c18f8866475ab 100644 --- a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts +++ b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts @@ -66,11 +66,11 @@ describe('Home page', () => { cy.visitKibana('/app/inventory'); cy.wait('@getEEMStatus'); cy.contains('server1'); - cy.contains('Host'); + cy.contains('host'); cy.contains('synth-node-trace-logs'); - cy.contains('Service'); + cy.contains('service'); cy.contains('foo'); - cy.contains('Container'); + cy.contains('container'); }); it('Navigates to apm when clicking on a service type entity', () => { diff --git a/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx b/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx index fc73e490d4d05..60124e7813bc4 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx @@ -9,7 +9,7 @@ import { type KibanaReactContextValue } from '@kbn/kibana-react-plugin/public'; import { render, screen } from '@testing-library/react'; import { AlertsBadge } from './alerts_badge'; import * as useKibana from '../../hooks/use_kibana'; -import { HostEntity, ServiceEntity } from '../../../common/entities'; +import type { Entity } from '../../../common/entities'; describe('AlertsBadge', () => { jest.spyOn(useKibana, 'useKibana').mockReturnValue({ @@ -27,7 +27,7 @@ describe('AlertsBadge', () => { }); it('render alerts badge for a host entity', () => { - const entity: HostEntity = { + const entity: Entity = { 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'host', @@ -45,7 +45,7 @@ describe('AlertsBadge', () => { expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.textContent).toEqual('1'); }); it('render alerts badge for a service entity', () => { - const entity: ServiceEntity = { + const entity: Entity = { 'entity.last_seen_timestamp': 'foo', 'agent.name': 'node', 'entity.id': '1', @@ -64,7 +64,7 @@ describe('AlertsBadge', () => { expect(screen.queryByTestId('inventoryAlertsBadgeLink')?.textContent).toEqual('5'); }); it('render alerts badge for a service entity with multiple identity fields', () => { - const entity: ServiceEntity = { + const entity: Entity = { 'entity.last_seen_timestamp': 'foo', 'agent.name': 'node', 'entity.id': '1', diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entities_grid.stories.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entities_grid.stories.tsx index a89781ad2742a..047c2e73d0d3e 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entities_grid.stories.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entities_grid.stories.tsx @@ -12,14 +12,13 @@ import React, { useMemo, useState } from 'react'; import { ENTITY_LAST_SEEN, ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; import { useArgs } from '@storybook/addons'; import { EntitiesGrid } from '.'; -import { EntityType } from '../../../common/entities'; import { entitiesMock } from './mock/entities_mock'; interface EntityGridStoriesArgs { - entityType?: EntityType; + entityType?: string; } -const entityTypeOptions: EntityType[] = ['host', 'container', 'service']; +const entityTypeOptions = ['host', 'container', 'service']; const stories: Meta = { title: 'app/inventory/entities_grid', 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 index 865e185eaa945..2e4f0c319edfc 100644 --- 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 @@ -8,7 +8,7 @@ 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 type { Entity } 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'; @@ -40,7 +40,7 @@ describe('EntityName', () => { }); it('returns host link', () => { - const entity: HostEntity = { + const entity: Entity = { 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'host', @@ -58,7 +58,7 @@ describe('EntityName', () => { }); it('returns container link', () => { - const entity: ContainerEntity = { + const entity: Entity = { 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'container', @@ -76,7 +76,7 @@ describe('EntityName', () => { }); it('returns service link without environment', () => { - const entity: ServiceEntity = { + const entity: Entity = { 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'service', @@ -94,7 +94,7 @@ describe('EntityName', () => { }); it('returns service link with environment', () => { - const entity: ServiceEntity = { + const entity: Entity = { 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'service', @@ -113,7 +113,7 @@ describe('EntityName', () => { }); it('returns service link with first environment when it is an array', () => { - const entity: ServiceEntity = { + const entity: Entity = { 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'service', @@ -132,7 +132,7 @@ describe('EntityName', () => { }); it('returns service link identity fields is an array', () => { - const entity: ServiceEntity = { + const entity: Entity = { 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'service', diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx index a5a6afc1ac20d..e3c0d24837f91 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx @@ -20,13 +20,12 @@ import { ENTITY_LAST_SEEN, ENTITY_TYPE, } from '@kbn/observability-shared-plugin/common'; -import { EntityColumnIds, EntityType } from '../../../common/entities'; +import { EntityColumnIds } from '../../../common/entities'; import { APIReturnType } from '../../api'; import { BadgeFilterWithPopover } from '../badge_filter_with_popover'; import { getColumns } from './grid_columns'; import { AlertsBadge } from '../alerts_badge/alerts_badge'; import { EntityName } from './entity_name'; -import { getEntityTypeLabel } from '../../utils/get_entity_type_label'; type InventoryEntitiesAPIReturnType = APIReturnType<'GET /internal/inventory/entities'>; type LatestEntities = InventoryEntitiesAPIReturnType['entities']; @@ -39,7 +38,7 @@ interface Props { pageIndex: number; onChangeSort: (sorting: EuiDataGridSorting['columns'][0]) => void; onChangePage: (nextPage: number) => void; - onFilterByType: (entityType: EntityType) => void; + onFilterByType: (entityType: string) => void; } const PAGE_SIZE = 20; @@ -96,7 +95,7 @@ export function EntitiesGrid({ onFilterByType(entityType)} /> ); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/mock/entities_mock.ts b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/mock/entities_mock.ts index 8a34a9f68c7b6..3b7e7afcadb99 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/mock/entities_mock.ts +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/mock/entities_mock.ts @@ -11,8 +11,10 @@ import { ENTITY_TYPE, ENTITY_ID, ENTITY_LAST_SEEN, + AGENT_NAME, + CLOUD_PROVIDER, } from '@kbn/observability-shared-plugin/common'; -import { Entity, EntityType } from '../../../../common/entities'; +import { Entity } from '../../../../common/entities'; const idGenerator = () => { let id = 0; @@ -31,11 +33,12 @@ function generateRandomTimestamp() { return randomDate.toISOString(); } -const getEntity = (entityType: EntityType) => ({ +const getEntity = (entityType: string, customFields: Record = {}) => ({ [ENTITY_LAST_SEEN]: generateRandomTimestamp(), [ENTITY_TYPE]: entityType, [ENTITY_DISPLAY_NAME]: faker.person.fullName(), [ENTITY_ID]: generateId(), + ...customFields, }); const alertsMock = [ @@ -58,9 +61,11 @@ const alertsMock = [ }, ]; -const hostsMock = Array.from({ length: 20 }, () => getEntity('host')); +const hostsMock = Array.from({ length: 20 }, () => getEntity('host', { [CLOUD_PROVIDER]: 'gcp' })); const containersMock = Array.from({ length: 20 }, () => getEntity('container')); -const servicesMock = Array.from({ length: 20 }, () => getEntity('service')); +const servicesMock = Array.from({ length: 20 }, () => + getEntity('service', { [AGENT_NAME]: 'java' }) +); export const entitiesMock = [ ...alertsMock, diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_icon/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_icon/index.tsx index c88358405bf12..a62f0026ddfa0 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entity_icon/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entity_icon/index.tsx @@ -27,35 +27,37 @@ export function EntityIcon({ entity }: EntityIconProps) { const entityType = entity[ENTITY_TYPE]; const defaultIconSize = euiThemeVars.euiSizeL; - switch (entityType) { - case 'host': - case 'container': { - const cloudProvider = getSingleValue( - entity[CLOUD_PROVIDER] as NotNullableCloudProvider | NotNullableCloudProvider[] - ); - return ( - - - - - - ); - } - case 'service': { - const agentName = getSingleValue(entity[AGENT_NAME] as AgentName | AgentName[]); - return ; - } - default: - // Return an empty EuiIcon instead of null to maintain UI alignment across all EntityIcon usages - return ; + if (entityType === 'host' || entityType === 'container') { + const cloudProvider = getSingleValue( + entity[CLOUD_PROVIDER] as NotNullableCloudProvider | NotNullableCloudProvider[] + ); + return ( + + + + + + ); } + + if (entityType === 'service') { + const agentName = getSingleValue(entity[AGENT_NAME] as AgentName | AgentName[]); + return ; + } + + if (entityType.startsWith('kubernetes')) { + return ; + } + + // Return an empty EuiIcon instead of null to maintain UI alignment across all EntityIcon usages + return ; } diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx index ee3014e990b0b..dee05d6f7cdd0 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx @@ -17,7 +17,7 @@ import { ENTITY_LAST_SEEN, ENTITY_TYPE, } from '@kbn/observability-shared-plugin/common'; -import { defaultEntityDefinitions, EntityColumnIds } from '../../../common/entities'; +import { ENTITIES_LATEST_ALIAS, EntityColumnIds } from '../../../common/entities'; import { useInventoryParams } from '../../hooks/use_inventory_params'; import { useKibana } from '../../hooks/use_kibana'; @@ -43,7 +43,7 @@ export function DiscoverButton({ dataView }: { dataView: DataView }) { if (entityDefinitionField) { const entityDefinitionFilter = buildPhrasesFilter( entityDefinitionField!, - defaultEntityDefinitions, + [ENTITIES_LATEST_ALIAS], dataView ); filters.push(entityDefinitionFilter); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/entity_types_controls.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/entity_types_controls.tsx index 6e55408d28e8c..e2d9dba2709f1 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/entity_types_controls.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/entity_types_controls.tsx @@ -8,19 +8,17 @@ import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { EntityType } from '../../../common/entities'; import { useInventoryAbortableAsync } from '../../hooks/use_inventory_abortable_async'; import { useInventoryParams } from '../../hooks/use_inventory_params'; import { useKibana } from '../../hooks/use_kibana'; -import { getEntityTypeLabel } from '../../utils/get_entity_type_label'; interface Props { - onChange: (entityTypes: EntityType[]) => void; + onChange: (entityTypes: string[]) => void; } -const toComboBoxOption = (entityType: EntityType): EuiComboBoxOptionOption => ({ +const toComboBoxOption = (entityType: string): EuiComboBoxOptionOption => ({ key: entityType, - label: getEntityTypeLabel(entityType), + label: entityType, 'data-test-subj': `entityTypesFilter${entityType}Option`, }); @@ -44,7 +42,7 @@ export function EntityTypesControls({ onChange }: Props) { const selectedOptions = entityTypes.map(toComboBoxOption); return ( - + { - onChange(newOptions.map((option) => option.key as EntityType)); + onChange(newOptions.map((option) => option.key).filter((key): key is string => !!key)); }} isClearable /> diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx index cdf8094fe7c51..4e945dd9a1cad 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx @@ -10,7 +10,6 @@ import deepEqual from 'fast-deep-equal'; import React, { useCallback, useEffect } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Query } from '@kbn/es-query'; -import { EntityType } from '../../../common/entities'; import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; import { useAdHocInventoryDataView } from '../../hooks/use_adhoc_inventory_data_view'; import { useInventoryParams } from '../../hooks/use_inventory_params'; @@ -84,7 +83,7 @@ export function SearchBar() { ); const handleEntityTypesChange = useCallback( - (nextEntityTypes: EntityType[]) => { + (nextEntityTypes: string[]) => { searchBarContentSubject$.next({ kuery, entityTypes: nextEntityTypes, refresh: false }); registerEntityTypeFilteredEvent({ filterEntityTypes: nextEntityTypes, filterKuery: kuery }); }, diff --git a/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx b/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx index fc494651d6f3f..fbb51c4f0d7e7 100644 --- a/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx @@ -6,12 +6,11 @@ */ import React, { createContext, useContext, type ReactChild } from 'react'; import { Subject } from 'rxjs'; -import { EntityType } from '../../../common/entities'; interface InventorySearchBarContextType { searchBarContentSubject$: Subject<{ kuery?: string; - entityTypes?: EntityType[]; + entityTypes?: string[]; refresh: boolean; }>; } diff --git a/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx b/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx index 965434eeac6d1..00dfb9e24d2dd 100644 --- a/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx @@ -7,7 +7,7 @@ import { EuiDataGridSorting } from '@elastic/eui'; import React from 'react'; import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { EntityColumnIds, EntityType } from '../../../common/entities'; +import { EntityColumnIds } from '../../../common/entities'; import { EntitiesGrid } from '../../components/entities_grid'; import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; import { useInventoryAbortableAsync } from '../../hooks/use_inventory_abortable_async'; @@ -82,7 +82,7 @@ export function InventoryPage() { }); } - function handleTypeFilter(entityType: EntityType) { + function handleTypeFilter(entityType: string) { inventoryRoute.push('/', { path: {}, query: { diff --git a/x-pack/plugins/observability_solution/inventory/public/utils/get_entity_type_label.test.ts b/x-pack/plugins/observability_solution/inventory/public/utils/get_entity_type_label.test.ts deleted file mode 100644 index e31a169d5d9fa..0000000000000 --- a/x-pack/plugins/observability_solution/inventory/public/utils/get_entity_type_label.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EntityType } from '../../common/entities'; -import { getEntityTypeLabel } from './get_entity_type_label'; - -describe('getEntityTypeLabel', () => { - it('should return "Service" for the "service" entityType', () => { - const label = getEntityTypeLabel('service'); - expect(label).toBe('Service'); - }); - - it('should return "Container" for the "container" entityType', () => { - const label = getEntityTypeLabel('container'); - expect(label).toBe('Container'); - }); - - it('should return "Host" for the "host" entityType', () => { - const label = getEntityTypeLabel('host'); - expect(label).toBe('Host'); - }); - - it('should return "N/A" for an unknown entityType', () => { - const label = getEntityTypeLabel('foo' as EntityType); - expect(label).toBe('N/A'); - }); -}); diff --git a/x-pack/plugins/observability_solution/inventory/public/utils/get_entity_type_label.ts b/x-pack/plugins/observability_solution/inventory/public/utils/get_entity_type_label.ts deleted file mode 100644 index 907ea70f0f0c6..0000000000000 --- a/x-pack/plugins/observability_solution/inventory/public/utils/get_entity_type_label.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { EntityType } from '../../common/entities'; - -export function getEntityTypeLabel(entityType: EntityType) { - switch (entityType) { - case 'service': - return i18n.translate('xpack.inventory.entityType.serviceLabel', { - defaultMessage: 'Service', - }); - case 'container': - return i18n.translate('xpack.inventory.entityType.containerLabel', { - defaultMessage: 'Container', - }); - case 'host': - return i18n.translate('xpack.inventory.entityType.hostLabel', { - defaultMessage: 'Host', - }); - default: - return i18n.translate('xpack.inventory.entityType.naLabel', { - defaultMessage: 'N/A', - }); - } -} diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_types.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_types.ts index 8db185f7b619f..2dfc9b8ccfdf3 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_types.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_types.ts @@ -7,8 +7,8 @@ import { type ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client'; import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; -import { ENTITIES_LATEST_ALIAS, EntityType } from '../../../common/entities'; -import { getEntityDefinitionIdWhereClause, getEntityTypesWhereClause } from './query_helper'; +import { ENTITIES_LATEST_ALIAS } from '../../../common/entities'; +import { getBuiltinEntityDefinitionIdESQLWhereClause } from './query_helper'; export async function getEntityTypes({ inventoryEsClient, @@ -17,11 +17,10 @@ export async function getEntityTypes({ }) { const entityTypesEsqlResponse = await inventoryEsClient.esql('get_entity_types', { query: `FROM ${ENTITIES_LATEST_ALIAS} - | ${getEntityTypesWhereClause()} - | ${getEntityDefinitionIdWhereClause()} + | ${getBuiltinEntityDefinitionIdESQLWhereClause()} | STATS count = COUNT(${ENTITY_TYPE}) BY ${ENTITY_TYPE} `, }); - return entityTypesEsqlResponse.values.map(([_, val]) => val as EntityType); + return entityTypesEsqlResponse.values.map(([_, val]) => val as string); } diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identify_fields.test.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identify_fields.test.ts index 0e6c663a00890..ffd5ba9c6f855 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identify_fields.test.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identify_fields.test.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { ContainerEntity, HostEntity, ServiceEntity } from '../../../common/entities'; +import type { Entity } from '../../../common/entities'; import { ENTITY_DEFINITION_ID, ENTITY_DISPLAY_NAME, ENTITY_ID, + ENTITY_IDENTITY_FIELDS, ENTITY_LAST_SEEN, } from '@kbn/observability-shared-plugin/common'; import { getIdentityFieldsPerEntityType } from './get_identity_fields_per_entity_type'; @@ -27,7 +28,7 @@ describe('getIdentityFields', () => { expect(result.size).toBe(0); }); it('should return a Map with unique entity types and their respective identity fields', () => { - const serviceEntity: ServiceEntity = { + const serviceEntity: Entity = { 'agent.name': 'node', 'entity.identity_fields': ['service.name', 'service.environment'], 'service.name': 'my-service', @@ -35,16 +36,16 @@ describe('getIdentityFields', () => { ...commonEntityFields, }; - const hostEntity: HostEntity = { - 'entity.identity_fields': ['host.name'], + const hostEntity: Entity = { + [ENTITY_IDENTITY_FIELDS]: ['host.name'], 'host.name': 'my-host', 'entity.type': 'host', 'cloud.provider': null, ...commonEntityFields, }; - const containerEntity: ContainerEntity = { - 'entity.identity_fields': 'container.id', + const containerEntity: Entity = { + [ENTITY_IDENTITY_FIELDS]: 'container.id', 'host.name': 'my-host', 'entity.type': 'container', 'cloud.provider': null, diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identity_fields_per_entity_type.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identity_fields_per_entity_type.ts index 0ca4eb9d21239..f54dc8a7f121f 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identity_fields_per_entity_type.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identity_fields_per_entity_type.ts @@ -6,9 +6,9 @@ */ import { ENTITY_IDENTITY_FIELDS, ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; -import { Entity, EntityType } from '../../../common/entities'; +import { Entity } from '../../../common/entities'; -export type IdentityFieldsPerEntityType = Map; +export type IdentityFieldsPerEntityType = Map; export const getIdentityFieldsPerEntityType = (entities: Entity[]) => { const identityFieldsPerEntityType: IdentityFieldsPerEntityType = new Map(); diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts index e500ce32c3cef..4fb3b930beace 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts @@ -8,15 +8,15 @@ import { type ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client'; import { kqlQuery } from '@kbn/observability-utils/es/queries/kql_query'; import { esqlResultToPlainObjects } from '@kbn/observability-utils/es/utils/esql_result_to_plain_objects'; -import { ENTITY_LAST_SEEN } from '@kbn/observability-shared-plugin/common'; +import { ENTITY_LAST_SEEN, ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; +import type { ScalarValue } from '@elastic/elasticsearch/lib/api/types'; import { ENTITIES_LATEST_ALIAS, MAX_NUMBER_OF_ENTITIES, - type EntityType, type Entity, type EntityColumnIds, } from '../../../common/entities'; -import { getEntityDefinitionIdWhereClause, getEntityTypesWhereClause } from './query_helper'; +import { getBuiltinEntityDefinitionIdESQLWhereClause } from './query_helper'; export async function getLatestEntities({ inventoryEsClient, @@ -28,27 +28,35 @@ export async function getLatestEntities({ inventoryEsClient: ObservabilityElasticsearchClient; sortDirection: 'asc' | 'desc'; sortField: EntityColumnIds; - entityTypes?: EntityType[]; + entityTypes?: string[]; kuery?: string; }) { // alertsCount doesn't exist in entities index. Ignore it and sort by entity.lastSeenTimestamp by default. const entitiesSortField = sortField === 'alertsCount' ? ENTITY_LAST_SEEN : sortField; - const request = { - query: `FROM ${ENTITIES_LATEST_ALIAS} - | ${getEntityTypesWhereClause(entityTypes)} - | ${getEntityDefinitionIdWhereClause()} - | SORT ${entitiesSortField} ${sortDirection} - | LIMIT ${MAX_NUMBER_OF_ENTITIES} - `, + const from = `FROM ${ENTITIES_LATEST_ALIAS}`; + const where: string[] = [getBuiltinEntityDefinitionIdESQLWhereClause()]; + const params: ScalarValue[] = []; + + if (entityTypes) { + where.push(`WHERE ${ENTITY_TYPE} IN (${entityTypes.map(() => '?').join()})`); + params.push(...entityTypes.map((entityType) => entityType)); + } + + const sort = `SORT ${entitiesSortField} ${sortDirection}`; + const limit = `LIMIT ${MAX_NUMBER_OF_ENTITIES}`; + + const query = [from, ...where, sort, limit].join(' | '); + + const latestEntitiesEsqlResponse = await inventoryEsClient.esql('get_latest_entities', { + query, filter: { bool: { filter: [...kqlQuery(kuery)], }, }, - }; - - const latestEntitiesEsqlResponse = await inventoryEsClient.esql('get_latest_entities', request); + params, + }); return esqlResultToPlainObjects(latestEntitiesEsqlResponse); } diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities_alerts.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities_alerts.ts index 4e6ce545a079e..e969f1d537e99 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities_alerts.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities_alerts.ts @@ -7,17 +7,17 @@ import { kqlQuery, termQuery } from '@kbn/observability-plugin/server'; import { ALERT_STATUS, ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils'; +import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; import { AlertsClient } from '../../lib/create_alerts_client.ts/create_alerts_client'; import { getGroupByTermsAgg } from './get_group_by_terms_agg'; import { IdentityFieldsPerEntityType } from './get_identity_fields_per_entity_type'; -import { EntityType } from '../../../common/entities'; interface Bucket { key: Record; doc_count: number; } -type EntityTypeBucketsAggregation = Record; +type EntityTypeBucketsAggregation = Record; export async function getLatestEntitiesAlerts({ alertsClient, @@ -27,7 +27,7 @@ export async function getLatestEntitiesAlerts({ alertsClient: AlertsClient; kuery?: string; identityFieldsPerEntityType: IdentityFieldsPerEntityType; -}): Promise> { +}): Promise> { if (identityFieldsPerEntityType.size === 0) { return []; } @@ -56,7 +56,7 @@ export async function getLatestEntitiesAlerts({ return buckets.map((bucket: Bucket) => ({ alertsCount: bucket.doc_count, - type: entityType, + [ENTITY_TYPE]: entityType, ...bucket.key, })); }); diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/query_helper.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/query_helper.ts index 0a3e97418da92..5a8cd08eaa0d8 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/query_helper.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/query_helper.ts @@ -5,13 +5,7 @@ * 2.0. */ -import { ENTITY_DEFINITION_ID, ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; -import { EntityType, defaultEntityTypes, defaultEntityDefinitions } from '../../../common/entities'; +import { ENTITY_DEFINITION_ID } from '@kbn/observability-shared-plugin/common'; -export const getEntityTypesWhereClause = (entityTypes: EntityType[] = defaultEntityTypes) => - `WHERE ${ENTITY_TYPE} IN (${entityTypes.map((entityType) => `"${entityType}"`).join()})`; - -export const getEntityDefinitionIdWhereClause = () => - `WHERE ${ENTITY_DEFINITION_ID} IN (${[...defaultEntityDefinitions] - .map((buildin) => `"${buildin}"`) - .join()})`; +export const getBuiltinEntityDefinitionIdESQLWhereClause = () => + `WHERE ${ENTITY_DEFINITION_ID} LIKE "builtin_*"`; diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts index eb80f80d02730..67b3803dd98de 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts @@ -10,7 +10,7 @@ import { createObservabilityEsClient } from '@kbn/observability-utils/es/client/ import * as t from 'io-ts'; import { orderBy } from 'lodash'; import { joinByKey } from '@kbn/observability-utils/array/join_by_key'; -import { entityTypeRt, entityColumnIdsRt, Entity } from '../../../common/entities'; +import { entityColumnIdsRt, Entity } from '../../../common/entities'; import { createInventoryServerRoute } from '../create_inventory_server_route'; import { getEntityTypes } from './get_entity_types'; import { getLatestEntities } from './get_latest_entities'; @@ -45,7 +45,7 @@ export const listLatestEntitiesRoute = createInventoryServerRoute({ sortDirection: t.union([t.literal('asc'), t.literal('desc')]), }), t.partial({ - entityTypes: jsonRt.pipe(t.array(entityTypeRt)), + entityTypes: jsonRt.pipe(t.array(t.string)), kuery: t.string, }), ]), @@ -53,7 +53,13 @@ export const listLatestEntitiesRoute = createInventoryServerRoute({ options: { tags: ['access:inventory'], }, - handler: async ({ params, context, logger, plugins, request }) => { + handler: async ({ + params, + context, + logger, + plugins, + request, + }): Promise<{ entities: Entity[] }> => { const coreContext = await context.core; const inventoryEsClient = createObservabilityEsClient({ client: coreContext.elasticsearch.client.asCurrentUser, @@ -85,7 +91,7 @@ export const listLatestEntitiesRoute = createInventoryServerRoute({ const joined = joinByKey( [...latestEntities, ...alerts], [...identityFieldsPerEntityType.values()].flat() - ).filter((entity) => entity['entity.id']); + ).filter((entity) => entity['entity.id']) as Entity[]; return { entities: diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/has_data/get_has_data.ts b/x-pack/plugins/observability_solution/inventory/server/routes/has_data/get_has_data.ts index 465e720938b32..27ba8c0fe46c3 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/has_data/get_has_data.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/has_data/get_has_data.ts @@ -7,10 +7,7 @@ import type { Logger } from '@kbn/core/server'; import { esqlResultToPlainObjects } from '@kbn/observability-utils/es/utils/esql_result_to_plain_objects'; import { type ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client'; -import { - getEntityDefinitionIdWhereClause, - getEntityTypesWhereClause, -} from '../entities/query_helper'; +import { getBuiltinEntityDefinitionIdESQLWhereClause } from '../entities/query_helper'; import { ENTITIES_LATEST_ALIAS } from '../../../common/entities'; export async function getHasData({ @@ -23,8 +20,7 @@ export async function getHasData({ try { const esqlResults = await inventoryEsClient.esql('get_has_data', { query: `FROM ${ENTITIES_LATEST_ALIAS} - | ${getEntityDefinitionIdWhereClause()} - | ${getEntityTypesWhereClause()} + | ${getBuiltinEntityDefinitionIdESQLWhereClause()} | STATS _count = COUNT(*) | LIMIT 1`, });