From e3b5c8770417eaa2483358c13373d2e5120e1296 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 31 Oct 2024 21:42:22 +1100 Subject: [PATCH] [8.16] [Entity Analytics] [Entity Store] Show errors on entity store enablement (#198263) (#198462) # Backport This will backport the following commits from `main` to `8.16`: - [[Entity Analytics] [Entity Store] Show errors on entity store enablement (#198263)](https://github.com/elastic/kibana/pull/198263) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) --------- Co-authored-by: Tiago Vila Verde --- .../output/kibana.serverless.staging.yaml | 2 + oas_docs/output/kibana.serverless.yaml | 2 + oas_docs/output/kibana.staging.yaml | 2 + oas_docs/output/kibana.yaml | 2 + .../entity_store/common.gen.ts | 1 + .../entity_store/common.schema.yaml | 2 + ...alytics_api_2023_10_31.bundled.schema.yaml | 2 + ...alytics_api_2023_10_31.bundled.schema.yaml | 2 + .../components/dashboard_panels.tsx | 62 ++++++++++++++++++- .../hooks/use_entity_engine_status.ts | 13 ++++ .../entity_store/hooks/use_entity_store.ts | 6 +- .../entity_store/entity_store_data_client.ts | 19 ++++-- .../saved_object/engine_descriptor.ts | 8 ++- 13 files changed, 111 insertions(+), 12 deletions(-) diff --git a/oas_docs/output/kibana.serverless.staging.yaml b/oas_docs/output/kibana.serverless.staging.yaml index e08ba7b665680..d5dc0dd30e8b8 100644 --- a/oas_docs/output/kibana.serverless.staging.yaml +++ b/oas_docs/output/kibana.serverless.staging.yaml @@ -24473,6 +24473,8 @@ components: Security_Entity_Analytics_API_EngineDescriptor: type: object properties: + error: + type: object fieldHistoryLength: type: integer filter: diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index e08ba7b665680..d5dc0dd30e8b8 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -24473,6 +24473,8 @@ components: Security_Entity_Analytics_API_EngineDescriptor: type: object properties: + error: + type: object fieldHistoryLength: type: integer filter: diff --git a/oas_docs/output/kibana.staging.yaml b/oas_docs/output/kibana.staging.yaml index 210a8c05e0b98..1b38c3ecc8984 100644 --- a/oas_docs/output/kibana.staging.yaml +++ b/oas_docs/output/kibana.staging.yaml @@ -39370,6 +39370,8 @@ components: Security_Entity_Analytics_API_EngineDescriptor: type: object properties: + error: + type: object fieldHistoryLength: type: integer filter: diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 210a8c05e0b98..1b38c3ecc8984 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -39370,6 +39370,8 @@ components: Security_Entity_Analytics_API_EngineDescriptor: type: object properties: + error: + type: object fieldHistoryLength: type: integer filter: diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts index 2dd83ca89bee0..228bf1e515675 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts @@ -36,6 +36,7 @@ export const EngineDescriptor = z.object({ status: EngineStatus, filter: z.string().optional(), fieldHistoryLength: z.number().int(), + error: z.object({}).optional(), }); export type InspectQuery = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml index 810961392aad1..00b100516b76c 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml @@ -30,6 +30,8 @@ components: type: string fieldHistoryLength: type: integer + error: + type: object EngineStatus: type: string diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 1c7be495492c6..1dfa9becae7db 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -770,6 +770,8 @@ components: EngineDescriptor: type: object properties: + error: + type: object fieldHistoryLength: type: integer filter: diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 9d736030856d9..a941f7215a972 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -770,6 +770,8 @@ components: EngineDescriptor: type: object properties: + error: + type: object fieldHistoryLength: type: integer filter: diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx index 8fa89e8d45a4e..99e2475f95a78 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx @@ -15,6 +15,7 @@ import { EuiLoadingLogo, EuiPanel, EuiImage, + EuiCallOut, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -50,9 +51,25 @@ const EntityStoreDashboardPanelsComponent = () => { const entityStore = useEntityEngineStatus(); const riskEngineStatus = useRiskEngineStatus(); - const { enable: enableStore } = useEntityStoreEnablement(); + const { enable: enableStore, query } = useEntityStoreEnablement(); + const { mutate: initRiskEngine } = useInitRiskEngineMutation(); + const callouts = entityStore.errors.map((err, i) => ( + + } + color="danger" + iconType="error" + > +

{err?.message}

+
+ )); + const enableEntityStore = (enable: Enablements) => () => { setModalState({ visible: false }); if (enable.riskScore) { @@ -74,6 +91,26 @@ const EntityStoreDashboardPanelsComponent = () => { } }; + if (query.error) { + return ( + <> + + } + color="danger" + iconType="error" + > +

{(query.error as { body: { message: string } }).body.message}

+
+ {callouts} + + ); + } + if (entityStore.status === 'loading') { return ( @@ -110,6 +147,29 @@ const EntityStoreDashboardPanelsComponent = () => { return ( + {entityStore.status === 'error' && isRiskScoreAvailable && ( + <> + {callouts} + + + + + + + + )} + {entityStore.status === 'error' && !isRiskScoreAvailable && ( + <> + {callouts} + + setModalState({ visible: true })} + loadingRiskEngine={riskEngineInitializing} + enablements="riskScore" + /> + + + )} {entityStore.status === 'enabled' && isRiskScoreAvailable && ( <> diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_engine_status.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_engine_status.ts index ef6ccd5d6fe20..8a1760728074b 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_engine_status.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_engine_status.ts @@ -17,6 +17,10 @@ interface Options { polling?: UseQueryOptions['refetchInterval']; } +interface EngineError { + message: string; +} + export const useEntityEngineStatus = (opts: Options = {}) => { // QUESTION: Maybe we should have an `EnablementStatus` API route for this? const { listEntityEngines } = useEntityStoreRoutes(); @@ -33,6 +37,10 @@ export const useEntityEngineStatus = (opts: Options = {}) => { return 'not_installed'; } + if (data?.engines?.some((engine) => engine.status === 'error')) { + return 'error'; + } + if (data?.engines?.every((engine) => engine.status === 'stopped')) { return 'stopped'; } @@ -52,7 +60,12 @@ export const useEntityEngineStatus = (opts: Options = {}) => { return 'enabled'; })(); + const errors = (data?.engines + ?.filter((engine) => engine.status === 'error') + .map((engine) => engine.error) ?? []) as EngineError[]; + return { status, + errors, }; }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts index 5b84892ffb6fb..21e73241451e5 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts @@ -41,7 +41,7 @@ export const useEntityStoreEnablement = () => { }); const { initEntityStore } = useEntityStoreRoutes(); - const { refetch: initialize } = useQuery({ + const { refetch: initialize, ...query } = useQuery({ queryKey: [ENTITY_STORE_ENABLEMENT_INIT], queryFn: async () => initEntityStore('user').then((usr) => initEntityStore('host').then((host) => [usr, host])), @@ -52,10 +52,10 @@ export const useEntityStoreEnablement = () => { telemetry?.reportEntityStoreInit({ timestamp: new Date().toISOString(), }); - initialize().then(() => setPolling(true)); + return initialize().then(() => setPolling(true)); }, [initialize, telemetry]); - return { enable }; + return { enable, query }; }; export const INIT_ENTITY_ENGINE_STATUS_KEY = ['POST', 'INIT_ENTITY_ENGINE']; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts index bb47e38b6c541..ab48812730d8e 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts @@ -279,7 +279,14 @@ export class EntityStoreDataClient { error: err.message, }); - await this.engineClient.update(entityType, ENGINE_STATUS.ERROR); + await this.engineClient.update(entityType, { + status: ENGINE_STATUS.ERROR, + error: { + message: err.message, + stack: err.stack, + action: 'init', + }, + }); await this.delete(entityType, taskManager, { deleteData: true, deleteEngine: false }); } @@ -318,7 +325,7 @@ export class EntityStoreDataClient { const fullEntityDefinition = await this.getExistingEntityDefinition(entityType); await this.entityClient.startEntityDefinition(fullEntityDefinition); - return this.engineClient.update(entityType, ENGINE_STATUS.STARTED); + return this.engineClient.updateStatus(entityType, ENGINE_STATUS.STARTED); } public async stop(entityType: EntityType) { @@ -338,7 +345,7 @@ export class EntityStoreDataClient { const fullEntityDefinition = await this.getExistingEntityDefinition(entityType); await this.entityClient.stopEntityDefinition(fullEntityDefinition); - return this.engineClient.update(entityType, ENGINE_STATUS.STOPPED); + return this.engineClient.updateStatus(entityType, ENGINE_STATUS.STOPPED); } public async get(entityType: EntityType) { @@ -511,7 +518,7 @@ export class EntityStoreDataClient { } // Update savedObject status - await this.engineClient.update(engine.type, ENGINE_STATUS.UPDATING); + await this.engineClient.updateStatus(engine.type, ENGINE_STATUS.UPDATING); try { // Update entity manager definition @@ -524,12 +531,12 @@ export class EntityStoreDataClient { }); // Restore the savedObject status and set the new index pattern - await this.engineClient.update(engine.type, originalStatus); + await this.engineClient.updateStatus(engine.type, originalStatus); return { type: engine.type, changes: { indexPatterns } }; } catch (error) { // Rollback the engine initial status when the update fails - await this.engineClient.update(engine.type, originalStatus); + await this.engineClient.updateStatus(engine.type, originalStatus); throw error; } diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts index af7b4ba80dde5..cfaea1b1da0ff 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/saved_object/engine_descriptor.ts @@ -78,17 +78,21 @@ export class EngineDescriptorClient { return attributes; } - async update(entityType: EntityType, status: EngineStatus) { + async update(entityType: EntityType, engine: Partial) { const id = this.getSavedObjectId(entityType); const { attributes } = await this.deps.soClient.update( entityEngineDescriptorTypeName, id, - { status }, + engine, { refresh: 'wait_for' } ); return attributes; } + async updateStatus(entityType: EntityType, status: EngineStatus) { + return this.update(entityType, { status }); + } + async find(entityType: EntityType): Promise> { return this.deps.soClient.find({ type: entityEngineDescriptorTypeName,