From ecffe688195dbec0aadac24d350c61b40fe76ed9 Mon Sep 17 00:00:00 2001 From: machadoum Date: Mon, 18 Nov 2024 12:18:20 +0100 Subject: [PATCH 01/19] wip --- .../entity_store/common.gen.ts | 31 +++ .../entity_store/common.schema.yaml | 43 ++++ .../{enablement.gen.ts => enable.gen.ts} | 8 +- ...blement.schema.yaml => enable.schema.yaml} | 21 -- .../entity_store/engine/list.schema.yaml | 1 + .../entity_store/status.gen.ts | 43 ++++ .../entity_store/status.schema.yaml | 44 +++++ .../common/api/quickstart_client.gen.ts | 14 +- .../entity_store/constants.ts | 14 ++ .../entity_analytics/api/entity_store.ts | 19 +- .../components/engine_components_status.tsx | 75 +++++++ .../engines_status/hooks/use_columns.tsx | 186 ++++++++++++++++++ .../components/engines_status/index.tsx | 91 +++++++++ .../hooks/use_entity_engine_status.ts | 16 ++ .../pages/entity_store_management_page.tsx | 111 +++++------ .../entity_store/auditing/resources.ts | 18 -- .../component_template.ts | 23 +++ .../elasticsearch_assets/enrich_policy.ts | 19 ++ .../elasticsearch_assets/entity_index.ts | 21 +- .../elasticsearch_assets/ingest_pipeline.ts | 25 +++ .../entity_store/entity_store_data_client.ts | 165 +++++++++++++++- .../entity_store/routes/enablement.ts | 4 +- .../entity_store/routes/list.ts | 1 + .../entity_store/routes/status.ts | 12 +- .../task/field_retention_enrichment_task.ts | 35 ++++ .../services/security_solution_api.gen.ts | 11 +- 26 files changed, 922 insertions(+), 129 deletions(-) rename x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/{enablement.gen.ts => enable.gen.ts} (78%) rename x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/{enablement.schema.yaml => enable.schema.yaml} (65%) create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.gen.ts create mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.schema.yaml create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/components/engine_components_status.tsx create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/hooks/use_columns.tsx create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.tsx delete mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/auditing/resources.ts 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 7e419dbe6453c..25c47e838d85c 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 @@ -39,6 +39,37 @@ export const EngineDescriptor = z.object({ error: z.object({}).optional(), }); +export type EngineComponentResource = z.infer; +export const EngineComponentResource = z.enum([ + 'entity_engine', + 'entity_definition', + 'index', + 'component_template', + 'index_template', + 'ingest_pipeline', + 'enrich_policy', + 'task', + 'transform', +]); +export type EngineComponentResourceEnum = typeof EngineComponentResource.enum; +export const EngineComponentResourceEnum = EngineComponentResource.enum; + +export type EngineComponentStatus = z.infer; +export const EngineComponentStatus = z.object({ + id: z.string(), + installed: z.boolean(), + resource: EngineComponentResource, + health: z.enum(['green', 'yellow', 'red', 'unknown']).optional(), + errors: z + .array( + z.object({ + title: z.string().optional(), + message: z.string().optional(), + }) + ) + .optional(), +}); + export type StoreStatus = z.infer; export const StoreStatus = z.enum(['not_installed', 'installing', 'running', 'stopped', 'error']); export type StoreStatusEnum = typeof StoreStatus.enum; 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 9a42191a556ac..5adb6fe038dc9 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 @@ -42,6 +42,49 @@ components: - updating - error + EngineComponentStatus: + type: object + required: + - id + - installed + - resource + properties: + id: + type: string + installed: + type: boolean + resource: + $ref: '#/components/schemas/EngineComponentResource' + health: + type: string + enum: + - green + - yellow + - red + - unknown + errors: + type: array + items: + type: object + properties: + title: + type: string + message: + type: string + + EngineComponentResource: + type: string + enum: + - entity_engine + - entity_definition + - index + - component_template + - index_template + - ingest_pipeline + - enrich_policy + - task + - transform + StoreStatus: type: string enum: diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enablement.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enable.gen.ts similarity index 78% rename from x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enablement.gen.ts rename to x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enable.gen.ts index 9644a1a333d16..70a58bf02be68 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enablement.gen.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enable.gen.ts @@ -16,13 +16,7 @@ import { z } from '@kbn/zod'; -import { IndexPattern, EngineDescriptor, StoreStatus } from './common.gen'; - -export type GetEntityStoreStatusResponse = z.infer; -export const GetEntityStoreStatusResponse = z.object({ - status: StoreStatus.optional(), - engines: z.array(EngineDescriptor).optional(), -}); +import { IndexPattern, EngineDescriptor } from './common.gen'; export type InitEntityStoreRequestBody = z.infer; export const InitEntityStoreRequestBody = z.object({ diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enablement.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enable.schema.yaml similarity index 65% rename from x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enablement.schema.yaml rename to x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enable.schema.yaml index 306e876dfc4a7..81eec22d9ade9 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enablement.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enable.schema.yaml @@ -41,24 +41,3 @@ paths: type: array items: $ref: './common.schema.yaml#/components/schemas/EngineDescriptor' - - /api/entity_store/status: - get: - x-labels: [ess, serverless] - x-codegen-enabled: true - operationId: GetEntityStoreStatus - summary: Get the status of the Entity Store - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: object - properties: - status: - $ref: './common.schema.yaml#/components/schemas/StoreStatus' - engines: - type: array - items: - $ref: './common.schema.yaml#/components/schemas/EngineDescriptor' diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.schema.yaml index deee32a8b2bb7..187718d609103 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.schema.yaml @@ -9,6 +9,7 @@ paths: x-codegen-enabled: true operationId: ListEntityEngines summary: List the Entity Engines + responses: '200': description: Successful response diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.gen.ts new file mode 100644 index 0000000000000..f4ac47b51033a --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.gen.ts @@ -0,0 +1,43 @@ +/* + * 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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Enable Entity Store + * version: 2023-10-31 + */ + +import { z } from '@kbn/zod'; +import { BooleanFromString } from '@kbn/zod-helpers'; + +import { StoreStatus, EngineDescriptor, EngineComponentStatus } from './common.gen'; + +export type GetEntityStoreStatusRequestQuery = z.infer; +export const GetEntityStoreStatusRequestQuery = z.object({ + /** + * If true returns a detailed status of the engine including all it's components + */ + withComponents: BooleanFromString.optional(), +}); +export type GetEntityStoreStatusRequestQueryInput = z.input< + typeof GetEntityStoreStatusRequestQuery +>; + +export type GetEntityStoreStatusResponse = z.infer; +export const GetEntityStoreStatusResponse = z.object({ + status: StoreStatus, + engines: z.array( + EngineDescriptor.merge( + z.object({ + components: z.array(EngineComponentStatus).optional(), + }) + ) + ), +}); diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.schema.yaml new file mode 100644 index 0000000000000..1ac67d192b0dd --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.schema.yaml @@ -0,0 +1,44 @@ +openapi: 3.0.0 + +info: + title: Enable Entity Store + version: '2023-10-31' +paths: + /api/entity_store/status: + get: + x-labels: [ess, serverless] + x-codegen-enabled: true + operationId: GetEntityStoreStatus + summary: Get the status of the Entity Store + + parameters: + - name: withComponents + in: query + schema: + type: boolean + description: If true returns a detailed status of the engine including all it's components + + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + required: + - status + - engines + properties: + status: + $ref: './common.schema.yaml#/components/schemas/StoreStatus' + engines: + type: array + items: + allOf: + - $ref: './common.schema.yaml#/components/schemas/EngineDescriptor' + - type: object + properties: + components: + type: array + items: + $ref: './common.schema.yaml#/components/schemas/EngineComponentStatus' diff --git a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts index 5a1cf49baecd1..1a755df726a0d 100644 --- a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts +++ b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts @@ -232,10 +232,9 @@ import type { UploadAssetCriticalityRecordsResponse, } from './entity_analytics/asset_criticality/upload_asset_criticality_csv.gen'; import type { - GetEntityStoreStatusResponse, InitEntityStoreRequestBodyInput, InitEntityStoreResponse, -} from './entity_analytics/entity_store/enablement.gen'; +} from './entity_analytics/entity_store/enable.gen'; import type { ApplyEntityEngineDataviewIndicesResponse } from './entity_analytics/entity_store/engine/apply_dataview_indices.gen'; import type { DeleteEntityEngineRequestQueryInput, @@ -269,6 +268,10 @@ import type { ListEntitiesRequestQueryInput, ListEntitiesResponse, } from './entity_analytics/entity_store/entities/list_entities.gen'; +import type { + GetEntityStoreStatusRequestQueryInput, + GetEntityStoreStatusResponse, +} from './entity_analytics/entity_store/status.gen'; import type { CleanUpRiskEngineResponse } from './entity_analytics/risk_engine/engine_cleanup_route.gen'; import type { DisableRiskEngineResponse } from './entity_analytics/risk_engine/engine_disable_route.gen'; import type { EnableRiskEngineResponse } from './entity_analytics/risk_engine/engine_enable_route.gen'; @@ -1302,7 +1305,7 @@ finalize it. }) .catch(catchAxiosErrorFormatAndThrow); } - async getEntityStoreStatus() { + async getEntityStoreStatus(props: GetEntityStoreStatusProps) { this.log.info(`${new Date().toISOString()} Calling API GetEntityStoreStatus`); return this.kbnClient .request({ @@ -1311,6 +1314,8 @@ finalize it. [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31', }, method: 'GET', + + query: props.query, }) .catch(catchAxiosErrorFormatAndThrow); } @@ -2288,6 +2293,9 @@ export interface GetEntityEngineProps { export interface GetEntityEngineStatsProps { params: GetEntityEngineStatsRequestParamsInput; } +export interface GetEntityStoreStatusProps { + query: GetEntityStoreStatusRequestQueryInput; +} export interface GetNotesProps { query: GetNotesRequestQueryInput; } diff --git a/x-pack/plugins/security_solution/common/entity_analytics/entity_store/constants.ts b/x-pack/plugins/security_solution/common/entity_analytics/entity_store/constants.ts index b6834422c8cfc..7b46a0a766e2e 100644 --- a/x-pack/plugins/security_solution/common/entity_analytics/entity_store/constants.ts +++ b/x-pack/plugins/security_solution/common/entity_analytics/entity_store/constants.ts @@ -23,3 +23,17 @@ export const ENTITY_STORE_REQUIRED_ES_CLUSTER_PRIVILEGES = [ // The index pattern for the entity store has to support '.entities.v1.latest.noop' index export const ENTITY_STORE_INDEX_PATTERN = '.entities.v1.latest.*'; + +export const EntityStoreResource = { + ENTITY_ENGINE: 'entity_engine', + ENTITY_DEFINITION: 'entity_definition', + INDEX: 'index', + COMPONENT_TEMPLATE: 'component_template', + INDEX_TEMPLATE: 'index_template', + INGEST_PIPELINE: 'ingest_pipeline', + ENRICH_POLICY: 'enrich_policy', + TASK: 'task', + TRANSFORM: 'transform', +} as const; + +export type EntityStoreResource = (typeof EntityStoreResource)[keyof typeof EntityStoreResource]; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts index 54f5415d24a35..784d65618255a 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts @@ -5,10 +5,10 @@ * 2.0. */ import { useMemo } from 'react'; +import type { GetEntityStoreStatusResponse } from '../../../common/api/entity_analytics/entity_store/status.gen'; import type { DeleteEntityEngineResponse, EntityType, - GetEntityEngineResponse, InitEntityEngineResponse, ListEntityEnginesResponse, StopEntityEngineResponse, @@ -36,13 +36,6 @@ export const useEntityStoreRoutes = () => { }); }; - const getEntityEngine = async (entityType: EntityType) => { - return http.fetch(`/api/entity_store/engines/${entityType}`, { - method: 'GET', - version: API_VERSIONS.public.v1, - }); - }; - const deleteEntityEngine = async (entityType: EntityType, deleteData: boolean) => { return http.fetch(`/api/entity_store/engines/${entityType}`, { method: 'DELETE', @@ -58,12 +51,20 @@ export const useEntityStoreRoutes = () => { }); }; + const getEntityStoreStatus = async (withComponents = false) => { + return http.fetch('/api/entity_store/status', { + method: 'GET', + version: API_VERSIONS.public.v1, + query: { withComponents }, + }); + }; + return { initEntityStore, stopEntityStore, - getEntityEngine, deleteEntityEngine, listEntityEngines, + getEntityStoreStatus, }; }, [http]); }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/components/engine_components_status.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/components/engine_components_status.tsx new file mode 100644 index 0000000000000..5f0f1df7fc464 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/components/engine_components_status.tsx @@ -0,0 +1,75 @@ +/* + * 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 { ReactNode } from 'react'; +import React, { useState, useMemo, useCallback } from 'react'; +import { EuiSpacer, EuiHealth, EuiCodeBlock } from '@elastic/eui'; +import { BasicTable } from '../../../../../../common/components/ml/tables/basic_table'; +import { useColumns } from '../hooks/use_columns'; +import type { EngineComponentStatus } from '../../../../../../../common/api/entity_analytics'; + +type ExpandedRowMap = Record; + +const componentToId = ({ id, resource }: EngineComponentStatus) => `${resource}-${id}`; + +export const EngineComponentsStatusTable = ({ + components, +}: { + components: EngineComponentStatus[]; +}) => { + const [expandedItems, setExpandedItems] = useState([]); + + const itemIdToExpandedRowMap: ExpandedRowMap = useMemo(() => { + return expandedItems.reduce((acc, componentStatus) => { + if (componentStatus.errors && componentStatus.errors.length > 0) { + acc[componentToId(componentStatus)] = ( + + ); + } + return acc; + }, {}); + }, [expandedItems]); + + const onToggle = useCallback( + (component: EngineComponentStatus) => { + const isItemExpanded = expandedItems.includes(component); + + if (isItemExpanded) { + setExpandedItems(expandedItems.filter((item) => component !== item)); + } else { + setExpandedItems([...expandedItems, component]); + } + }, + [expandedItems] + ); + + const columns = useColumns(onToggle, expandedItems); + + return ( + + ); +}; + +const TransformExtendedData = ({ errors }: { errors: EngineComponentStatus['errors'] }) => { + return ( + <> + {errors?.map(({ title, message }) => ( + <> + + {title} + + {message} + + ))} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/hooks/use_columns.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/hooks/use_columns.tsx new file mode 100644 index 0000000000000..09a8cb101c87a --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/hooks/use_columns.tsx @@ -0,0 +1,186 @@ +/* + * 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 { IconColor } from '@elastic/eui'; +import { + EuiLink, + type EuiBasicTableColumn, + EuiHealth, + EuiScreenReaderOnly, + EuiButtonIcon, + EuiIcon, +} from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EngineComponentResourceEnum, + type EngineComponentResource, +} from '../../../../../../../common/api/entity_analytics'; +import type { EngineComponentStatus } from '../../../../../../../common/api/entity_analytics'; +import { useKibana } from '../../../../../../common/lib/kibana'; + +type TableColumn = EuiBasicTableColumn; + +export const HEALTH_COLOR: Record['health'], IconColor> = { + green: 'success', + unknown: 'subdued', + yellow: 'warning', + red: 'danger', +} as const; + +const RESOURCE_TO_TEXT: Record = { + ingest_pipeline: 'Ingest Pipeline', + enrich_policy: 'Enrich Policy', + index: 'Index', + component_template: 'Component Template', + task: 'Task', + transform: 'Transform', + entity_definition: 'Entity Definition', + entity_engine: 'Engine', + index_template: 'Index Template', +}; + +export const useColumns = ( + onToggleExpandedItem: (item: EngineComponentStatus) => void, + expandedItems: EngineComponentStatus[] +): TableColumn[] => { + const { getUrlForApp } = useKibana().services.application; + + return useMemo( + () => [ + { + field: 'resource', + name: ( + + ), + width: '20%', + render: (resource: EngineComponentStatus['resource']) => RESOURCE_TO_TEXT[resource], + }, + { + field: 'id', + name: ( + + ), + render: (id: EngineComponentStatus['id'], { resource, installed }) => { + const path = getResourcePath(id, resource); + + if (!installed || !path) { + return id; + } + + return ( + + {id} + + ); + }, + }, + { + field: 'installed', + name: ( + + ), + width: '10%', + align: 'center', + render: (value: boolean) => + value ? ( + + ) : ( + + ), + }, + { + name: ( + + ), + width: '10%', + align: 'center', + render: ({ installed, resource, health }: EngineComponentStatus) => { + if (!installed) { + return null; + } + + return ; + }, + }, + { + isExpander: true, + align: 'right', + width: '40px', + name: ( + + + + + + ), + mobileOptions: { header: false }, + render: (component: EngineComponentStatus) => { + const isItemExpanded = expandedItems.includes(component); + + return component.resource === EngineComponentResourceEnum.transform && + component.errors && + component.errors.length > 0 ? ( + onToggleExpandedItem(component)} + aria-label={isItemExpanded ? 'Collapse' : 'Expand'} + iconType={isItemExpanded ? 'arrowDown' : 'arrowRight'} + /> + ) : null; + }, + }, + ], + [expandedItems, getUrlForApp, onToggleExpandedItem] + ); +}; + +const getResourcePath = (id: string, resource: EngineComponentResource) => { + if (resource === EngineComponentResourceEnum.ingest_pipeline) { + return `ingest/ingest_pipelines?pipeline=${id}`; + } + + if (resource === EngineComponentResourceEnum.index_template) { + return `data/index_management/templates/${id}`; + } + + if (resource === EngineComponentResourceEnum.index) { + return `data/index_management/indices/index_details?indexName=${id}`; + } + + if (resource === EngineComponentResourceEnum.component_template) { + return `data/index_management/component_templates/${id}`; + } + + if (resource === EngineComponentResourceEnum.enrich_policy) { + return `data/index_management/enrich_policies?policy=${id}`; + } + + if (resource === EngineComponentResourceEnum.transform) { + return `data/transform/enrich_policies?_a=(transform:(queryText:'${id}'))`; + } + return null; +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.tsx new file mode 100644 index 0000000000000..3bc6274db1a09 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.tsx @@ -0,0 +1,91 @@ +/* + * 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, { useCallback } from 'react'; +import { + EuiLoadingSpinner, + EuiPanel, + EuiSpacer, + EuiButtonEmpty, + EuiFlexItem, + EuiTitle, + EuiFlexGroup, +} from '@elastic/eui'; +import { capitalize } from 'lodash/fp'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { downloadBlob } from '../../../../../common/utils/download_blob'; +import { useEntityEngineStatusNew } from '../../hooks/use_entity_engine_status'; +import { EngineComponentsStatusTable } from './components/engine_components_status'; + +export const EngineStatus: React.FC = () => { + const { data, isLoading, error } = useEntityEngineStatusNew({ withComponents: true }); + + const downloadJson = useCallback(() => { + downloadBlob(new Blob([JSON.stringify(data)]), 'engines_status.json'); + }, [data]); + + if (!data || isLoading) return ; + + if (error) return ; + + if (data.engines.length === 0) { + return ( + + ); + } + + return ( + <> + + {data?.engines?.length > 0 && ( + + + + + + + + + + )} + + {(data?.engines ?? []).map((engine) => ( + <> + +

+ +

+
+ + + + + {engine.components && ( + + )} + + + + + ))} +
+
+ + ); +}; 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 8a1760728074b..eaaf0f080802d 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 @@ -7,6 +7,8 @@ import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query'; +import type { IHttpFetchError } from '@kbn/core/public'; +import type { GetEntityStoreStatusResponse } from '../../../../../common/api/entity_analytics/entity_store/status.gen'; import type { ListEntityEnginesResponse } from '../../../../../common/api/entity_analytics'; import { useEntityStoreRoutes } from '../../../api/entity_store'; @@ -15,12 +17,26 @@ export const ENTITY_STORE_ENGINE_STATUS = 'ENTITY_STORE_ENGINE_STATUS'; interface Options { disabled?: boolean; polling?: UseQueryOptions['refetchInterval']; + withComponents?: boolean; } interface EngineError { message: string; } +export const useEntityEngineStatusNew = (opts: Options = {}) => { + const { getEntityStoreStatus } = useEntityStoreRoutes(); + + return useQuery({ + queryKey: ['GET_ENTITY_STORE_STATUS', opts.withComponents], + queryFn: () => getEntityStoreStatus(opts.withComponents), + cacheTime: 0, + // enabled: !skip, + refetchOnWindowFocus: true, + keepPreviousData: true, + }); +}; + export const useEntityEngineStatus = (opts: Options = {}) => { // QUESTION: Maybe we should have an `EnablementStatus` API route for this? const { listEntityEngines } = useEntityStoreRoutes(); diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx index 84648d89f912d..23e1d13bda3ca 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx @@ -21,14 +21,16 @@ import { EuiCode, EuiSwitch, EuiHealth, - EuiButton, EuiLoadingSpinner, EuiToolTip, EuiBetaBadge, + EuiTabs, + EuiTab, + EuiButtonEmpty, } from '@elastic/eui'; import React, { useCallback, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useEntityEngineStatus } from '../components/entity_store/hooks/use_entity_engine_status'; + import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { ASSET_CRITICALITY_INDEX_PATTERN } from '../../../common/entity_analytics/asset_criticality'; import { useKibana } from '../../common/lib/kibana'; @@ -43,10 +45,18 @@ import { import { TECHNICAL_PREVIEW, TECHNICAL_PREVIEW_TOOLTIP } from '../../common/translations'; import { useEntityEnginePrivileges } from '../components/entity_store/hooks/use_entity_engine_privileges'; import { MissingPrivilegesCallout } from '../components/entity_store/components/missing_privileges_callout'; +import { EngineStatus } from '../components/entity_store/components/engines_status'; +import { useEntityEngineStatus } from '../components/entity_store/hooks/use_entity_engine_status'; const entityStoreEnabledStatuses = ['enabled']; const switchDisabledStatuses = ['error', 'loading', 'installing']; const entityStoreInstallingStatuses = ['installing', 'loading']; +const installedStatuses = ['stopped', 'enabled', 'error']; + +enum TabId { + Import = 'import', + Resources = 'resources', +} export const EntityStoreManagementPage = () => { const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); @@ -59,6 +69,8 @@ export const EntityStoreManagementPage = () => { const hasAssetCriticalityWritePermissions = assetCriticalityPrivileges?.has_write_permissions; const [polling, setPolling] = useState(false); + const [selectedTabId, setSelectedTabId] = useState(TabId.Import); + const entityStoreStatus = useEntityEngineStatus({ disabled: false, polling: !polling @@ -134,46 +146,22 @@ export const EntityStoreManagementPage = () => { ); }; - const ClearEntityDataPanel: React.FC = () => { + const ClearEntityDataButton: React.FC = () => { return ( <> - { + showClearModal(); + }} > - -

- -

- - -
- - { - showClearModal(); - }} - > - - -
+ + + {isClearModalVisible && ( { } return ( - -

- -

-
{ alignItems="center" rightSideItems={ !isEntityStoreFeatureFlagDisabled && privileges?.has_all_required - ? [ + ? // !isEntityStoreFeatureFlagDisabled && true + [ { onSwitch={onSwitchClick} status={entityStoreStatus.status} />, + canDeleteEntityEngine ? : null, ] : [] } @@ -321,10 +303,32 @@ export const EntityStoreManagementPage = () => { )} - - + + + + setSelectedTabId(TabId.Import)} + > + {'Import Entities'} + + + {installedStatuses.includes(entityStoreStatus.status) && privileges?.has_all_required && ( + setSelectedTabId(TabId.Resources)} + > + {'Engine Status'} + + )} + + + - + {selectedTabId === TabId.Import && } + {selectedTabId === TabId.Resources && } {initEntityEngineMutation.isError && ( @@ -360,10 +364,7 @@ export const EntityStoreManagementPage = () => { )} {callouts} - - {!isEntityStoreFeatureFlagDisabled && - privileges?.has_all_required && - canDeleteEntityEngine && } + {selectedTabId === TabId.Import && } diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/auditing/resources.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/auditing/resources.ts deleted file mode 100644 index 67d33fb42dc93..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/auditing/resources.ts +++ /dev/null @@ -1,18 +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. - */ - -export const EntityStoreResource = { - ENTITY_ENGINE: 'entity_engine', - ENTITY_DEFINITION: 'entity_definition', - ENTITY_INDEX: 'entity_index', - INDEX_COMPONENT_TEMPLATE: 'index_component_template', - PLATFORM_PIPELINE: 'platform_pipeline', - FIELD_RETENTION_ENRICH_POLICY: 'field_retention_enrich_policy', - FIELD_RETENTION_ENRICH_POLICY_TASK: 'field_retention_enrich_policy_task', -} as const; - -export type EntityStoreResource = (typeof EntityStoreResource)[keyof typeof EntityStoreResource]; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/component_template.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/component_template.ts index d0fe14dcd3b8d..f8e2de5a10749 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/component_template.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/component_template.ts @@ -6,6 +6,8 @@ */ import type { ElasticsearchClient } from '@kbn/core/server'; +import type { EngineComponentStatus } from '../../../../../common/api/entity_analytics'; +import { EntityStoreResource } from '../../../../../common/entity_analytics/entity_store/constants'; import type { UnitedEntityDefinition } from '../united_entity_definitions'; const getComponentTemplateName = (definitionId: string) => `${definitionId}-latest@platform`; @@ -41,3 +43,24 @@ export const deleteEntityIndexComponentTemplate = ({ unitedDefinition, esClient } ); }; + +export const getEntityIndexComponentTemplateStatus = async ({ + definitionId, + esClient, +}: Pick & { definitionId: string }): Promise => { + const name = getComponentTemplateName(definitionId); + const componentTemplate = await esClient.cluster.getComponentTemplate( + { + name, + }, + { + ignore: [404], + } + ); + + return { + id: name, + installed: componentTemplate?.component_templates?.length > 0, + resource: EntityStoreResource.COMPONENT_TEMPLATE, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/enrich_policy.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/enrich_policy.ts index 4b8ce594a6cb7..edf21219007e4 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/enrich_policy.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/enrich_policy.ts @@ -7,6 +7,7 @@ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import type { EnrichPutPolicyRequest } from '@elastic/elasticsearch/lib/api/types'; +import { EntityStoreResource } from '../../../../../common/entity_analytics/entity_store/constants'; import { getEntitiesIndexName } from '../utils'; import type { UnitedEntityDefinition } from '../united_entity_definitions'; @@ -105,3 +106,21 @@ export const deleteFieldRetentionEnrichPolicy = async ({ } } }; + +export const getFieldRetentionEnrichPolicyStatus = async ({ + definitionMetadata, + esClient, +}: { + definitionMetadata: DefinitionMetadata; + esClient: ElasticsearchClient; +}) => { + const name = getFieldRetentionEnrichPolicyName(definitionMetadata); + const policy = await esClient.enrich.getPolicy({ name }, { ignore: [404] }); + const policies = policy.policies; + + return { + installed: policies.length > 0, + id: name, + resource: EntityStoreResource.ENRICH_POLICY, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/entity_index.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/entity_index.ts index 6fb5935618dfc..f000fc16d6639 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/entity_index.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/entity_index.ts @@ -6,7 +6,8 @@ */ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; -import type { EntityType } from '../../../../../common/api/entity_analytics'; +import { EntityStoreResource } from '../../../../../common/entity_analytics/entity_store/constants'; +import type { EngineComponentStatus, EntityType } from '../../../../../common/api/entity_analytics'; import { getEntitiesIndexName } from '../utils'; import { createOrUpdateIndex } from '../../utils/create_or_update_index'; @@ -36,3 +37,21 @@ export const deleteEntityIndex = ({ entityType, esClient, namespace }: Options) ignore: [404], } ); + +export const getEntityIndexStatus = async ({ + entityType, + esClient, + namespace, +}: Pick): Promise => { + const index = getEntitiesIndexName(entityType, namespace); + const exists = await esClient.indices.exists( + { + index, + }, + { + ignore: [404], + } + ); + + return { id: index, installed: exists, resource: EntityStoreResource.INDEX }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts index f4d2b848b726f..da96b672d2f5a 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts @@ -8,6 +8,7 @@ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import type { IngestProcessorContainer } from '@elastic/elasticsearch/lib/api/types'; import type { EntityDefinition } from '@kbn/entities-schema'; +import { EntityStoreResource } from '../../../../../common/entity_analytics/entity_store/constants'; import { type FieldRetentionDefinition } from '../field_retention_definition'; import { debugDeepCopyContextStep, @@ -161,3 +162,27 @@ export const deletePlatformPipeline = ({ } ); }; + +export const getPlatformPipelineStatus = async ({ + definition, + esClient, +}: { + definition: EntityDefinition; + esClient: ElasticsearchClient; +}) => { + const pipelineId = getPlatformPipelineId(definition); + const pipeline = await esClient.ingest.getPipeline( + { + id: pipelineId, + }, + { + ignore: [404], + } + ); + + return { + id: pipelineId, + installed: !!pipeline[pipelineId], + resource: EntityStoreResource.INGEST_PIPELINE, + }; +}; 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 a89469acf569b..d55f8b181cfed 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 @@ -20,11 +20,18 @@ import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; import type { DataViewsService } from '@kbn/data-views-plugin/common'; import { isEqual } from 'lodash/fp'; import moment from 'moment'; +import type { EntityDefinitionWithState } from '@kbn/entityManager-plugin/server/lib/entities/types'; +import type { EntityDefinition } from '@kbn/entities-schema'; +import type { estypes } from '@elastic/elasticsearch'; import type { + GetEntityStoreStatusRequestQuery, GetEntityStoreStatusResponse, +} from '../../../../common/api/entity_analytics/entity_store/status.gen'; +import type { InitEntityStoreRequestBody, InitEntityStoreResponse, -} from '../../../../common/api/entity_analytics/entity_store/enablement.gen'; +} from '../../../../common/api/entity_analytics/entity_store/enable.gen'; +import { EntityStoreResource } from '../../../../common/entity_analytics/entity_store/constants'; import type { AppClient } from '../../..'; import { EntityType } from '../../../../common/api/entity_analytics'; import type { @@ -33,6 +40,8 @@ import type { InitEntityEngineRequestBody, InitEntityEngineResponse, InspectQuery, + ListEntityEnginesResponse, + EngineComponentStatus, } from '../../../../common/api/entity_analytics'; import { EngineDescriptorClient } from './saved_object/engine_descriptor'; import { ENGINE_STATUS, ENTITY_STORE_STATUS, MAX_SEARCH_RESPONSE_SIZE } from './constants'; @@ -41,6 +50,7 @@ import { getUnitedEntityDefinition } from './united_entity_definitions'; import { startEntityStoreFieldRetentionEnrichTask, removeEntityStoreFieldRetentionEnrichTask, + getEntityStoreFieldRetentionEnrichTaskState as getEntityStoreFieldRetentionEnrichTaskStatus, } from './task'; import { createEntityIndex, @@ -52,6 +62,10 @@ import { createFieldRetentionEnrichPolicy, executeFieldRetentionEnrichPolicy, deleteFieldRetentionEnrichPolicy, + getPlatformPipelineStatus, + getFieldRetentionEnrichPolicyStatus, + getEntityIndexStatus, + getEntityIndexComponentTemplateStatus, } from './elasticsearch_assets'; import { RiskScoreDataClient } from '../risk_score/risk_score_data_client'; import { @@ -62,7 +76,6 @@ import { isPromiseRejected, } from './utils'; import { EntityEngineActions } from './auditing/actions'; -import { EntityStoreResource } from './auditing/resources'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../audit'; import type { EntityRecord, EntityStoreConfig } from './types'; import { @@ -71,6 +84,19 @@ import { } from '../../telemetry/event_based/events'; import { CRITICALITY_VALUES } from '../asset_criticality/constants'; +// Workaround. TransformState type is wrong. The health type should be: TransformHealth from '@kbn/transform-plugin/common/types/transform_stats' +export interface TransformHealth extends estypes.TransformGetTransformStatsTransformStatsHealth { + issues?: TransformHealthIssue[]; +} + +export interface TransformHealthIssue { + type: string; + issue: string; + details?: string; + count: number; + first_occurrence?: number; +} + interface EntityStoreClientOpts { logger: Logger; clusterClient: IScopedClusterClient; @@ -131,6 +157,42 @@ export class EntityStoreDataClient { }); } + private async getEngineComponentsState( + type: EntityType, + definition?: EntityDefinition + ): Promise { + const { namespace, taskManager } = this.options; + + return definition + ? Promise.all([ + ...(taskManager + ? [getEntityStoreFieldRetentionEnrichTaskStatus({ namespace, taskManager })] + : []), + getPlatformPipelineStatus({ + definition, + esClient: this.esClient, + }), + getFieldRetentionEnrichPolicyStatus({ + definitionMetadata: { + namespace, + entityType: type, + version: definition.version, + }, + esClient: this.esClient, + }), + getEntityIndexStatus({ + entityType: type, + esClient: this.esClient, + namespace, + }), + getEntityIndexComponentTemplateStatus({ + definitionId: definition.id, + esClient: this.esClient, + }), + ]) + : Promise.resolve([] as EngineComponentStatus[]); + } + public async enable( { indexPattern = '', filter = '', fieldHistoryLength = 10 }: InitEntityStoreRequestBody, { pipelineDebugMode = false }: { pipelineDebugMode?: boolean } = {} @@ -152,7 +214,10 @@ export class EntityStoreDataClient { return { engines, succeeded: true }; } - public async status(): Promise { + public async status({ + withComponents = false, + }: GetEntityStoreStatusRequestQuery): Promise { + const { namespace } = this.options; const { engines, count } = await this.engineClient.list(); let status = ENTITY_STORE_STATUS.RUNNING; @@ -166,9 +231,51 @@ export class EntityStoreDataClient { status = ENTITY_STORE_STATUS.INSTALLING; } - return { engines, status }; + if (withComponents) { + const enginesWithComponents = await Promise.all( + engines.map(async (engine) => { + const entityDefinitionId = buildEntityDefinitionId(engine.type, namespace); + const { + definitions: [definition], + } = await this.entityClient.getEntityDefinitions({ + id: entityDefinitionId, + includeState: withComponents, + }); + + const definitionComponents = this.getComponentFromEntityDefinition( + entityDefinitionId, + definition + ); + + const entityStoreComponents = await this.getEngineComponentsState( + engine.type, + definition + ); + + return { + ...engine, + components: [...definitionComponents, ...entityStoreComponents], + }; + }) + ); + + return { engines: enginesWithComponents, status }; + } else { + return { engines, status }; + } } + // [x] TS types and openAPI + // [x] check if this change should be part of another API + // [x] fix UI types + // [x] refactor UI + // TODO test for error state + // TODO API tests + // TODO Unit tests? + // TODO create openAPI doc and defines returned types interface EntityState {} + // TODO does calling `GET kbn:/api/entity_store/engines/user` on a clean cluster kill Kibana? + // TODO investigate why we list engines for user without saved object privileges (see message from Mark) + public async init( entityType: EntityType, { indexPattern = '', filter = '', fieldHistoryLength = 10 }: InitEntityEngineRequestBody, @@ -355,6 +462,50 @@ export class EntityStoreDataClient { } } + public getComponentFromEntityDefinition( + id: string, + definition: EntityDefinitionWithState | EntityDefinition + ): EngineComponentStatus[] { + if (!definition) { + return [ + { + id, + installed: false, + resource: EntityStoreResource.ENTITY_DEFINITION, + }, + ]; + } + + if ('state' in definition) { + return [ + { + id: definition.id, + installed: definition.state.installed, + resource: EntityStoreResource.ENTITY_DEFINITION, + }, + ...definition.state.components.transforms.map(({ installed, running, stats }) => ({ + id, + resource: EntityStoreResource.TRANSFORM, + installed, + errors: (stats?.health as TransformHealth)?.issues?.map(({ issue, details }) => ({ + title: issue, + message: details, + })), + })), + ...definition.state.components.ingestPipelines.map((pipeline) => ({ + resource: EntityStoreResource.INGEST_PIPELINE, + ...pipeline, + })), + ...definition.state.components.indexTemplates.map(({ installed }) => ({ + id, + installed, + resource: EntityStoreResource.INDEX_TEMPLATE, + })), + ]; + } + return []; + } + public async getExistingEntityDefinition(entityType: EntityType) { const entityDefinitionId = buildEntityDefinitionId(entityType, this.options.namespace); @@ -424,11 +575,11 @@ export class EntityStoreDataClient { return this.engineClient.updateStatus(entityType, ENGINE_STATUS.STOPPED); } - public async get(entityType: EntityType) { - return this.engineClient.get(entityType); + public async get(entityType: EntityType, includeState = false) { + return { engine: this.engineClient.get(entityType) }; } - public async list() { + public async list(): Promise { return this.engineClient.list(); } diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/enablement.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/enablement.ts index 16813fccdf235..d19e9699f4756 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/enablement.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/enablement.ts @@ -10,8 +10,8 @@ import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import type { InitEntityStoreResponse } from '../../../../../common/api/entity_analytics/entity_store/enablement.gen'; -import { InitEntityStoreRequestBody } from '../../../../../common/api/entity_analytics/entity_store/enablement.gen'; +import type { InitEntityStoreResponse } from '../../../../../common/api/entity_analytics/entity_store/enable.gen'; +import { InitEntityStoreRequestBody } from '../../../../../common/api/entity_analytics/entity_store/enable.gen'; import { API_VERSIONS, APP_ID } from '../../../../../common/constants'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { checkAndInitAssetCriticalityResources } from '../../asset_criticality/check_and_init_asset_criticality_resources'; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts index 372331cea7087..519afdb9767bb 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts @@ -39,6 +39,7 @@ export const listEntityEnginesRoute = ( try { const secSol = await context.securitySolution; + // should includeStatus be snake case? const body = await secSol.getEntityStoreDataClient().list(); return response.ok({ body }); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/status.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/status.ts index 7a59b59b9914a..c2c24b114f66b 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/status.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/status.ts @@ -15,7 +15,9 @@ import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; -import type { GetEntityStoreStatusResponse } from '../../../../../common/api/entity_analytics/entity_store/enablement.gen'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { GetEntityStoreStatusResponse } from '../../../../../common/api/entity_analytics/entity_store/status.gen'; +import { GetEntityStoreStatusRequestQuery } from '../../../../../common/api/entity_analytics/entity_store/status.gen'; import { API_VERSIONS, APP_ID } from '../../../../../common/constants'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { checkAndInitAssetCriticalityResources } from '../../asset_criticality/check_and_init_asset_criticality_resources'; @@ -38,7 +40,11 @@ export const getEntityStoreStatusRoute = ( .addVersion( { version: API_VERSIONS.public.v1, - validate: {}, + validate: { + request: { + query: buildRouteValidationWithZod(GetEntityStoreStatusRequestQuery), + }, + }, }, async ( @@ -54,7 +60,7 @@ export const getEntityStoreStatusRoute = ( try { const body: GetEntityStoreStatusResponse = await secSol .getEntityStoreDataClient() - .status(); + .status(request.query); return response.ok({ body }); } catch (e) { diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts index 708b74277faae..7b79901bac482 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts @@ -13,6 +13,7 @@ import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; +import { EntityStoreResource } from '../../../../../common/entity_analytics/entity_store/constants'; import type { EntityType } from '../../../../../common/api/entity_analytics/entity_store'; import { defaultState, @@ -275,3 +276,37 @@ const createTaskRunnerFactory = }, }; }; + +export const getEntityStoreFieldRetentionEnrichTaskState = async ({ + namespace, + taskManager, +}: { + namespace: string; + taskManager: TaskManagerStartContract; +}) => { + const taskId = getTaskId(namespace); + try { + const taskState = await taskManager.get(taskId); + + return { + id: taskState.id, + resource: EntityStoreResource.TASK, + installed: true, + enabled: taskState.enabled, + status: taskState.status, + retryAttempts: taskState.attempts, + nextRun: taskState.runAt, + lastRun: taskState.state.lastExecutionTimestamp, + runs: taskState.state.runs, + }; + } catch (e) { + if (SavedObjectsErrorHelpers.isNotFoundError(e)) { + return { + id: taskId, + installed: false, + resource: EntityStoreResource.TASK, + }; + } + throw e; + } +}; diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts index 3574199709aee..f2e9bcc8eac50 100644 --- a/x-pack/test/api_integration/services/security_solution_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts @@ -81,6 +81,7 @@ import { } from '@kbn/security-solution-plugin/common/api/endpoint/suggestions/get_suggestions.gen'; import { GetEntityEngineRequestParamsInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/get.gen'; import { GetEntityEngineStatsRequestParamsInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/stats.gen'; +import { GetEntityStoreStatusRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/status.gen'; import { GetNotesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_notes/get_notes_route.gen'; import { GetPolicyResponseRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy_response.gen'; import { GetProtectionUpdatesNoteRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/protection_updates_note/protection_updates_note.gen'; @@ -106,7 +107,7 @@ import { InitEntityEngineRequestParamsInput, InitEntityEngineRequestBodyInput, } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/init.gen'; -import { InitEntityStoreRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/enablement.gen'; +import { InitEntityStoreRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/enable.gen'; import { InstallPrepackedTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen'; import { ListEntitiesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/entities/list_entities.gen'; import { PatchRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.gen'; @@ -844,12 +845,13 @@ finalize it. .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, - getEntityStoreStatus(kibanaSpace: string = 'default') { + getEntityStoreStatus(props: GetEntityStoreStatusProps, kibanaSpace: string = 'default') { return supertest .get(routeWithNamespace('/api/entity_store/status', kibanaSpace)) .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); }, /** * Get all notes for a given document. @@ -1618,6 +1620,9 @@ export interface GetEntityEngineProps { export interface GetEntityEngineStatsProps { params: GetEntityEngineStatsRequestParamsInput; } +export interface GetEntityStoreStatusProps { + query: GetEntityStoreStatusRequestQueryInput; +} export interface GetNotesProps { query: GetNotesRequestQueryInput; } From 1845efb5581001d98dd30dec77ce443de70f57e3 Mon Sep 17 00:00:00 2001 From: machadoum Date: Thu, 21 Nov 2024 17:40:44 +0100 Subject: [PATCH 02/19] Delete entity store stats API --- .../entity_store/engine/index.ts | 1 - .../entity_store/engine/stats.gen.ts | 39 ----------- .../entity_store/engine/stats.schema.yaml | 41 ------------ .../entity_store/routes/stats.ts | 64 ------------------- 4 files changed, 145 deletions(-) delete mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.gen.ts delete mode 100644 x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.schema.yaml delete mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stats.ts diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/index.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/index.ts index b21308de36f18..32bc0a4efc07b 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/index.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/index.ts @@ -10,6 +10,5 @@ export * from './get.gen'; export * from './init.gen'; export * from './list.gen'; export * from './start.gen'; -export * from './stats.gen'; export * from './stop.gen'; export * from './apply_dataview_indices.gen'; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.gen.ts deleted file mode 100644 index 8b2cb44947535..0000000000000 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.gen.ts +++ /dev/null @@ -1,39 +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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Get Entity Engine stats - * version: 2023-10-31 - */ - -import { z } from '@kbn/zod'; - -import { EntityType, IndexPattern, EngineStatus } from '../common.gen'; - -export type GetEntityEngineStatsRequestParams = z.infer; -export const GetEntityEngineStatsRequestParams = z.object({ - /** - * The entity type of the engine (either 'user' or 'host'). - */ - entityType: EntityType, -}); -export type GetEntityEngineStatsRequestParamsInput = z.input< - typeof GetEntityEngineStatsRequestParams ->; - -export type GetEntityEngineStatsResponse = z.infer; -export const GetEntityEngineStatsResponse = z.object({ - type: EntityType.optional(), - indexPattern: IndexPattern.optional(), - status: EngineStatus.optional(), - transforms: z.array(z.object({})).optional(), - indices: z.array(z.object({})).optional(), -}); diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.schema.yaml deleted file mode 100644 index 25c010acc92ce..0000000000000 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/stats.schema.yaml +++ /dev/null @@ -1,41 +0,0 @@ -openapi: 3.0.0 - -info: - title: Get Entity Engine stats - version: '2023-10-31' -paths: - /api/entity_store/engines/{entityType}/stats: - post: - x-labels: [ess, serverless] - x-codegen-enabled: true - operationId: GetEntityEngineStats - summary: Get Entity Engine stats - parameters: - - name: entityType - in: path - required: true - schema: - $ref: '../common.schema.yaml#/components/schemas/EntityType' - description: The entity type of the engine (either 'user' or 'host'). - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: object - properties: - type: - $ref : '../common.schema.yaml#/components/schemas/EntityType' - indexPattern: - $ref : '../common.schema.yaml#/components/schemas/IndexPattern' - status: - $ref : '../common.schema.yaml#/components/schemas/EngineStatus' - transforms: - type: array - items: - type: object - indices: - type: array - items: - type: object diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stats.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stats.ts deleted file mode 100644 index 24785fbd5c015..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stats.ts +++ /dev/null @@ -1,64 +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 type { IKibanaResponse, Logger } from '@kbn/core/server'; -import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; -import { transformError } from '@kbn/securitysolution-es-utils'; -import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; - -import type { GetEntityEngineStatsResponse } from '../../../../../common/api/entity_analytics/entity_store/engine/stats.gen'; -import { GetEntityEngineStatsRequestParams } from '../../../../../common/api/entity_analytics/entity_store/engine/stats.gen'; -import { API_VERSIONS, APP_ID } from '../../../../../common/constants'; -import type { EntityAnalyticsRoutesDeps } from '../../types'; - -export const getEntityEngineStatsRoute = ( - router: EntityAnalyticsRoutesDeps['router'], - logger: Logger -) => { - router.versioned - .post({ - access: 'public', - path: '/api/entity_store/engines/{entityType}/stats', - security: { - authz: { - requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], - }, - }, - }) - .addVersion( - { - version: API_VERSIONS.public.v1, - validate: { - request: { - params: buildRouteValidationWithZod(GetEntityEngineStatsRequestParams), - }, - }, - }, - - async ( - context, - request, - response - ): Promise> => { - const siemResponse = buildSiemResponse(response); - - try { - // TODO - throw new Error('Not implemented'); - - // return response.ok({ body }); - } catch (e) { - logger.error('Error in GetEntityEngineStats:', e); - const error = transformError(e); - return siemResponse.error({ - statusCode: error.statusCode, - body: error.message, - }); - } - } - ); -}; From 6d6ce5ac4ae28006c6a64fd733eaf4bd824a4088 Mon Sep 17 00:00:00 2001 From: machadoum Date: Thu, 21 Nov 2024 17:44:03 +0100 Subject: [PATCH 03/19] Self-code review 1/10 --- .../entity_store/engine/list.schema.yaml | 1 - .../entity_store/entity_store_data_client.ts | 11 ----------- .../lib/entity_analytics/entity_store/routes/list.ts | 1 - 3 files changed, 13 deletions(-) diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.schema.yaml index 187718d609103..deee32a8b2bb7 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/engine/list.schema.yaml @@ -9,7 +9,6 @@ paths: x-codegen-enabled: true operationId: ListEntityEngines summary: List the Entity Engines - responses: '200': description: Successful response 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 d55f8b181cfed..2d3e3017a07d9 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 @@ -265,17 +265,6 @@ export class EntityStoreDataClient { } } - // [x] TS types and openAPI - // [x] check if this change should be part of another API - // [x] fix UI types - // [x] refactor UI - // TODO test for error state - // TODO API tests - // TODO Unit tests? - // TODO create openAPI doc and defines returned types interface EntityState {} - // TODO does calling `GET kbn:/api/entity_store/engines/user` on a clean cluster kill Kibana? - // TODO investigate why we list engines for user without saved object privileges (see message from Mark) - public async init( entityType: EntityType, { indexPattern = '', filter = '', fieldHistoryLength = 10 }: InitEntityEngineRequestBody, diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts index 519afdb9767bb..372331cea7087 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts @@ -39,7 +39,6 @@ export const listEntityEnginesRoute = ( try { const secSol = await context.securitySolution; - // should includeStatus be snake case? const body = await secSol.getEntityStoreDataClient().list(); return response.ok({ body }); From ae45c47b8d23ecf58ed5b5efc43ebcfd982d20f1 Mon Sep 17 00:00:00 2001 From: machadoum Date: Tue, 26 Nov 2024 13:30:37 +0100 Subject: [PATCH 04/19] Delete EntityStoreResource const in favout of OpenAPi enum --- .../entity_store/constants.ts | 14 ---------- .../component_template.ts | 8 ++++-- .../elasticsearch_assets/enrich_policy.ts | 4 +-- .../elasticsearch_assets/entity_index.ts | 9 ++++-- .../elasticsearch_assets/ingest_pipeline.ts | 4 +-- .../entity_store/entity_store_data_client.ts | 28 +++++++++---------- .../task/field_retention_enrichment_task.ts | 10 ++++--- 7 files changed, 35 insertions(+), 42 deletions(-) diff --git a/x-pack/plugins/security_solution/common/entity_analytics/entity_store/constants.ts b/x-pack/plugins/security_solution/common/entity_analytics/entity_store/constants.ts index 7b46a0a766e2e..b6834422c8cfc 100644 --- a/x-pack/plugins/security_solution/common/entity_analytics/entity_store/constants.ts +++ b/x-pack/plugins/security_solution/common/entity_analytics/entity_store/constants.ts @@ -23,17 +23,3 @@ export const ENTITY_STORE_REQUIRED_ES_CLUSTER_PRIVILEGES = [ // The index pattern for the entity store has to support '.entities.v1.latest.noop' index export const ENTITY_STORE_INDEX_PATTERN = '.entities.v1.latest.*'; - -export const EntityStoreResource = { - ENTITY_ENGINE: 'entity_engine', - ENTITY_DEFINITION: 'entity_definition', - INDEX: 'index', - COMPONENT_TEMPLATE: 'component_template', - INDEX_TEMPLATE: 'index_template', - INGEST_PIPELINE: 'ingest_pipeline', - ENRICH_POLICY: 'enrich_policy', - TASK: 'task', - TRANSFORM: 'transform', -} as const; - -export type EntityStoreResource = (typeof EntityStoreResource)[keyof typeof EntityStoreResource]; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/component_template.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/component_template.ts index f8e2de5a10749..a1352594ff47d 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/component_template.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/component_template.ts @@ -6,8 +6,10 @@ */ import type { ElasticsearchClient } from '@kbn/core/server'; -import type { EngineComponentStatus } from '../../../../../common/api/entity_analytics'; -import { EntityStoreResource } from '../../../../../common/entity_analytics/entity_store/constants'; +import { + EngineComponentResourceEnum, + type EngineComponentStatus, +} from '../../../../../common/api/entity_analytics'; import type { UnitedEntityDefinition } from '../united_entity_definitions'; const getComponentTemplateName = (definitionId: string) => `${definitionId}-latest@platform`; @@ -61,6 +63,6 @@ export const getEntityIndexComponentTemplateStatus = async ({ return { id: name, installed: componentTemplate?.component_templates?.length > 0, - resource: EntityStoreResource.COMPONENT_TEMPLATE, + resource: EngineComponentResourceEnum.component_template, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/enrich_policy.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/enrich_policy.ts index edf21219007e4..7064a4dd98851 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/enrich_policy.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/enrich_policy.ts @@ -7,7 +7,7 @@ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import type { EnrichPutPolicyRequest } from '@elastic/elasticsearch/lib/api/types'; -import { EntityStoreResource } from '../../../../../common/entity_analytics/entity_store/constants'; +import { EngineComponentResourceEnum } from '../../../../../common/api/entity_analytics'; import { getEntitiesIndexName } from '../utils'; import type { UnitedEntityDefinition } from '../united_entity_definitions'; @@ -121,6 +121,6 @@ export const getFieldRetentionEnrichPolicyStatus = async ({ return { installed: policies.length > 0, id: name, - resource: EntityStoreResource.ENRICH_POLICY, + resource: EngineComponentResourceEnum.enrich_policy, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/entity_index.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/entity_index.ts index f000fc16d6639..0de8fe20050ce 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/entity_index.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/entity_index.ts @@ -6,8 +6,11 @@ */ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; -import { EntityStoreResource } from '../../../../../common/entity_analytics/entity_store/constants'; -import type { EngineComponentStatus, EntityType } from '../../../../../common/api/entity_analytics'; +import { + EngineComponentResourceEnum, + type EngineComponentStatus, + type EntityType, +} from '../../../../../common/api/entity_analytics'; import { getEntitiesIndexName } from '../utils'; import { createOrUpdateIndex } from '../../utils/create_or_update_index'; @@ -53,5 +56,5 @@ export const getEntityIndexStatus = async ({ } ); - return { id: index, installed: exists, resource: EntityStoreResource.INDEX }; + return { id: index, installed: exists, resource: EngineComponentResourceEnum.index }; }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts index da96b672d2f5a..f57102fd13f14 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts @@ -8,7 +8,7 @@ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import type { IngestProcessorContainer } from '@elastic/elasticsearch/lib/api/types'; import type { EntityDefinition } from '@kbn/entities-schema'; -import { EntityStoreResource } from '../../../../../common/entity_analytics/entity_store/constants'; +import { EngineComponentResourceEnum } from '../../../../../common/api/entity_analytics'; import { type FieldRetentionDefinition } from '../field_retention_definition'; import { debugDeepCopyContextStep, @@ -183,6 +183,6 @@ export const getPlatformPipelineStatus = async ({ return { id: pipelineId, installed: !!pipeline[pipelineId], - resource: EntityStoreResource.INGEST_PIPELINE, + resource: EngineComponentResourceEnum.ingest_pipeline, }; }; 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 2d3e3017a07d9..c5c730eab700c 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 @@ -31,9 +31,8 @@ import type { InitEntityStoreRequestBody, InitEntityStoreResponse, } from '../../../../common/api/entity_analytics/entity_store/enable.gen'; -import { EntityStoreResource } from '../../../../common/entity_analytics/entity_store/constants'; import type { AppClient } from '../../..'; -import { EntityType } from '../../../../common/api/entity_analytics'; +import { EngineComponentResourceEnum, EntityType } from '../../../../common/api/entity_analytics'; import type { Entity, EngineDataviewUpdateResult, @@ -42,6 +41,7 @@ import type { InspectQuery, ListEntityEnginesResponse, EngineComponentStatus, + EngineComponentResource, } from '../../../../common/api/entity_analytics'; import { EngineDescriptorClient } from './saved_object/engine_descriptor'; import { ENGINE_STATUS, ENTITY_STORE_STATUS, MAX_SEARCH_RESPONSE_SIZE } from './constants'; @@ -299,7 +299,7 @@ export class EntityStoreDataClient { this.log('info', entityType, `Initializing entity store`); this.audit( EntityEngineActions.INIT, - EntityStoreResource.ENTITY_ENGINE, + EngineComponentResourceEnum.entity_engine, entityType, 'Initializing entity engine' ); @@ -428,7 +428,7 @@ export class EntityStoreDataClient { this.audit( EntityEngineActions.INIT, - EntityStoreResource.ENTITY_ENGINE, + EngineComponentResourceEnum.entity_engine, entityType, 'Failed to initialize entity engine resources', err @@ -460,7 +460,7 @@ export class EntityStoreDataClient { { id, installed: false, - resource: EntityStoreResource.ENTITY_DEFINITION, + resource: EngineComponentResourceEnum.entity_definition, }, ]; } @@ -470,11 +470,11 @@ export class EntityStoreDataClient { { id: definition.id, installed: definition.state.installed, - resource: EntityStoreResource.ENTITY_DEFINITION, + resource: EngineComponentResourceEnum.entity_definition, }, ...definition.state.components.transforms.map(({ installed, running, stats }) => ({ id, - resource: EntityStoreResource.TRANSFORM, + resource: EngineComponentResourceEnum.transform, installed, errors: (stats?.health as TransformHealth)?.issues?.map(({ issue, details }) => ({ title: issue, @@ -482,13 +482,13 @@ export class EntityStoreDataClient { })), })), ...definition.state.components.ingestPipelines.map((pipeline) => ({ - resource: EntityStoreResource.INGEST_PIPELINE, + resource: EngineComponentResourceEnum.ingest_pipeline, ...pipeline, })), ...definition.state.components.indexTemplates.map(({ installed }) => ({ id, installed, - resource: EntityStoreResource.INDEX_TEMPLATE, + resource: EngineComponentResourceEnum.index_template, })), ]; } @@ -527,7 +527,7 @@ export class EntityStoreDataClient { const fullEntityDefinition = await this.getExistingEntityDefinition(entityType); this.audit( EntityEngineActions.START, - EntityStoreResource.ENTITY_DEFINITION, + EngineComponentResourceEnum.entity_definition, entityType, 'Starting entity definition' ); @@ -554,7 +554,7 @@ export class EntityStoreDataClient { const fullEntityDefinition = await this.getExistingEntityDefinition(entityType); this.audit( EntityEngineActions.STOP, - EntityStoreResource.ENTITY_DEFINITION, + EngineComponentResourceEnum.entity_definition, entityType, 'Stopping entity definition' ); @@ -597,7 +597,7 @@ export class EntityStoreDataClient { this.log('info', entityType, `Deleting entity store`); this.audit( EntityEngineActions.DELETE, - EntityStoreResource.ENTITY_ENGINE, + EngineComponentResourceEnum.entity_engine, entityType, 'Deleting entity engine' ); @@ -665,7 +665,7 @@ export class EntityStoreDataClient { this.audit( EntityEngineActions.DELETE, - EntityStoreResource.ENTITY_ENGINE, + EngineComponentResourceEnum.entity_engine, entityType, 'Failed to delete entity engine', err @@ -812,7 +812,7 @@ export class EntityStoreDataClient { private audit( action: EntityEngineActions, - resource: EntityStoreResource, + resource: EngineComponentResource, entityType: EntityType, msg: string, error?: Error diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts index 7b79901bac482..3d103ea2af467 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts @@ -13,8 +13,10 @@ import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import { EntityStoreResource } from '../../../../../common/entity_analytics/entity_store/constants'; -import type { EntityType } from '../../../../../common/api/entity_analytics/entity_store'; +import { + EngineComponentResourceEnum, + type EntityType, +} from '../../../../../common/api/entity_analytics/entity_store'; import { defaultState, stateSchemaByVersion, @@ -290,7 +292,7 @@ export const getEntityStoreFieldRetentionEnrichTaskState = async ({ return { id: taskState.id, - resource: EntityStoreResource.TASK, + resource: EngineComponentResourceEnum.task, installed: true, enabled: taskState.enabled, status: taskState.status, @@ -304,7 +306,7 @@ export const getEntityStoreFieldRetentionEnrichTaskState = async ({ return { id: taskId, installed: false, - resource: EntityStoreResource.TASK, + resource: EngineComponentResourceEnum.task, }; } throw e; From 974494a34f736868ff267554eba537ed987fd319 Mon Sep 17 00:00:00 2001 From: machadoum Date: Tue, 26 Nov 2024 14:35:41 +0100 Subject: [PATCH 05/19] Add unit tests --- .../engine_components_status.test.tsx | 118 ++++++++++++++++++ .../components/engine_components_status.tsx | 6 +- .../engines_status/hooks/use_columns.tsx | 8 +- 3 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/components/engine_components_status.test.tsx diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/components/engine_components_status.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/components/engine_components_status.test.tsx new file mode 100644 index 0000000000000..e97c9f961bb50 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/components/engine_components_status.test.tsx @@ -0,0 +1,118 @@ +/* + * 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 { EngineComponentsStatusTable } from './engine_components_status'; +import { + EngineComponentResourceEnum, + type EngineComponentStatus, +} from '../../../../../../../common/api/entity_analytics'; +import { TestProviders } from '../../../../../../common/mock'; + +const uninstalledWithErrorsComponent = { + id: 'entity_engine_id', + installed: false, + resource: EngineComponentResourceEnum.entity_engine, + errors: [{ title: 'Error 1', message: 'Error message 1' }], +}; + +const installedComponent = { + id: 'index_id', + resource: EngineComponentResourceEnum.index, + errors: [], + installed: true, +}; + +const mockComponents: EngineComponentStatus[] = [ + uninstalledWithErrorsComponent, + installedComponent, +]; + +const mockGetUrlForApp = jest.fn(); + +jest.mock('../../../../../../common/lib/kibana', () => { + return { + useKibana: jest.fn().mockReturnValue({ + services: { + application: { + getUrlForApp: () => mockGetUrlForApp(), + navigateToApp: jest.fn(), + }, + }, + }), + }; +}); + +describe('EngineComponentsStatusTable', () => { + it('renders the table with components', () => { + render(, { wrapper: TestProviders }); + expect(screen.getByTestId('engine-components-status-table')).toBeInTheDocument(); + }); + + it('expands and collapses rows correctly', () => { + render(, { wrapper: TestProviders }); + + const toggleButton = screen.getByLabelText('Expand'); + fireEvent.click(toggleButton); + + expect(screen.getByText('Error 1')).toBeInTheDocument(); + expect(screen.getByText('Error message 1')).toBeInTheDocument(); + + fireEvent.click(toggleButton); + + expect(screen.queryByText('Error 1')).not.toBeInTheDocument(); + expect(screen.queryByText('Error message 1')).not.toBeInTheDocument(); + }); + + describe('columns', () => { + it('renders the correct resource text', () => { + render(, { + wrapper: TestProviders, + }); + expect(screen.getByText('Index')).toBeInTheDocument(); + }); + + it('renders checkmark on installation column when installed', () => { + render(, { + wrapper: TestProviders, + }); + + const icon = screen.getByTestId('installation-status'); + + expect(icon).toHaveAttribute('data-euiicon-type', 'check'); + }); + + it('renders cross on installation column when installed', () => { + render(, { + wrapper: TestProviders, + }); + + const icon = screen.getByTestId('installation-status'); + + expect(icon).toHaveAttribute('data-euiicon-type', 'cross'); + }); + + it('renders the correct health status', () => { + render(, { + wrapper: TestProviders, + }); + + expect(screen.queryByRole('img', { name: /health/i })).not.toBeInTheDocument(); + }); + + it('renders the correct identifier link', () => { + mockGetUrlForApp.mockReturnValue('mockedUrl'); + + render(, { + wrapper: TestProviders, + }); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', 'mockedUrl'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/components/engine_components_status.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/components/engine_components_status.tsx index 5f0f1df7fc464..eb789035d566f 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/components/engine_components_status.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/components/engine_components_status.tsx @@ -5,7 +5,7 @@ * 2.0. */ import type { ReactNode } from 'react'; -import React, { useState, useMemo, useCallback } from 'react'; +import React, { useState, useMemo, useCallback, Fragment } from 'react'; import { EuiSpacer, EuiHealth, EuiCodeBlock } from '@elastic/eui'; import { BasicTable } from '../../../../../../common/components/ml/tables/basic_table'; import { useColumns } from '../hooks/use_columns'; @@ -63,12 +63,12 @@ const TransformExtendedData = ({ errors }: { errors: EngineComponentStatus['erro return ( <> {errors?.map(({ title, message }) => ( - <> + {title} {message} - + ))} ); diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/hooks/use_columns.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/hooks/use_columns.tsx index 09a8cb101c87a..3156c768bf4fc 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/hooks/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/hooks/use_columns.tsx @@ -102,9 +102,9 @@ export const useColumns = ( align: 'center', render: (value: boolean) => value ? ( - + ) : ( - + ), }, { @@ -142,9 +142,7 @@ export const useColumns = ( render: (component: EngineComponentStatus) => { const isItemExpanded = expandedItems.includes(component); - return component.resource === EngineComponentResourceEnum.transform && - component.errors && - component.errors.length > 0 ? ( + return component.errors && component.errors.length > 0 ? ( onToggleExpandedItem(component)} aria-label={isItemExpanded ? 'Collapse' : 'Expand'} From 082721c4fca9c425928ccbdda3eadffe3ab3499d Mon Sep 17 00:00:00 2001 From: machadoum Date: Wed, 27 Nov 2024 14:20:43 +0100 Subject: [PATCH 06/19] Add error message to UI and improve unit --- .../components/engines_status/index.test.tsx | 100 +++++++++ .../components/engines_status/index.tsx | 104 ++++++---- .../hooks/use_entity_engine_status.ts | 1 - .../entity_store_management_page.test.tsx | 196 ++++++++++++++++++ .../pages/entity_store_management_page.tsx | 25 ++- .../entity_store.ts | 77 +++++-- 6 files changed, 424 insertions(+), 79 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.test.tsx diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.test.tsx new file mode 100644 index 0000000000000..fd1f17e2960be --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.test.tsx @@ -0,0 +1,100 @@ +/* + * 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 { EngineStatus } from '.'; + +import { TestProviders } from '@kbn/timelines-plugin/public/mock'; + +const mockUseEntityEngineStatusNew = jest.fn(); +jest.mock('../../hooks/use_entity_engine_status', () => ({ + useEntityEngineStatusNew: () => mockUseEntityEngineStatusNew(), +})); + +const mockDownloadBlob = jest.fn(); +jest.mock('../../../../../common/utils/download_blob', () => ({ + downloadBlob: () => mockDownloadBlob(), +})); + +describe('EngineStatus', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders loading spinner when data is loading', () => { + mockUseEntityEngineStatusNew.mockReturnValue({ + data: undefined, + isLoading: true, + error: null, + }); + + render(, { wrapper: TestProviders }); + + expect(screen.getByRole('progressbar')).toBeInTheDocument(); + }); + + it('renders error state when there is an error', () => { + mockUseEntityEngineStatusNew.mockReturnValue({ + data: null, + isLoading: false, + error: new Error('Error'), + }); + + render(, { wrapper: TestProviders }); + + expect(screen.getByText('There was an error loading the engine status')).toBeInTheDocument(); + }); + + it('renders "No engines found" message when there are no engines', () => { + mockUseEntityEngineStatusNew.mockReturnValue({ + data: { engines: [] }, + isLoading: false, + error: null, + }); + + render(, { wrapper: TestProviders }); + + expect(screen.getByText('No engines found')).toBeInTheDocument(); + }); + + it('renders engine components when data is available', () => { + const mockData = { + engines: [ + { + type: 'test', + components: [{ id: 'entity_engine_id', installed: true, resource: 'entity_engine' }], + }, + ], + }; + mockUseEntityEngineStatusNew.mockReturnValue({ data: mockData, isLoading: false, error: null }); + + render(, { wrapper: TestProviders }); + + expect(screen.getByText('Test Store')).toBeInTheDocument(); + expect(screen.getByText('Download status')).toBeInTheDocument(); + }); + + it('calls downloadJson when download button is clicked', () => { + const mockData = { + engines: [ + { + type: 'test', + components: [{ id: 'entity_engine_id', installed: true, resource: 'entity_engine' }], + }, + ], + }; + mockUseEntityEngineStatusNew.mockReturnValue({ data: mockData, isLoading: false, error: null }); + + render(, { wrapper: TestProviders }); + + const downloadButton = screen.getByText('Download status'); + fireEvent.click(downloadButton); + + expect(mockDownloadBlob).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.tsx index 3bc6274db1a09..90465165c61ad 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, Fragment } from 'react'; import { EuiLoadingSpinner, EuiPanel, @@ -14,23 +14,39 @@ import { EuiFlexItem, EuiTitle, EuiFlexGroup, + EuiCallOut, } from '@elastic/eui'; import { capitalize } from 'lodash/fp'; import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { useErrorToast } from '../../../../../common/hooks/use_error_toast'; import { downloadBlob } from '../../../../../common/utils/download_blob'; import { useEntityEngineStatusNew } from '../../hooks/use_entity_engine_status'; import { EngineComponentsStatusTable } from './components/engine_components_status'; +const FILE_NAME = 'engines_status.json'; + export const EngineStatus: React.FC = () => { const { data, isLoading, error } = useEntityEngineStatusNew({ withComponents: true }); const downloadJson = useCallback(() => { - downloadBlob(new Blob([JSON.stringify(data)]), 'engines_status.json'); + downloadBlob(new Blob([JSON.stringify(data)]), FILE_NAME); }, [data]); - if (!data || isLoading) return ; + const errorMessage = i18n.translate( + 'xpack.securitySolution.entityAnalytics.entityStore.enginesStatus.queryError', + { + defaultMessage: 'There was an error loading the engine status', + } + ); - if (error) return ; + useErrorToast(errorMessage, error); + + if (error) { + return ; + } + + if (!data || isLoading) return ; if (data.engines.length === 0) { return ( @@ -42,50 +58,46 @@ export const EngineStatus: React.FC = () => { } return ( - <> - - {data?.engines?.length > 0 && ( - - - - - - - - - - )} - - {(data?.engines ?? []).map((engine) => ( - <> - -

- -

-
+ + {data?.engines?.length > 0 && ( + + + + + + + + + + )} + + {(data?.engines ?? []).map((engine) => ( + + +

+ +

+
- + - - {engine.components && ( - - )} - + + {engine.components && } + - - - ))} -
-
- + + + ))} +
+
); }; 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 eaaf0f080802d..9a8c0bdeaef33 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 @@ -31,7 +31,6 @@ export const useEntityEngineStatusNew = (opts: Options = {}) => { queryKey: ['GET_ENTITY_STORE_STATUS', opts.withComponents], queryFn: () => getEntityStoreStatus(opts.withComponents), cacheTime: 0, - // enabled: !skip, refetchOnWindowFocus: true, keepPreviousData: true, }); diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.test.tsx new file mode 100644 index 0000000000000..7b81026fe90c2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.test.tsx @@ -0,0 +1,196 @@ +/* + * 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 { EntityStoreManagementPage } from './entity_store_management_page'; +import { TestProviders } from '../../common/mock'; + +jest.mock('../components/entity_store/components/engines_status', () => ({ + EngineStatus: () => {'Mocked Engine Status Tab'}, +})); + +const mockUseAssetCriticalityPrivileges = jest.fn(); +jest.mock('../components/asset_criticality/use_asset_criticality', () => ({ + useAssetCriticalityPrivileges: () => mockUseAssetCriticalityPrivileges(), +})); + +const mockUseIsExperimentalFeatureEnabled = jest.fn(); +jest.mock('../../common/hooks/use_experimental_features', () => ({ + useIsExperimentalFeatureEnabled: () => mockUseIsExperimentalFeatureEnabled(), +})); + +const mockUseHasSecurityCapability = jest.fn().mockReturnValue(true); +jest.mock('../../helper_hooks', () => ({ + useHasSecurityCapability: () => mockUseHasSecurityCapability(), +})); + +const mockUseEntityEngineStatus = jest.fn(); +jest.mock('../components/entity_store/hooks/use_entity_engine_status', () => ({ + useEntityEngineStatus: () => mockUseEntityEngineStatus(), +})); + +const mockUseEntityEnginePrivileges = jest.fn(); +jest.mock('../components/entity_store/hooks/use_entity_engine_privileges', () => ({ + useEntityEnginePrivileges: () => mockUseEntityEnginePrivileges(), +})); + +describe('EntityStoreManagementPage', () => { + beforeEach(() => { + jest.clearAllMocks(); + + mockUseAssetCriticalityPrivileges.mockReturnValue({ + isLoading: false, + data: { has_write_permissions: true }, + }); + + mockUseEntityEnginePrivileges.mockReturnValue({ + data: { has_all_required: true }, + }); + + mockUseEntityEngineStatus.mockReturnValue({ + status: 'enabled', + errors: [], + }); + + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + }); + + it('does not render page when asset criticality is loading', () => { + mockUseAssetCriticalityPrivileges.mockReturnValue({ + isLoading: true, + }); + + render(, { wrapper: TestProviders }); + + expect(screen.queryByTestId('entityStoreManagementPage')).not.toBeInTheDocument(); + }); + + it('renders the page header', () => { + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + + render(, { wrapper: TestProviders }); + + expect(screen.getByTestId('entityStoreManagementPage')).toBeInTheDocument(); + expect(screen.getByText('Entity Store')).toBeInTheDocument(); + }); + + it('disables the switch when status is loading', () => { + mockUseEntityEngineStatus.mockReturnValue({ + status: 'loading', + errors: [], + }); + + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + + render(, { wrapper: TestProviders }); + + expect(screen.getByTestId('entity-store-switch')).toBeDisabled(); + }); + + it('show clear entity data modal when clear data button clicked', () => { + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + + render(, { wrapper: TestProviders }); + + fireEvent.click(screen.getByText('Clear Entity Data')); + + expect(screen.getByText('Clear Entity data?')).toBeInTheDocument(); + }); + + it('renders the AssetCriticalityIssueCallout when there is an error', () => { + mockUseAssetCriticalityPrivileges.mockReturnValue({ + isLoading: false, + error: { body: { message: 'Error message', status_code: 403 } }, + }); + + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + + render(, { wrapper: TestProviders }); + + expect( + screen.getByText('Asset criticality CSV file upload functionality unavailable.') + ).toBeInTheDocument(); + expect(screen.getByText('Error message')).toBeInTheDocument(); + }); + + it('renders the InsufficientAssetCriticalityPrivilegesCallout when there are no write permissions', () => { + mockUseAssetCriticalityPrivileges.mockReturnValue({ + isLoading: false, + data: { has_write_permissions: false }, + }); + + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + + render(, { wrapper: TestProviders }); + + expect( + screen.getByText('Insufficient index privileges to perform CSV upload') + ).toBeInTheDocument(); + }); + + it('selects the Import tab by default', () => { + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + + render(, { wrapper: TestProviders }); + + expect(screen.getByText('Import Entities').parentNode).toHaveAttribute('aria-selected', 'true'); + }); + + it('switches to the Status tab when clicked', () => { + mockUseEntityEngineStatus.mockReturnValue({ + status: 'enabled', + errors: [], + }); + + mockUseEntityEnginePrivileges.mockReturnValue({ + data: { has_all_required: true }, + }); + + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + + render(, { wrapper: TestProviders }); + + fireEvent.click(screen.getByText('Engine Status')); + + expect(screen.getByText('Engine Status').parentNode).toHaveAttribute('aria-selected', 'true'); + }); + + it('does not render the Status tab when entity store is not installed', () => { + mockUseEntityEngineStatus.mockReturnValue({ + status: 'not_installed', + errors: [], + }); + + mockUseEntityEnginePrivileges.mockReturnValue({ + data: { has_all_required: true }, + }); + + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + + render(, { wrapper: TestProviders }); + + expect(screen.queryByText('Engine Status')).not.toBeInTheDocument(); + }); + + it('does not render the Status tab when privileges are missing', () => { + mockUseEntityEngineStatus.mockReturnValue({ + status: 'enabled', + errors: [], + }); + + mockUseEntityEnginePrivileges.mockReturnValue({ + data: { has_all_required: false, privileges: { kibana: {}, elasticsearch: {} } }, + }); + + mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + + render(, { wrapper: TestProviders }); + + expect(screen.queryByText('Engine Status')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx index 23e1d13bda3ca..3fc875131ca8c 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx @@ -55,7 +55,7 @@ const installedStatuses = ['stopped', 'enabled', 'error']; enum TabId { Import = 'import', - Resources = 'resources', + Status = 'status', } export const EntityStoreManagementPage = () => { @@ -269,8 +269,7 @@ export const EntityStoreManagementPage = () => { alignItems="center" rightSideItems={ !isEntityStoreFeatureFlagDisabled && privileges?.has_all_required - ? // !isEntityStoreFeatureFlagDisabled && true - [ + ? [ { />, canDeleteEntityEngine ? : null, ] - : [] + : undefined } /> @@ -311,16 +310,22 @@ export const EntityStoreManagementPage = () => { isSelected={selectedTabId === TabId.Import} onClick={() => setSelectedTabId(TabId.Import)} > - {'Import Entities'} + {installedStatuses.includes(entityStoreStatus.status) && privileges?.has_all_required && ( setSelectedTabId(TabId.Resources)} + key={TabId.Status} + isSelected={selectedTabId === TabId.Status} + onClick={() => setSelectedTabId(TabId.Status)} > - {'Engine Status'} + )} @@ -328,7 +333,7 @@ export const EntityStoreManagementPage = () => { {selectedTabId === TabId.Import && } - {selectedTabId === TabId.Resources && } + {selectedTabId === TabId.Status && } {initEntityEngineMutation.isError && ( diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts index 5d0dcec11d9b6..3bc451f86c147 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import { FtrProviderContext } from '../../../../ftr_provider_context'; import { EntityStoreUtils } from '../../utils'; import { dataViewRouteHelpersFactory } from '../../utils/data_view'; @@ -14,8 +14,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const utils = EntityStoreUtils(getService); - // Failing: See https://github.com/elastic/kibana/issues/200758 - describe.skip('@ess @skipInServerlessMKI Entity Store APIs', () => { + describe('@ess @skipInServerlessMKI Entity Store APIs', () => { const dataView = dataViewRouteHelpersFactory(supertest); before(async () => { @@ -85,7 +84,7 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(200); - expect(getResponse.body).to.eql({ + expect(getResponse.body).toEqual({ status: 'started', type: 'host', indexPattern: '', @@ -101,7 +100,7 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(200); - expect(getResponse.body).to.eql({ + expect(getResponse.body).toEqual({ status: 'started', type: 'user', indexPattern: '', @@ -118,7 +117,7 @@ export default ({ getService }: FtrProviderContext) => { // @ts-expect-error body is any const sortedEngines = body.engines.sort((a, b) => a.type.localeCompare(b.type)); - expect(sortedEngines).to.eql([ + expect(sortedEngines).toEqual([ { status: 'started', type: 'host', @@ -160,7 +159,7 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(200); - expect(body.status).to.eql('stopped'); + expect(body.status).toEqual('stopped'); }); it('should start the entity engine', async () => { @@ -176,7 +175,7 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(200); - expect(body.status).to.eql('started'); + expect(body.status).toEqual('started'); }); }); @@ -213,10 +212,11 @@ export default ({ getService }: FtrProviderContext) => { afterEach(async () => { await utils.cleanEngines(); }); + it('should return "not_installed" when no engines have been initialized', async () => { - const { body } = await api.getEntityStoreStatus().expect(200); + const { body } = await api.getEntityStoreStatus({ query: {} }).expect(200); - expect(body).to.eql({ + expect(body).toEqual({ engines: [], status: 'not_installed', }); @@ -225,23 +225,56 @@ export default ({ getService }: FtrProviderContext) => { it('should return "installing" when at least one engine is being initialized', async () => { await utils.enableEntityStore(); - const { body } = await api.getEntityStoreStatus().expect(200); + const { body } = await api.getEntityStoreStatus({ query: {} }).expect(200); - expect(body.status).to.eql('installing'); - expect(body.engines.length).to.eql(2); - expect(body.engines[0].status).to.eql('installing'); - expect(body.engines[1].status).to.eql('installing'); + expect(body.status).toEqual('installing'); + expect(body.engines.length).toEqual(2); + expect(body.engines[0].status).toEqual('installing'); + expect(body.engines[1].status).toEqual('installing'); }); it('should return "started" when all engines are started', async () => { await utils.initEntityEngineForEntityTypesAndWait(['host', 'user']); - const { body } = await api.getEntityStoreStatus().expect(200); + const { body } = await api.getEntityStoreStatus({ query: {} }).expect(200); + + expect(body.status).toEqual('running'); + expect(body.engines.length).toEqual(2); + expect(body.engines[0].status).toEqual('started'); + expect(body.engines[1].status).toEqual('started'); + }); - expect(body.status).to.eql('running'); - expect(body.engines.length).to.eql(2); - expect(body.engines[0].status).to.eql('started'); - expect(body.engines[1].status).to.eql('started'); + describe('status with components', () => { + it('should return empty list when when no engines have been initialized', async () => { + const { body } = await api + .getEntityStoreStatus({ query: { withComponents: true } }) + .expect(200); + + expect(body).toEqual({ + engines: [], + status: 'not_installed', + }); + }); + + it('should return components status when engines are installed', async () => { + await utils.initEntityEngineForEntityTypesAndWait(['host']); + + const { body } = await api + .getEntityStoreStatus({ query: { withComponents: true } }) + .expect(200); + + expect(body.engines[0].components).toEqual([ + expect.objectContaining({ resource: 'entity_definition' }), + expect.objectContaining({ resource: 'transform' }), + expect.objectContaining({ resource: 'ingest_pipeline' }), + expect.objectContaining({ resource: 'index_template' }), + expect.objectContaining({ resource: 'task' }), + expect.objectContaining({ resource: 'ingest_pipeline' }), + expect.objectContaining({ resource: 'enrich_policy' }), + expect.objectContaining({ resource: 'index' }), + expect.objectContaining({ resource: 'component_template' }), + ]); + }); }); }); @@ -262,14 +295,14 @@ export default ({ getService }: FtrProviderContext) => { it("should not update the index patten when it didn't change", async () => { const response = await api.applyEntityEngineDataviewIndices(); - expect(response.body).to.eql({ success: true, result: [{ type: 'host', changes: {} }] }); + expect(response.body).toEqual({ success: true, result: [{ type: 'host', changes: {} }] }); }); it('should update the index pattern when the data view changes', async () => { await dataView.updateIndexPattern('security-solution', 'test-*'); const response = await api.applyEntityEngineDataviewIndices(); - expect(response.body).to.eql({ + expect(response.body).toEqual({ success: true, result: [ { From 41edd60adcc4c2896ab306a144df43802467ce6f Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:30:12 +0000 Subject: [PATCH 07/19] [CI] Auto-commit changed files from 'yarn openapi:bundle' --- ...alytics_api_2023_10_31.bundled.schema.yaml | 96 ++++++++++++------- ...alytics_api_2023_10_31.bundled.schema.yaml | 96 ++++++++++++------- 2 files changed, 120 insertions(+), 72 deletions(-) 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 fa79b170f3513..772772f227ae8 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 @@ -430,41 +430,6 @@ paths: summary: Start an Entity Engine tags: - Security Entity Analytics API - /api/entity_store/engines/{entityType}/stats: - post: - operationId: GetEntityEngineStats - parameters: - - description: The entity type of the engine (either 'user' or 'host'). - in: path - name: entityType - required: true - schema: - $ref: '#/components/schemas/EntityType' - responses: - '200': - content: - application/json: - schema: - type: object - properties: - indexPattern: - $ref: '#/components/schemas/IndexPattern' - indices: - items: - type: object - type: array - status: - $ref: '#/components/schemas/EngineStatus' - transforms: - items: - type: object - type: array - type: - $ref: '#/components/schemas/EntityType' - description: Successful response - summary: Get Entity Engine stats - tags: - - Security Entity Analytics API /api/entity_store/engines/{entityType}/stop: post: operationId: StopEntityEngine @@ -615,6 +580,14 @@ paths: /api/entity_store/status: get: operationId: GetEntityStoreStatus + parameters: + - description: >- + If true returns a detailed status of the engine including all it's + components + in: query + name: withComponents + schema: + type: boolean responses: '200': content: @@ -624,10 +597,20 @@ paths: properties: engines: items: - $ref: '#/components/schemas/EngineDescriptor' + allOf: + - $ref: '#/components/schemas/EngineDescriptor' + - type: object + properties: + components: + items: + $ref: '#/components/schemas/EngineComponentStatus' + type: array type: array status: $ref: '#/components/schemas/StoreStatus' + required: + - status + - engines description: Successful response summary: Get the status of the Entity Store tags: @@ -824,6 +807,47 @@ components: $ref: '#/components/schemas/AssetCriticalityLevel' required: - criticality_level + EngineComponentResource: + enum: + - entity_engine + - entity_definition + - index + - component_template + - index_template + - ingest_pipeline + - enrich_policy + - task + - transform + type: string + EngineComponentStatus: + type: object + properties: + errors: + items: + type: object + properties: + message: + type: string + title: + type: string + type: array + health: + enum: + - green + - yellow + - red + - unknown + type: string + id: + type: string + installed: + type: boolean + resource: + $ref: '#/components/schemas/EngineComponentResource' + required: + - id + - installed + - resource EngineDataviewUpdateResult: type: object properties: 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 9c2b3d62b1650..1e4a3a4e28727 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 @@ -430,41 +430,6 @@ paths: summary: Start an Entity Engine tags: - Security Entity Analytics API - /api/entity_store/engines/{entityType}/stats: - post: - operationId: GetEntityEngineStats - parameters: - - description: The entity type of the engine (either 'user' or 'host'). - in: path - name: entityType - required: true - schema: - $ref: '#/components/schemas/EntityType' - responses: - '200': - content: - application/json: - schema: - type: object - properties: - indexPattern: - $ref: '#/components/schemas/IndexPattern' - indices: - items: - type: object - type: array - status: - $ref: '#/components/schemas/EngineStatus' - transforms: - items: - type: object - type: array - type: - $ref: '#/components/schemas/EntityType' - description: Successful response - summary: Get Entity Engine stats - tags: - - Security Entity Analytics API /api/entity_store/engines/{entityType}/stop: post: operationId: StopEntityEngine @@ -615,6 +580,14 @@ paths: /api/entity_store/status: get: operationId: GetEntityStoreStatus + parameters: + - description: >- + If true returns a detailed status of the engine including all it's + components + in: query + name: withComponents + schema: + type: boolean responses: '200': content: @@ -624,10 +597,20 @@ paths: properties: engines: items: - $ref: '#/components/schemas/EngineDescriptor' + allOf: + - $ref: '#/components/schemas/EngineDescriptor' + - type: object + properties: + components: + items: + $ref: '#/components/schemas/EngineComponentStatus' + type: array type: array status: $ref: '#/components/schemas/StoreStatus' + required: + - status + - engines description: Successful response summary: Get the status of the Entity Store tags: @@ -824,6 +807,47 @@ components: $ref: '#/components/schemas/AssetCriticalityLevel' required: - criticality_level + EngineComponentResource: + enum: + - entity_engine + - entity_definition + - index + - component_template + - index_template + - ingest_pipeline + - enrich_policy + - task + - transform + type: string + EngineComponentStatus: + type: object + properties: + errors: + items: + type: object + properties: + message: + type: string + title: + type: string + type: array + health: + enum: + - green + - yellow + - red + - unknown + type: string + id: + type: string + installed: + type: boolean + resource: + $ref: '#/components/schemas/EngineComponentResource' + required: + - id + - installed + - resource EngineDataviewUpdateResult: type: object properties: From 71996b14af03b8991c1dbcb87666fb06e4399b14 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:47:48 +0000 Subject: [PATCH 08/19] [CI] Auto-commit changed files from 'make api-docs' --- oas_docs/output/kibana.serverless.yaml | 95 ++++++++++++++++---------- oas_docs/output/kibana.yaml | 94 +++++++++++++++---------- 2 files changed, 116 insertions(+), 73 deletions(-) diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 68a5ccaff2e10..677ad28c7d4bc 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -7560,42 +7560,6 @@ paths: tags: - Security Entity Analytics API x-beta: true - /api/entity_store/engines/{entityType}/stats: - post: - operationId: GetEntityEngineStats - parameters: - - description: The entity type of the engine (either 'user' or 'host'). - in: path - name: entityType - required: true - schema: - $ref: '#/components/schemas/Security_Entity_Analytics_API_EntityType' - responses: - '200': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - type: object - properties: - indexPattern: - $ref: '#/components/schemas/Security_Entity_Analytics_API_IndexPattern' - indices: - items: - type: object - type: array - status: - $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineStatus' - transforms: - items: - type: object - type: array - type: - $ref: '#/components/schemas/Security_Entity_Analytics_API_EntityType' - description: Successful response - summary: Get Entity Engine stats - tags: - - Security Entity Analytics API - x-beta: true /api/entity_store/engines/{entityType}/stop: post: operationId: StopEntityEngine @@ -7749,6 +7713,12 @@ paths: /api/entity_store/status: get: operationId: GetEntityStoreStatus + parameters: + - description: If true returns a detailed status of the engine including all it's components + in: query + name: withComponents + schema: + type: boolean responses: '200': content: @@ -7758,10 +7728,20 @@ paths: properties: engines: items: - $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor' + allOf: + - $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor' + - type: object + properties: + components: + items: + $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineComponentStatus' + type: array type: array status: $ref: '#/components/schemas/Security_Entity_Analytics_API_StoreStatus' + required: + - status + - engines description: Successful response summary: Get the status of the Entity Store tags: @@ -45755,6 +45735,47 @@ components: $ref: '#/components/schemas/Security_Entity_Analytics_API_AssetCriticalityLevel' required: - criticality_level + Security_Entity_Analytics_API_EngineComponentResource: + enum: + - entity_engine + - entity_definition + - index + - component_template + - index_template + - ingest_pipeline + - enrich_policy + - task + - transform + type: string + Security_Entity_Analytics_API_EngineComponentStatus: + type: object + properties: + errors: + items: + type: object + properties: + message: + type: string + title: + type: string + type: array + health: + enum: + - green + - yellow + - red + - unknown + type: string + id: + type: string + installed: + type: boolean + resource: + $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineComponentResource' + required: + - id + - installed + - resource Security_Entity_Analytics_API_EngineDataviewUpdateResult: type: object properties: diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 208bced5d70f6..a06d73174b099 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -10445,41 +10445,6 @@ paths: summary: Start an Entity Engine tags: - Security Entity Analytics API - /api/entity_store/engines/{entityType}/stats: - post: - operationId: GetEntityEngineStats - parameters: - - description: The entity type of the engine (either 'user' or 'host'). - in: path - name: entityType - required: true - schema: - $ref: '#/components/schemas/Security_Entity_Analytics_API_EntityType' - responses: - '200': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - type: object - properties: - indexPattern: - $ref: '#/components/schemas/Security_Entity_Analytics_API_IndexPattern' - indices: - items: - type: object - type: array - status: - $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineStatus' - transforms: - items: - type: object - type: array - type: - $ref: '#/components/schemas/Security_Entity_Analytics_API_EntityType' - description: Successful response - summary: Get Entity Engine stats - tags: - - Security Entity Analytics API /api/entity_store/engines/{entityType}/stop: post: operationId: StopEntityEngine @@ -10630,6 +10595,12 @@ paths: /api/entity_store/status: get: operationId: GetEntityStoreStatus + parameters: + - description: If true returns a detailed status of the engine including all it's components + in: query + name: withComponents + schema: + type: boolean responses: '200': content: @@ -10639,10 +10610,20 @@ paths: properties: engines: items: - $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor' + allOf: + - $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor' + - type: object + properties: + components: + items: + $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineComponentStatus' + type: array type: array status: $ref: '#/components/schemas/Security_Entity_Analytics_API_StoreStatus' + required: + - status + - engines description: Successful response summary: Get the status of the Entity Store tags: @@ -53478,6 +53459,47 @@ components: $ref: '#/components/schemas/Security_Entity_Analytics_API_AssetCriticalityLevel' required: - criticality_level + Security_Entity_Analytics_API_EngineComponentResource: + enum: + - entity_engine + - entity_definition + - index + - component_template + - index_template + - ingest_pipeline + - enrich_policy + - task + - transform + type: string + Security_Entity_Analytics_API_EngineComponentStatus: + type: object + properties: + errors: + items: + type: object + properties: + message: + type: string + title: + type: string + type: array + health: + enum: + - green + - yellow + - red + - unknown + type: string + id: + type: string + installed: + type: boolean + resource: + $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineComponentResource' + required: + - id + - installed + - resource Security_Entity_Analytics_API_EngineDataviewUpdateResult: type: object properties: From 676a888d4778976c71c825a147046b1f73031240 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:10:37 +0000 Subject: [PATCH 09/19] [CI] Auto-commit changed files from 'yarn openapi:generate' --- .../common/api/quickstart_client.gen.ts | 19 ------------------- .../services/security_solution_api.gen.ts | 16 ---------------- 2 files changed, 35 deletions(-) diff --git a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts index 1a755df726a0d..016c77d7254dd 100644 --- a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts +++ b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts @@ -256,10 +256,6 @@ import type { StartEntityEngineRequestParamsInput, StartEntityEngineResponse, } from './entity_analytics/entity_store/engine/start.gen'; -import type { - GetEntityEngineStatsRequestParamsInput, - GetEntityEngineStatsResponse, -} from './entity_analytics/entity_store/engine/stats.gen'; import type { StopEntityEngineRequestParamsInput, StopEntityEngineResponse, @@ -1293,18 +1289,6 @@ finalize it. }) .catch(catchAxiosErrorFormatAndThrow); } - async getEntityEngineStats(props: GetEntityEngineStatsProps) { - this.log.info(`${new Date().toISOString()} Calling API GetEntityEngineStats`); - return this.kbnClient - .request({ - path: replaceParams('/api/entity_store/engines/{entityType}/stats', props.params), - headers: { - [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31', - }, - method: 'POST', - }) - .catch(catchAxiosErrorFormatAndThrow); - } async getEntityStoreStatus(props: GetEntityStoreStatusProps) { this.log.info(`${new Date().toISOString()} Calling API GetEntityStoreStatus`); return this.kbnClient @@ -2290,9 +2274,6 @@ export interface GetEndpointSuggestionsProps { export interface GetEntityEngineProps { params: GetEntityEngineRequestParamsInput; } -export interface GetEntityEngineStatsProps { - params: GetEntityEngineStatsRequestParamsInput; -} export interface GetEntityStoreStatusProps { query: GetEntityStoreStatusRequestQueryInput; } diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts index f2e9bcc8eac50..ed598c941ed56 100644 --- a/x-pack/test/api_integration/services/security_solution_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts @@ -80,7 +80,6 @@ import { GetEndpointSuggestionsRequestBodyInput, } from '@kbn/security-solution-plugin/common/api/endpoint/suggestions/get_suggestions.gen'; import { GetEntityEngineRequestParamsInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/get.gen'; -import { GetEntityEngineStatsRequestParamsInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/stats.gen'; import { GetEntityStoreStatusRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/status.gen'; import { GetNotesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_notes/get_notes_route.gen'; import { GetPolicyResponseRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy_response.gen'; @@ -833,18 +832,6 @@ finalize it. .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, - getEntityEngineStats(props: GetEntityEngineStatsProps, kibanaSpace: string = 'default') { - return supertest - .post( - routeWithNamespace( - replaceParams('/api/entity_store/engines/{entityType}/stats', props.params), - kibanaSpace - ) - ) - .set('kbn-xsrf', 'true') - .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); - }, getEntityStoreStatus(props: GetEntityStoreStatusProps, kibanaSpace: string = 'default') { return supertest .get(routeWithNamespace('/api/entity_store/status', kibanaSpace)) @@ -1617,9 +1604,6 @@ export interface GetEndpointSuggestionsProps { export interface GetEntityEngineProps { params: GetEntityEngineRequestParamsInput; } -export interface GetEntityEngineStatsProps { - params: GetEntityEngineStatsRequestParamsInput; -} export interface GetEntityStoreStatusProps { query: GetEntityStoreStatusRequestQueryInput; } From 54ee5d28c576f891f5bbb6ba4ee059ae6201a816 Mon Sep 17 00:00:00 2001 From: machadoum Date: Thu, 28 Nov 2024 10:01:57 +0100 Subject: [PATCH 10/19] Update i18n --- .../plugins/translations/translations/fr-FR.json | 8 +++----- .../plugins/translations/translations/ja-JP.json | 16 +++++++--------- .../plugins/translations/translations/zh-CN.json | 8 +++----- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 3157abff85051..cbf03d8b17bcb 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -26511,9 +26511,6 @@ "xpack.lens.editorFrame.suggestionPanelTitle": "Suggestions", "xpack.lens.editorFrame.tooManyDimensionsSingularWarningLabel": "Veuillez retirer {dimensionsTooMany, plural, one {une dimension} other {{dimensionsTooMany} dimensions}}", "xpack.lens.editorFrame.workspaceLabel": "Espace de travail", - "xpack.lens.featureBadge.iconDescription": "{count} {count, plural, one {modificateur} other {modificateurs}} de visualisation", - "xpack.lens.fixErrors": "Effectuez des modifications dans l'éditeur Lens pour corriger l'erreur", - "xpack.lens.moreErrors": "Effectuez des modifications dans l'éditeur Lens pour afficher plus d'erreurs", "xpack.lens.endValue.nearest": "La plus proche", "xpack.lens.endValue.none": "Masquer", "xpack.lens.endValue.zero": "Zéro", @@ -26521,6 +26518,7 @@ "xpack.lens.endValueDescription.none": "Ne pas étendre la série au bord du graphique", "xpack.lens.endValueDescription.zero": "Étendre la série au bord du graphique avec la valeur zéro", "xpack.lens.experimentalLabel": "Version d'évaluation technique", + "xpack.lens.featureBadge.iconDescription": "{count} {count, plural, one {modificateur} other {modificateurs}} de visualisation", "xpack.lens.fieldFormats.longSuffix.d": "par jour", "xpack.lens.fieldFormats.longSuffix.h": "par heure", "xpack.lens.fieldFormats.longSuffix.m": "par minute", @@ -26540,6 +26538,7 @@ "xpack.lens.fittingFunctionsTitle.lookahead": "Suivant", "xpack.lens.fittingFunctionsTitle.none": "Masquer", "xpack.lens.fittingFunctionsTitle.zero": "Zéro", + "xpack.lens.fixErrors": "Effectuez des modifications dans l'éditeur Lens pour corriger l'erreur", "xpack.lens.formula.disableWordWrapLabel": "Désactiver le renvoi à la ligne des mots", "xpack.lens.formula.editorHelpInlineHideLabel": "Masquer la référence des fonctions", "xpack.lens.formula.editorHelpInlineHideToolTip": "Masquer la référence des fonctions", @@ -27082,6 +27081,7 @@ "xpack.lens.modalTitle.title.deleteAnnotations": "Supprimer le groupe d'annotations ?", "xpack.lens.modalTitle.title.deleteReferenceLines": "Supprimer le calque de lignes de référence ?", "xpack.lens.modalTitle.title.deleteVis": "Supprimer le calque de visualisation ?", + "xpack.lens.moreErrors": "Effectuez des modifications dans l'éditeur Lens pour afficher plus d'erreurs", "xpack.lens.pageTitle": "Lens", "xpack.lens.paletteHeatmapGradient.label": "Couleur", "xpack.lens.paletteMetricGradient.label": "Couleur", @@ -40422,7 +40422,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.noPermissionTitle": "Privilèges d'index insuffisants pour effectuer le chargement de fichiers CSV", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.resultsStepTitle": "Résultats", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.selectFileStepTitle": "Sélectionner un fichier", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.subTitle": "Importer des entités à l'aide d'un fichier texte", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unavailable": "La fonctionnalité de chargement de fichiers CSV de criticité des ressources n'est pas disponible.", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unsupportedFileTypeError": "Format de fichier sélectionné non valide. Veuillez choisir un fichier {supportedFileExtensions} et réessayer", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.uploadFileSizeLimit": "La taille maximale de fichier est de : {maxFileSize}", @@ -40464,7 +40463,6 @@ "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntitiesModal.clearAllEntities": "Effacer toutes les entités", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntitiesModal.close": "Fermer", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntitiesModal.title": "Effacer les données des entités ?", - "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntityData": "Supprimez toutes les données d'entité extraites du stockage. Cette action supprimera définitivement les enregistrements d’utilisateur et d'hôte persistants, les données ne seront par ailleurs plus disponibles pour l'analyse. Procédez avec prudence, car cette opération est irréversible. Notez que cette opération ne supprimera pas les données sources, les scores de risque des entités ni les attributions de criticité des ressources.", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.featureFlagDisabled": "Les fonctionnalités du stockage d'entités ne sont pas disponibles", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.subTitle": "Permet un monitoring complet des hôtes et des utilisateurs de votre système.", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.title": "Stockage d'entités", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fa369745f0295..5d9a58d5b1e33 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -26483,13 +26483,6 @@ "xpack.lens.editorFrame.suggestionPanelTitle": "提案", "xpack.lens.editorFrame.tooManyDimensionsSingularWarningLabel": "{dimensionsTooMany, plural, other {{dimensionsTooMany} ディメンション}}を削除してください", "xpack.lens.editorFrame.workspaceLabel": "ワークスペース", - "xpack.lens.failure": "ビジュアライゼーションを表示できませんでした", - "xpack.lens.featureBadge.iconDescription": "{count}個のビジュアライゼーション{count, plural, other {修飾子}}", - "xpack.lens.fixErrors": "Lensエディターで編集し、エラーを修正", - "xpack.lens.legacyURLConflict.shortMessage": "URLの競合が発生しました", - "xpack.lens.missingTimeRangeParam.longMessage": "指定された構成にはtimeRangeプロパティが必須です", - "xpack.lens.missingTimeRangeParam.shortMessage": "timeRangeプロパティがありません", - "xpack.lens.moreErrors": "Lensエディターで編集すると、エラーの詳細が表示されます", "xpack.lens.endValue.nearest": "最も近い", "xpack.lens.endValue.none": "非表示", "xpack.lens.endValue.zero": "ゼロ", @@ -26497,6 +26490,8 @@ "xpack.lens.endValueDescription.none": "系列をグラフの端まで拡張しない", "xpack.lens.endValueDescription.zero": "系列をゼロとしてグラフの端まで拡張", "xpack.lens.experimentalLabel": "テクニカルプレビュー", + "xpack.lens.failure": "ビジュアライゼーションを表示できませんでした", + "xpack.lens.featureBadge.iconDescription": "{count}個のビジュアライゼーション{count, plural, other {修飾子}}", "xpack.lens.fieldFormats.longSuffix.d": "日単位", "xpack.lens.fieldFormats.longSuffix.h": "時間単位", "xpack.lens.fieldFormats.longSuffix.m": "分単位", @@ -26516,6 +26511,7 @@ "xpack.lens.fittingFunctionsTitle.lookahead": "次へ", "xpack.lens.fittingFunctionsTitle.none": "非表示", "xpack.lens.fittingFunctionsTitle.zero": "ゼロ", + "xpack.lens.fixErrors": "Lensエディターで編集し、エラーを修正", "xpack.lens.formula.disableWordWrapLabel": "単語の折り返しを無効にする", "xpack.lens.formula.editorHelpInlineHideLabel": "関数リファレンスを非表示", "xpack.lens.formula.editorHelpInlineHideToolTip": "関数リファレンスを非表示", @@ -26998,6 +26994,7 @@ "xpack.lens.legacyMetric.titlePositions.bottom": "一番下", "xpack.lens.legacyMetric.titlePositions.top": "トップ", "xpack.lens.legacyUrlConflict.objectNoun": "Lensビジュアライゼーション", + "xpack.lens.legacyURLConflict.shortMessage": "URLの競合が発生しました", "xpack.lens.lineCurve.smooth": "平滑化", "xpack.lens.lineCurve.step": "手順", "xpack.lens.lineCurve.straight": "直線", @@ -27052,11 +27049,14 @@ "xpack.lens.metric.supportingVisualization.none": "なし", "xpack.lens.metric.supportingVisualization.trendline": "折れ線", "xpack.lens.metric.timeField": "時間フィールド", + "xpack.lens.missingTimeRangeParam.longMessage": "指定された構成にはtimeRangeプロパティが必須です", + "xpack.lens.missingTimeRangeParam.shortMessage": "timeRangeプロパティがありません", "xpack.lens.modalTitle.revertAnnotationGroupTitle": "\"{title}\"の変更を元に戻しますか?", "xpack.lens.modalTitle.title.clearVis": "ビジュアライゼーションレイヤーを消去しますか?", "xpack.lens.modalTitle.title.deleteAnnotations": "注釈グループを削除しますか?", "xpack.lens.modalTitle.title.deleteReferenceLines": "基準線レイヤーを削除しますか?", "xpack.lens.modalTitle.title.deleteVis": "ビジュアライゼーションレイヤーを削除しますか?", + "xpack.lens.moreErrors": "Lensエディターで編集すると、エラーの詳細が表示されます", "xpack.lens.pageTitle": "Lens", "xpack.lens.paletteHeatmapGradient.label": "色", "xpack.lens.paletteMetricGradient.label": "色", @@ -40391,7 +40391,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.noPermissionTitle": "CSVアップロードを実行する十分なインデックス権限がありません", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.resultsStepTitle": "結果", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.selectFileStepTitle": "ファイルを選択", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.subTitle": "テキストファイルを使用してエンティティをインポート", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unavailable": "アセット重要度CSVファイルアップロード機能が利用できません。", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unsupportedFileTypeError": "無効なファイル形式が選択されました。{supportedFileExtensions}ファイルを選択して再試行してください。", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.uploadFileSizeLimit": "最大ファイルサイズ:{maxFileSize}", @@ -40433,7 +40432,6 @@ "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntitiesModal.clearAllEntities": "すべてのエンティティを消去", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntitiesModal.close": "閉じる", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntitiesModal.title": "エンティティデータを消去しますか?", - "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntityData": "すべての抽出されたエンティティデータをストアから削除します。このアクションにより、永続化されたユーザーおよびホストレコードが完全に削除され、データは分析で使用できなくなります。注意して続行してください。この操作は元に戻せません。この処理では、ソースデータ、エンティティリスクスコア、アセット重要度割り当ては削除されません。", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.featureFlagDisabled": "エンティティストア機能を利用できません", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.subTitle": "システムのホストとユーザーを包括的に監視できます。", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.title": "エンティティストア", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0a1477ee622ac..da15532b36e0c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -26012,9 +26012,6 @@ "xpack.lens.editorFrame.suggestionPanelTitle": "建议", "xpack.lens.editorFrame.tooManyDimensionsSingularWarningLabel": "请移除{dimensionsTooMany, plural, one {一个维度} other {{dimensionsTooMany} 个维度}}", "xpack.lens.editorFrame.workspaceLabel": "工作区", - "xpack.lens.featureBadge.iconDescription": "{count} 个可视化{count, plural, other {修饰符}}", - "xpack.lens.fixErrors": "在 Lens 编辑器中编辑以修复该错误", - "xpack.lens.moreErrors": "在 Lens 编辑器中编辑以查看更多错误", "xpack.lens.endValue.nearest": "最近", "xpack.lens.endValue.none": "隐藏", "xpack.lens.endValue.zero": "零", @@ -26022,6 +26019,7 @@ "xpack.lens.endValueDescription.none": "不将序列扩展到图表边缘", "xpack.lens.endValueDescription.zero": "将为零序列扩展到图表边缘", "xpack.lens.experimentalLabel": "技术预览", + "xpack.lens.featureBadge.iconDescription": "{count} 个可视化{count, plural, other {修饰符}}", "xpack.lens.fieldFormats.longSuffix.d": "每天", "xpack.lens.fieldFormats.longSuffix.h": "每小时", "xpack.lens.fieldFormats.longSuffix.m": "每分钟", @@ -26041,6 +26039,7 @@ "xpack.lens.fittingFunctionsTitle.lookahead": "下一个", "xpack.lens.fittingFunctionsTitle.none": "隐藏", "xpack.lens.fittingFunctionsTitle.zero": "零", + "xpack.lens.fixErrors": "在 Lens 编辑器中编辑以修复该错误", "xpack.lens.formula.disableWordWrapLabel": "禁用自动换行", "xpack.lens.formula.editorHelpInlineHideLabel": "隐藏函数引用", "xpack.lens.formula.editorHelpInlineHideToolTip": "隐藏函数引用", @@ -26583,6 +26582,7 @@ "xpack.lens.modalTitle.title.deleteAnnotations": "删除标注组?", "xpack.lens.modalTitle.title.deleteReferenceLines": "删除参考线图层?", "xpack.lens.modalTitle.title.deleteVis": "删除可视化图层?", + "xpack.lens.moreErrors": "在 Lens 编辑器中编辑以查看更多错误", "xpack.lens.pageTitle": "Lens", "xpack.lens.paletteHeatmapGradient.label": "颜色", "xpack.lens.paletteMetricGradient.label": "颜色", @@ -39762,7 +39762,6 @@ "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.noPermissionTitle": "索引权限不足,无法执行 CSV 上传", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.resultsStepTitle": "结果", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.selectFileStepTitle": "选择文件", - "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.subTitle": "使用文本文件导入实体", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unavailable": "资产关键度 CSV 文件上传功能不可用。", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.unsupportedFileTypeError": "选定的文件格式无效。请选择 {supportedFileExtensions} 文件,然后重试", "xpack.securitySolution.entityAnalytics.assetCriticalityUploadPage.uploadFileSizeLimit": "最大文件大小:{maxFileSize}", @@ -39804,7 +39803,6 @@ "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntitiesModal.clearAllEntities": "清除所有实体", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntitiesModal.close": "关闭", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntitiesModal.title": "清除实体数据?", - "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.clearEntityData": "移除从仓库中提取的所有实体数据。此操作将永久删除持久化用户和主机记录,并且数据将不再可用于分析。请谨慎操作,因为此操作无法撤消。请注意,此操作不会删除源数据、实体风险分数或资产关键度分配。", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.featureFlagDisabled": "实体仓库功能不可用", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.subTitle": "允许全面监测您系统的主机和用户。", "xpack.securitySolution.entityAnalytics.entityStoreManagementPage.title": "实体仓库", From be4d9fa94ec8e078b03e6b5087ddc41575c6087b Mon Sep 17 00:00:00 2001 From: machadoum Date: Thu, 28 Nov 2024 10:04:08 +0100 Subject: [PATCH 11/19] Remove unucessary change --- .../entity_analytics/entity_store/entity_store_data_client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c5c730eab700c..44c7981f98515 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 @@ -564,8 +564,8 @@ export class EntityStoreDataClient { return this.engineClient.updateStatus(entityType, ENGINE_STATUS.STOPPED); } - public async get(entityType: EntityType, includeState = false) { - return { engine: this.engineClient.get(entityType) }; + public async get(entityType: EntityType) { + return this.engineClient.get(entityType); } public async list(): Promise { From e00727797fcdc96bcbf810073f8666a73a1b8b00 Mon Sep 17 00:00:00 2001 From: machadoum Date: Thu, 28 Nov 2024 16:24:41 +0100 Subject: [PATCH 12/19] Please code review --- .../entity_store/components/engines_status/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.tsx index 7623f9a403c89..692841334bd50 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, Fragment } from 'react'; +import React, { Fragment } from 'react'; import { EuiLoadingSpinner, EuiPanel, @@ -29,9 +29,9 @@ const FILE_NAME = 'engines_status.json'; export const EngineStatus: React.FC = () => { const { data, isLoading, error } = useEntityStoreStatus({ withComponents: true }); - const downloadJson = useCallback(() => { + const downloadJson = () => { downloadBlob(new Blob([JSON.stringify(data)]), FILE_NAME); - }, [data]); + }; const errorMessage = i18n.translate( 'xpack.securitySolution.entityAnalytics.entityStore.enginesStatus.queryError', From bdb9a091064a92d5973949c493a9129191f6f3e0 Mon Sep 17 00:00:00 2001 From: machadoum Date: Thu, 28 Nov 2024 17:03:01 +0100 Subject: [PATCH 13/19] Fix conflict issues --- .../pages/entity_store_management_page.test.tsx | 6 +++--- .../entity_analytics/pages/entity_store_management_page.tsx | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.test.tsx index 5bd27d13ecfe5..a4cf70b47f4a3 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.test.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.test.tsx @@ -53,7 +53,7 @@ describe('EntityStoreManagementPage', () => { }); mockUseEntityEngineStatus.mockReturnValue({ - status: 'enabled', + status: 'running', errors: [], }); @@ -143,7 +143,7 @@ describe('EntityStoreManagementPage', () => { it('switches to the Status tab when clicked', () => { mockUseEntityEngineStatus.mockReturnValue({ - status: 'enabled', + status: 'running', errors: [], }); @@ -179,7 +179,7 @@ describe('EntityStoreManagementPage', () => { it('does not render the Status tab when privileges are missing', () => { mockUseEntityEngineStatus.mockReturnValue({ - status: 'enabled', + status: 'running', errors: [], }); diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx index be21fd45f2623..b40a1562b1c3e 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx @@ -60,8 +60,7 @@ const isSwitchDisabled = (status?: StoreStatus) => status === 'error' || status const isEntityStoreEnabled = (status?: StoreStatus) => status === 'running'; const canDeleteEntityEngine = (status?: StoreStatus) => !['not_installed', 'installing'].includes(status || ''); -const isEntityStoreInstalled = (status?: StoreStatus) => - ['stopped', 'enabled', 'error'].includes(status || ''); +const isEntityStoreInstalled = (status?: StoreStatus) => status && status !== 'not_installed'; export const EntityStoreManagementPage = () => { const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); From 5d5386fc76aeae38c44b00169157a0b54afaf96e Mon Sep 17 00:00:00 2001 From: machadoum Date: Thu, 28 Nov 2024 17:08:59 +0100 Subject: [PATCH 14/19] Rename API flag to include_components --- .../common/api/entity_analytics/entity_store/status.gen.ts | 2 +- .../common/api/entity_analytics/entity_store/status.schema.yaml | 2 +- .../public/entity_analytics/api/entity_store.ts | 2 +- .../entity_analytics/entity_store/entity_store_data_client.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.gen.ts index f4ac47b51033a..76249a787a43b 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.gen.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.gen.ts @@ -24,7 +24,7 @@ export const GetEntityStoreStatusRequestQuery = z.object({ /** * If true returns a detailed status of the engine including all it's components */ - withComponents: BooleanFromString.optional(), + include_components: BooleanFromString.optional(), }); export type GetEntityStoreStatusRequestQueryInput = z.input< typeof GetEntityStoreStatusRequestQuery diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.schema.yaml index 1ac67d192b0dd..a1be66282328e 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/status.schema.yaml @@ -12,7 +12,7 @@ paths: summary: Get the status of the Entity Store parameters: - - name: withComponents + - name: include_components in: query schema: type: boolean diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts index 981f90d9401ca..0098b842748e2 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/entity_store.ts @@ -38,7 +38,7 @@ export const useEntityStoreRoutes = () => { return http.fetch('/api/entity_store/status', { method: 'GET', version: API_VERSIONS.public.v1, - query: { withComponents }, + query: { include_components: withComponents }, }); }; 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 44c7981f98515..72d650846fc55 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 @@ -215,7 +215,7 @@ export class EntityStoreDataClient { } public async status({ - withComponents = false, + include_components: withComponents = false, }: GetEntityStoreStatusRequestQuery): Promise { const { namespace } = this.options; const { engines, count } = await this.engineClient.list(); From de9e7cda86d70240b3301df74d73f96c940e8286 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:27:47 +0000 Subject: [PATCH 15/19] [CI] Auto-commit changed files from 'yarn openapi:bundle' --- ...solution_entity_analytics_api_2023_10_31.bundled.schema.yaml | 2 +- ...solution_entity_analytics_api_2023_10_31.bundled.schema.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 772772f227ae8..b1b85b8222786 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 @@ -585,7 +585,7 @@ paths: If true returns a detailed status of the engine including all it's components in: query - name: withComponents + name: include_components schema: type: boolean responses: 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 1e4a3a4e28727..4a3b3495467e9 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 @@ -585,7 +585,7 @@ paths: If true returns a detailed status of the engine including all it's components in: query - name: withComponents + name: include_components schema: type: boolean responses: From 93ee4e2571fcdb0a2d33d04ce07fc61c21bc1e98 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:47:23 +0000 Subject: [PATCH 16/19] [CI] Auto-commit changed files from 'make api-docs' --- oas_docs/output/kibana.serverless.yaml | 2 +- oas_docs/output/kibana.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 677ad28c7d4bc..959c4d886eee2 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -7716,7 +7716,7 @@ paths: parameters: - description: If true returns a detailed status of the engine including all it's components in: query - name: withComponents + name: include_components schema: type: boolean responses: diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index a06d73174b099..0b5b03378168e 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -10598,7 +10598,7 @@ paths: parameters: - description: If true returns a detailed status of the engine including all it's components in: query - name: withComponents + name: include_components schema: type: boolean responses: From de00917e8895e61a3415a7c1bc4cc4283da8ab8b Mon Sep 17 00:00:00 2001 From: machadoum Date: Fri, 29 Nov 2024 09:43:46 +0100 Subject: [PATCH 17/19] Fix CI --- .../components/engines_status/index.test.tsx | 16 ++++++++-------- .../trial_license_complete_tier/entity_store.ts | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.test.tsx index fd1f17e2960be..2f9e74df39d39 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.test.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/engines_status/index.test.tsx @@ -11,9 +11,9 @@ import { EngineStatus } from '.'; import { TestProviders } from '@kbn/timelines-plugin/public/mock'; -const mockUseEntityEngineStatusNew = jest.fn(); -jest.mock('../../hooks/use_entity_engine_status', () => ({ - useEntityEngineStatusNew: () => mockUseEntityEngineStatusNew(), +const mockUseEntityStore = jest.fn(); +jest.mock('../../hooks/use_entity_store', () => ({ + useEntityStoreStatus: () => mockUseEntityStore(), })); const mockDownloadBlob = jest.fn(); @@ -27,7 +27,7 @@ describe('EngineStatus', () => { }); it('renders loading spinner when data is loading', () => { - mockUseEntityEngineStatusNew.mockReturnValue({ + mockUseEntityStore.mockReturnValue({ data: undefined, isLoading: true, error: null, @@ -39,7 +39,7 @@ describe('EngineStatus', () => { }); it('renders error state when there is an error', () => { - mockUseEntityEngineStatusNew.mockReturnValue({ + mockUseEntityStore.mockReturnValue({ data: null, isLoading: false, error: new Error('Error'), @@ -51,7 +51,7 @@ describe('EngineStatus', () => { }); it('renders "No engines found" message when there are no engines', () => { - mockUseEntityEngineStatusNew.mockReturnValue({ + mockUseEntityStore.mockReturnValue({ data: { engines: [] }, isLoading: false, error: null, @@ -71,7 +71,7 @@ describe('EngineStatus', () => { }, ], }; - mockUseEntityEngineStatusNew.mockReturnValue({ data: mockData, isLoading: false, error: null }); + mockUseEntityStore.mockReturnValue({ data: mockData, isLoading: false, error: null }); render(, { wrapper: TestProviders }); @@ -88,7 +88,7 @@ describe('EngineStatus', () => { }, ], }; - mockUseEntityEngineStatusNew.mockReturnValue({ data: mockData, isLoading: false, error: null }); + mockUseEntityStore.mockReturnValue({ data: mockData, isLoading: false, error: null }); render(, { wrapper: TestProviders }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts index 3bc451f86c147..104fbf05b5159 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts @@ -247,7 +247,7 @@ export default ({ getService }: FtrProviderContext) => { describe('status with components', () => { it('should return empty list when when no engines have been initialized', async () => { const { body } = await api - .getEntityStoreStatus({ query: { withComponents: true } }) + .getEntityStoreStatus({ query: { include_components: true } }) .expect(200); expect(body).toEqual({ @@ -260,7 +260,7 @@ export default ({ getService }: FtrProviderContext) => { await utils.initEntityEngineForEntityTypesAndWait(['host']); const { body } = await api - .getEntityStoreStatus({ query: { withComponents: true } }) + .getEntityStoreStatus({ query: { include_components: true } }) .expect(200); expect(body.engines[0].components).toEqual([ From 1196c6dee2a3d9f7b40c1d245a8088b07abdf27f Mon Sep 17 00:00:00 2001 From: machadoum Date: Fri, 29 Nov 2024 10:40:01 +0100 Subject: [PATCH 18/19] Fix UI issues --- .../entity_store/hooks/use_entity_store.ts | 9 ++++++--- .../pages/entity_store_management_page.tsx | 13 +++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) 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 88001ec83c90c..ceb93d164af62 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 @@ -106,15 +106,18 @@ export const useStopEntityEngineMutation = (options?: UseMutationOptions<{}>) => }; export const DELETE_ENTITY_ENGINE_STATUS_KEY = ['POST', 'STOP_ENTITY_ENGINE']; -export const useDeleteEntityEngineMutation = (options?: UseMutationOptions<{}>) => { +export const useDeleteEntityEngineMutation = ({ onSuccess }: { onSuccess?: () => void }) => { const queryClient = useQueryClient(); const { deleteEntityEngine } = useEntityStoreRoutes(); + return useMutation( () => Promise.all([deleteEntityEngine('user', true), deleteEntityEngine('host', true)]), { mutationKey: DELETE_ENTITY_ENGINE_STATUS_KEY, - onSuccess: () => queryClient.refetchQueries({ queryKey: ENTITY_STORE_STATUS }), - ...options, + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ENTITY_STORE_STATUS }); + onSuccess?.(); + }, } ); }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx index b40a1562b1c3e..d1b7f9afd5aad 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx @@ -29,7 +29,7 @@ import { EuiButtonEmpty, } from '@elastic/eui'; import type { ReactNode } from 'react'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import type { SecurityAppError } from '@kbn/securitysolution-t-grid'; @@ -100,6 +100,15 @@ export const EntityStoreManagementPage = () => { const { data: privileges } = useEntityEnginePrivileges(); + const shouldDisplayEngineStatusTab = + isEntityStoreInstalled(entityStoreStatus.data?.status) && privileges?.has_all_required; + + useEffect(() => { + if (selectedTabId === TabId.Status && !shouldDisplayEngineStatusTab) { + setSelectedTabId(TabId.Import); + } + }, [shouldDisplayEngineStatusTab, selectedTabId]); + if (assetCriticalityIsLoading) { // Wait for permission before rendering content to avoid flickering return null; @@ -202,7 +211,7 @@ export const EntityStoreManagementPage = () => { /> - {isEntityStoreInstalled(entityStoreStatus.data?.status) && privileges?.has_all_required && ( + {shouldDisplayEngineStatusTab && ( Date: Fri, 29 Nov 2024 13:42:05 +0100 Subject: [PATCH 19/19] Fix unit test --- .../entity_store_management_page.test.tsx | 37 ++++++++++++------- .../pages/entity_store_management_page.tsx | 8 ++-- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.test.tsx index a4cf70b47f4a3..cc15a5360a3d1 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.test.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.test.tsx @@ -29,9 +29,10 @@ jest.mock('../../helper_hooks', () => ({ useHasSecurityCapability: () => mockUseHasSecurityCapability(), })); -const mockUseEntityEngineStatus = jest.fn(); +const mockUseEntityStoreStatus = jest.fn(); jest.mock('../components/entity_store/hooks/use_entity_store', () => ({ - useEntityStoreStatus: () => mockUseEntityEngineStatus(), + ...jest.requireActual('../components/entity_store/hooks/use_entity_store'), + useEntityStoreStatus: () => mockUseEntityStoreStatus(), })); const mockUseEntityEnginePrivileges = jest.fn(); @@ -52,8 +53,10 @@ describe('EntityStoreManagementPage', () => { data: { has_all_required: true }, }); - mockUseEntityEngineStatus.mockReturnValue({ - status: 'running', + mockUseEntityStoreStatus.mockReturnValue({ + data: { + status: 'running', + }, errors: [], }); @@ -79,9 +82,11 @@ describe('EntityStoreManagementPage', () => { expect(screen.getByText('Entity Store')).toBeInTheDocument(); }); - it('disables the switch when status is loading', () => { - mockUseEntityEngineStatus.mockReturnValue({ - status: 'loading', + it('disables the switch when status is installing', () => { + mockUseEntityStoreStatus.mockReturnValue({ + data: { + status: 'installing', + }, errors: [], }); @@ -142,8 +147,10 @@ describe('EntityStoreManagementPage', () => { }); it('switches to the Status tab when clicked', () => { - mockUseEntityEngineStatus.mockReturnValue({ - status: 'running', + mockUseEntityStoreStatus.mockReturnValue({ + data: { + status: 'running', + }, errors: [], }); @@ -161,8 +168,10 @@ describe('EntityStoreManagementPage', () => { }); it('does not render the Status tab when entity store is not installed', () => { - mockUseEntityEngineStatus.mockReturnValue({ - status: 'not_installed', + mockUseEntityStoreStatus.mockReturnValue({ + data: { + status: 'not_installed', + }, errors: [], }); @@ -178,8 +187,10 @@ describe('EntityStoreManagementPage', () => { }); it('does not render the Status tab when privileges are missing', () => { - mockUseEntityEngineStatus.mockReturnValue({ - status: 'running', + mockUseEntityStoreStatus.mockReturnValue({ + data: { + status: 'running', + }, errors: [], }); diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx index d1b7f9afd5aad..c0b9d8d697e53 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx @@ -421,10 +421,8 @@ const InsufficientAssetCriticalityPrivilegesCallout: React.FC = () => { ); }; -const AssetCriticalityIssueCallout: React.FC = ({ +const AssetCriticalityIssueCallout: React.FC<{ errorMessage?: string | ReactNode }> = ({ errorMessage, -}: { - errorMessage?: string | ReactNode; }) => { const msg = errorMessage ?? ( { if (!hasEntityAnalyticsCapability || assetCriticalityPrivilegesError?.body.status_code === 403) { - return ; + return ( + + ); } if (!hasAssetCriticalityWritePermissions) { return ;