From 1550c90d3769ff27b0613b8004b9193a888c8236 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Wed, 18 Dec 2024 09:24:04 +0100 Subject: [PATCH] [SecuritySolution] Entity store status page - Add health status to transform (#202523) ## Summary Add the actual transform status to the Entity Store status page. Before, it just showed green whenever the transform was installed. ### How to test it? * Install the Engine * Break the transform by updating it on the stack management page * The status change should be reflected in the engine status page ![Screenshot 2024-12-16 at 11 05 30](https://github.com/user-attachments/assets/a288a037-e1ea-4747-a2bb-266300dad946) --- .../entity_store_data_client.test.ts | 145 ++++++++++++++++++ .../entity_store/entity_store_data_client.ts | 12 +- 2 files changed, 154 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts index 86ac7d136d22..b7cacea3c375 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts @@ -17,6 +17,18 @@ import type { DataViewsService } from '@kbn/data-views-plugin/common'; import type { AppClient } from '../../..'; import type { EntityStoreConfig } from './types'; import { mockGlobalState } from '../../../../public/common/mock'; +import type { EntityDefinition } from '@kbn/entities-schema'; +import { getUnitedEntityDefinition } from './united_entity_definitions'; + +const unitedDefinition = getUnitedEntityDefinition({ + entityType: 'host', + namespace: 'test', + fieldHistoryLength: 10, + indexPatterns: [], + syncDelay: '1m', + frequency: '1m', +}); +const definition: EntityDefinition = unitedDefinition.entityManagerDefinition; describe('EntityStoreDataClient', () => { const mockSavedObjectClient = savedObjectsClientMock.create(); @@ -182,4 +194,137 @@ describe('EntityStoreDataClient', () => { expect(response.records[0]).toEqual(fakeEntityRecord); }); }); + + describe('getComponentFromEntityDefinition', () => { + it('returns installed false if no definition is provided', () => { + const result = dataClient.getComponentFromEntityDefinition('security_host_test', undefined); + expect(result).toEqual([ + { + id: 'security_host_test', + installed: false, + resource: 'entity_definition', + }, + ]); + }); + + it('returns correct components for EntityDefinitionWithState', () => { + const definitionWithState = { + ...definition, + state: { + installed: true, + running: true, + components: { + transforms: [ + { + id: 'transforms_id', + installed: true, + running: true, + }, + ], + ingestPipelines: [ + { + id: 'pipeline-1', + installed: true, + }, + ], + indexTemplates: [ + { + id: 'indexTemplates_id', + installed: true, + }, + ], + }, + }, + }; + + const result = dataClient.getComponentFromEntityDefinition( + 'security_host_test', + definitionWithState + ); + expect(result).toEqual([ + { + id: 'security_host_test', + installed: true, + resource: 'entity_definition', + }, + { + id: 'security_host_test', + resource: 'transform', + installed: true, + health: 'unknown', + errors: undefined, + }, + { + resource: 'ingest_pipeline', + id: 'pipeline-1', + installed: true, + }, + { + id: 'security_host_test', + installed: true, + resource: 'index_template', + }, + ]); + }); + + it('returns empty array for EntityDefinition without state', () => { + const result = dataClient.getComponentFromEntityDefinition('security_host_test', definition); + expect(result).toEqual([]); + }); + + it('handles transform health issues correctly', () => { + const definitionWithState = { + ...definition, + state: { + installed: true, + components: { + transforms: [ + { + installed: true, + stats: { + health: { + status: 'yellow', + issues: [ + { + type: 'issue-type', + issue: 'issue-message', + details: 'issue-details', + count: 1, + }, + ], + }, + }, + }, + ], + ingestPipelines: [], + indexTemplates: [], + }, + }, + }; + + const result = dataClient.getComponentFromEntityDefinition( + 'security_host_test', + definitionWithState + ); + expect(result).toEqual([ + { + id: 'security_host_test', + installed: true, + resource: 'entity_definition', + }, + { + id: 'security_host_test', + resource: 'transform', + installed: true, + health: 'yellow', + errors: [ + { + title: 'issue-message', + message: 'issue-details', + }, + ], + }, + ]); + }); + }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts index c18dc1863a8d..faf2edc15419 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts @@ -15,7 +15,7 @@ import type { AnalyticsServiceSetup, } from '@kbn/core/server'; import { EntityClient } from '@kbn/entityManager-plugin/server/lib/entity_client'; -import type { SortOrder } from '@elastic/elasticsearch/lib/api/types'; +import type { HealthStatus, SortOrder } from '@elastic/elasticsearch/lib/api/types'; import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; import type { DataViewsService } from '@kbn/data-views-plugin/common'; import { isEqual } from 'lodash/fp'; @@ -465,7 +465,7 @@ export class EntityStoreDataClient { public getComponentFromEntityDefinition( id: string, - definition: EntityDefinitionWithState | EntityDefinition + definition: EntityDefinitionWithState | EntityDefinition | undefined ): EngineComponentStatus[] { if (!definition) { return [ @@ -478,16 +478,22 @@ export class EntityStoreDataClient { } if ('state' in definition) { + const transformHealthToComponentHealth = ( + health: HealthStatus | undefined + ): EngineComponentStatus['health'] => + health ? (health.toLowerCase() as Lowercase) : 'unknown'; + return [ { id: definition.id, installed: definition.state.installed, resource: EngineComponentResourceEnum.entity_definition, }, - ...definition.state.components.transforms.map(({ installed, running, stats }) => ({ + ...definition.state.components.transforms.map(({ installed, stats }) => ({ id, resource: EngineComponentResourceEnum.transform, installed, + health: transformHealthToComponentHealth(stats?.health?.status), errors: (stats?.health as TransformHealth)?.issues?.map(({ issue, details }) => ({ title: issue, message: details,