From bd1dfd2016b6721753c38feb58bf1e15f8d6d388 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Thu, 25 Jul 2024 14:30:03 -0600 Subject: [PATCH 01/12] [EEM] UI Poc - Adding entities to detail along with sorting and pagination - Moving create to the same spot as the rest of the specs - Adding transform details - Adding transform messages - Adding entity listing - Fixing rebase --- packages/deeplinks/observability/constants.ts | 2 + .../deeplinks/observability/deep_links.ts | 3 + x-pack/.i18nrc.json | 1 + .../packages/kbn-data-forge/src/constants.ts | 1 + .../src/data_sources/fake_stack_ds/index.ts | 69 +++++++++ .../kbn-data-forge/src/data_sources/index.ts | 6 +- .../kbn-data-forge/src/types/index.ts | 3 +- x-pack/packages/kbn-entities-schema/index.ts | 11 +- .../src/rest_spec/{ => definition}/create.ts | 0 .../src/rest_spec/{ => definition}/delete.ts | 0 .../src/rest_spec/definition/find.ts | 59 +++++++ .../src/rest_spec/{ => definition}/get.ts | 7 +- .../src/rest_spec/{ => definition}/reset.ts | 0 .../src/rest_spec/enablement/check.ts | 14 ++ .../src/rest_spec/entities/find.test.ts | 26 ++++ .../src/rest_spec/entities/find.ts | 43 ++++++ .../kbn-entities-schema/src/schema/entity.ts | 4 + .../entity_manager/common/locators/paths.ts | 17 ++ .../entity_manager/common/translations.ts | 12 ++ x-pack/plugins/entity_manager/kibana.jsonc | 26 +++- .../entity_manager/public/application.tsx | 131 ++++++++++++++++ .../public/components/badges.tsx | 79 ++++++++++ .../public/components/entities_listing.tsx | 146 ++++++++++++++++++ .../public/components/pagination.tsx | 127 +++++++++++++++ .../public/components/rows_per_page.tsx | 93 +++++++++++ .../public/components/stats/entity_count.tsx | 27 ++++ .../stats/history_checkpoint_duration.tsx | 34 ++++ .../public/components/stats/history_count.tsx | 27 ++++ .../public/components/stats/last_seen.tsx | 25 +++ .../stats/latest_checkpoint_duration.tsx | 34 ++++ .../public/components/stats/types.ts | 15 ++ .../public/context/plugin_context.tsx | 19 +++ .../public/hooks/query_key_factory.ts | 35 +++++ .../public/hooks/use_enable_enablement.ts | 26 ++++ .../hooks/use_fetch_enablement_status.ts | 35 +++++ .../public/hooks/use_fetch_entities.ts | 52 +++++++ .../hooks/use_fetch_entity_definition.ts | 36 +++++ .../hooks/use_fetch_entity_definitions.ts | 33 ++++ .../hooks/use_fetch_transform_messages.ts | 50 ++++++ .../entity_manager/public/hooks/use_kibana.ts | 22 +++ .../public/hooks/use_plugin_context.ts | 19 +++ .../components/built_in_definition_notice.tsx | 55 +++++++ .../create_entity_definition_btn.tsx | 20 +++ .../components/definition_listing.tsx | 97 ++++++++++++ .../pages/entity_manager/entity_manager.tsx | 86 +++++++++++ .../components/definition_details.tsx | 40 +++++ .../components/entities.tsx | 32 ++++ .../components/header_details.tsx | 23 +++ .../components/pagination.tsx | 127 +++++++++++++++ .../components/rows_per_page.tsx | 93 +++++++++++ .../components/transform.tsx | 95 ++++++++++++ .../components/transform_content.tsx | 96 ++++++++++++ .../components/transform_details_panel.tsx | 100 ++++++++++++ .../components/transform_health_badge.tsx | 45 ++++++ .../components/transform_messages.tsx | 118 ++++++++++++++ .../components/transform_stats_panel.tsx | 51 ++++++ .../components/transform_status_badge.tsx | 73 +++++++++ .../components/transforms.tsx | 39 +++++ .../entity_manager_detail.tsx | 62 ++++++++ .../plugins/entity_manager/public/plugin.ts | 61 +++++++- .../plugins/entity_manager/public/routes.tsx | 34 ++++ x-pack/plugins/entity_manager/public/types.ts | 46 +++++- .../server/lib/entities/find_entities.ts | 81 ++++++++++ .../entities/get_entity_definition_stats.ts | 70 +++++++++ .../server/lib/entity_client.ts | 26 +++- .../entities/{ => definition}/create.ts | 12 +- .../entities/{ => definition}/delete.ts | 8 +- .../entities/{get.ts => definition/find.ts} | 17 +- .../server/routes/entities/definition/get.ts | 74 +++++++++ .../routes/entities/{ => definition}/reset.ts | 27 ++-- .../entities/{ => definition}/update.ts | 12 +- .../server/routes/entities/find.ts | 26 ++++ .../server/routes/entities/index.ts | 17 +- x-pack/plugins/entity_manager/tsconfig.json | 31 ++-- .../observability/public/plugin.ts | 12 ++ 75 files changed, 3108 insertions(+), 67 deletions(-) create mode 100644 x-pack/packages/kbn-data-forge/src/data_sources/fake_stack_ds/index.ts rename x-pack/packages/kbn-entities-schema/src/rest_spec/{ => definition}/create.ts (100%) rename x-pack/packages/kbn-entities-schema/src/rest_spec/{ => definition}/delete.ts (100%) create mode 100644 x-pack/packages/kbn-entities-schema/src/rest_spec/definition/find.ts rename x-pack/packages/kbn-entities-schema/src/rest_spec/{ => definition}/get.ts (79%) rename x-pack/packages/kbn-entities-schema/src/rest_spec/{ => definition}/reset.ts (100%) create mode 100644 x-pack/packages/kbn-entities-schema/src/rest_spec/enablement/check.ts create mode 100644 x-pack/packages/kbn-entities-schema/src/rest_spec/entities/find.test.ts create mode 100644 x-pack/packages/kbn-entities-schema/src/rest_spec/entities/find.ts create mode 100644 x-pack/plugins/entity_manager/common/locators/paths.ts create mode 100644 x-pack/plugins/entity_manager/common/translations.ts create mode 100644 x-pack/plugins/entity_manager/public/application.tsx create mode 100644 x-pack/plugins/entity_manager/public/components/badges.tsx create mode 100644 x-pack/plugins/entity_manager/public/components/entities_listing.tsx create mode 100644 x-pack/plugins/entity_manager/public/components/pagination.tsx create mode 100644 x-pack/plugins/entity_manager/public/components/rows_per_page.tsx create mode 100644 x-pack/plugins/entity_manager/public/components/stats/entity_count.tsx create mode 100644 x-pack/plugins/entity_manager/public/components/stats/history_checkpoint_duration.tsx create mode 100644 x-pack/plugins/entity_manager/public/components/stats/history_count.tsx create mode 100644 x-pack/plugins/entity_manager/public/components/stats/last_seen.tsx create mode 100644 x-pack/plugins/entity_manager/public/components/stats/latest_checkpoint_duration.tsx create mode 100644 x-pack/plugins/entity_manager/public/components/stats/types.ts create mode 100644 x-pack/plugins/entity_manager/public/context/plugin_context.tsx create mode 100644 x-pack/plugins/entity_manager/public/hooks/query_key_factory.ts create mode 100644 x-pack/plugins/entity_manager/public/hooks/use_enable_enablement.ts create mode 100644 x-pack/plugins/entity_manager/public/hooks/use_fetch_enablement_status.ts create mode 100644 x-pack/plugins/entity_manager/public/hooks/use_fetch_entities.ts create mode 100644 x-pack/plugins/entity_manager/public/hooks/use_fetch_entity_definition.ts create mode 100644 x-pack/plugins/entity_manager/public/hooks/use_fetch_entity_definitions.ts create mode 100644 x-pack/plugins/entity_manager/public/hooks/use_fetch_transform_messages.ts create mode 100644 x-pack/plugins/entity_manager/public/hooks/use_kibana.ts create mode 100644 x-pack/plugins/entity_manager/public/hooks/use_plugin_context.ts create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager/components/built_in_definition_notice.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager/components/create_entity_definition_btn.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager/components/definition_listing.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager/entity_manager.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/definition_details.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/entities.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/header_details.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/pagination.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/rows_per_page.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_content.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_details_panel.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_health_badge.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_messages.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_stats_panel.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_status_badge.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transforms.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager_detail/entity_manager_detail.tsx create mode 100644 x-pack/plugins/entity_manager/public/routes.tsx create mode 100644 x-pack/plugins/entity_manager/server/lib/entities/find_entities.ts create mode 100644 x-pack/plugins/entity_manager/server/lib/entities/get_entity_definition_stats.ts rename x-pack/plugins/entity_manager/server/routes/entities/{ => definition}/create.ts (82%) rename x-pack/plugins/entity_manager/server/routes/entities/{ => definition}/delete.ts (87%) rename x-pack/plugins/entity_manager/server/routes/entities/{get.ts => definition/find.ts} (76%) create mode 100644 x-pack/plugins/entity_manager/server/routes/entities/definition/get.ts rename x-pack/plugins/entity_manager/server/routes/entities/{ => definition}/reset.ts (71%) rename x-pack/plugins/entity_manager/server/routes/entities/{ => definition}/update.ts (87%) create mode 100644 x-pack/plugins/entity_manager/server/routes/entities/find.ts diff --git a/packages/deeplinks/observability/constants.ts b/packages/deeplinks/observability/constants.ts index 45868fa3a16b2..96a1652dcf9b6 100644 --- a/packages/deeplinks/observability/constants.ts +++ b/packages/deeplinks/observability/constants.ts @@ -23,6 +23,8 @@ export const OBSERVABILITY_ONBOARDING_APP_ID = 'observabilityOnboarding'; export const SLO_APP_ID = 'slo'; +export const ENTITY_MANAGER_APP_ID = 'entityManager'; + export const AI_ASSISTANT_APP_ID = 'observabilityAIAssistant'; export const INVESTIGATE_APP_ID = 'investigate'; diff --git a/packages/deeplinks/observability/deep_links.ts b/packages/deeplinks/observability/deep_links.ts index 088b9c866c03d..910c42efa587c 100644 --- a/packages/deeplinks/observability/deep_links.ts +++ b/packages/deeplinks/observability/deep_links.ts @@ -20,6 +20,7 @@ import { OBLT_UX_APP_ID, OBLT_PROFILING_APP_ID, INVENTORY_APP_ID, + ENTITY_MANAGER_APP_ID, } from './constants'; type LogsApp = typeof LOGS_APP_ID; @@ -30,6 +31,7 @@ type ApmApp = typeof APM_APP_ID; type SyntheticsApp = typeof SYNTHETICS_APP_ID; type ObservabilityOnboardingApp = typeof OBSERVABILITY_ONBOARDING_APP_ID; type SloApp = typeof SLO_APP_ID; +type EntityManagerApp = typeof ENTITY_MANAGER_APP_ID; type AiAssistantApp = typeof AI_ASSISTANT_APP_ID; type ObltUxApp = typeof OBLT_UX_APP_ID; type ObltProfilingApp = typeof OBLT_PROFILING_APP_ID; @@ -44,6 +46,7 @@ export type AppId = | MetricsApp | SyntheticsApp | SloApp + | EntityManagerApp | AiAssistantApp | ObltUxApp | ObltProfilingApp diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 97aa05deb4a42..ac74efbef142c 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -120,6 +120,7 @@ "xpack.securitySolutionEss": "plugins/security_solution_ess", "xpack.securitySolutionServerless": "plugins/security_solution_serverless", "xpack.sessionView": "plugins/session_view", + "xpack.entityManager": "plugins/observability_solution/entity_manager", "xpack.slo": "plugins/observability_solution/slo", "xpack.snapshotRestore": "plugins/snapshot_restore", "xpack.spaces": "plugins/spaces", diff --git a/x-pack/packages/kbn-data-forge/src/constants.ts b/x-pack/packages/kbn-data-forge/src/constants.ts index 0105ae5cade14..6a55b892a21ed 100644 --- a/x-pack/packages/kbn-data-forge/src/constants.ts +++ b/x-pack/packages/kbn-data-forge/src/constants.ts @@ -9,6 +9,7 @@ export const FAKE_HOSTS = 'fake_hosts'; export const FAKE_LOGS = 'fake_logs'; export const FAKE_STACK = 'fake_stack'; export const SERVICE_LOGS = 'service.logs'; +export const FAKE_STACK_DS = 'fake_stack_ds'; export const INDEX_PREFIX = 'kbn-data-forge'; diff --git a/x-pack/packages/kbn-data-forge/src/data_sources/fake_stack_ds/index.ts b/x-pack/packages/kbn-data-forge/src/data_sources/fake_stack_ds/index.ts new file mode 100644 index 0000000000000..81b11c7e23505 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/src/data_sources/fake_stack_ds/index.ts @@ -0,0 +1,69 @@ +/* + * 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 { isArray, omit } from 'lodash'; +import { generateEvent as generateAdminConsole } from '../fake_stack/admin_console'; +import { generateEvent as generateMongoDB } from '../fake_stack/mongodb'; +import { generateEvent as generateMessageProcessor } from '../fake_stack/message_processor'; +import { generateEvent as generateNginxProxy } from '../fake_stack/nginx_proxy'; +import { generateEvent as generateHeartbeat } from '../fake_stack/heartbeat'; +import { Doc, GeneratorFunction } from '../../types'; +import { ADMIN_CONSOLE } from '../fake_stack/common/constants'; + +const convertToMongoDBRawLog = (event: any) => { + return { + '@timestamp': event['@timestamp'], + message: `${event['@timestamp']} ${event.log.level === 'ERROR' ? 'E' : 'I'} ${ + event.mongodb.component + } [${event.mongodb.context}] ${event.message}`, + } as unknown as Doc & { message: string }; +}; + +const convertToNginxRawLog = (event: any) => { + return { '@timestamp': event['@timestamp'], message: event.message } as unknown as Doc & { + message: string; + }; +}; + +export const generateEvent: GeneratorFunction = (config, schedule, index, timestamp) => { + const scenario = config.indexing.scenario || 'fake_stack'; + const adminConsoleEvents = generateAdminConsole(config, schedule, index, timestamp); + const mongodbEvents = generateMongoDB(config, schedule, index, timestamp).map( + convertToMongoDBRawLog + ); + const messageProcessorEvents = generateMessageProcessor(config, schedule, index, timestamp); + const nginxProxyEvents = generateNginxProxy(config, schedule, index, timestamp).map( + convertToNginxRawLog + ); + const heartbeatEvents = generateHeartbeat(config, schedule, index, timestamp); + return [ + ...(isArray(adminConsoleEvents) ? adminConsoleEvents : [adminConsoleEvents]), + ...(isArray(mongodbEvents) ? mongodbEvents : [mongodbEvents]), + ...(isArray(messageProcessorEvents) ? messageProcessorEvents : [messageProcessorEvents]), + ...(isArray(nginxProxyEvents) ? nginxProxyEvents : [nginxProxyEvents]), + ...(isArray(heartbeatEvents) ? heartbeatEvents : [heartbeatEvents]), + ].map((event: Doc) => { + const labels = event.labels ?? {}; + const message = JSON.stringify( + omit( + { + ...event, + labels: { ...labels, scenario }, + }, + 'namespace' + ) + ); + const finalDoc = { + namespace: ADMIN_CONSOLE, + '@timestamp': event['@timestamp'], + message, + data_stream: { dataset: ADMIN_CONSOLE, namespace: 'default' }, + }; + + return finalDoc; + }); +}; diff --git a/x-pack/packages/kbn-data-forge/src/data_sources/index.ts b/x-pack/packages/kbn-data-forge/src/data_sources/index.ts index 8799b2254b6aa..9832c5be9f2ba 100644 --- a/x-pack/packages/kbn-data-forge/src/data_sources/index.ts +++ b/x-pack/packages/kbn-data-forge/src/data_sources/index.ts @@ -6,18 +6,20 @@ */ import { GeneratorFunction, Dataset, IndexTemplateDef } from '../types'; -import { FAKE_HOSTS, FAKE_LOGS, FAKE_STACK, SERVICE_LOGS } from '../constants'; +import { FAKE_HOSTS, FAKE_LOGS, FAKE_STACK, FAKE_STACK_DS, SERVICE_LOGS } from '../constants'; import * as fakeLogs from './fake_logs'; import * as fakeHosts from './fake_hosts'; import * as fakeStack from './fake_stack'; import * as serviceLogs from './service_logs'; +import * as fakeStackDs from './fake_stack_ds'; export const indexTemplates: Record = { [FAKE_HOSTS]: [fakeHosts.indexTemplate], [FAKE_LOGS]: [fakeLogs.indexTemplate], [FAKE_STACK]: fakeStack.indexTemplate, [SERVICE_LOGS]: [], // uses logs-*-* index templates + [FAKE_STACK_DS]: [], // uses logs-*-* index templates }; export const generateEvents: Record = { @@ -25,6 +27,7 @@ export const generateEvents: Record = { [FAKE_LOGS]: fakeLogs.generateEvent, [FAKE_STACK]: fakeStack.generteEvent, [SERVICE_LOGS]: serviceLogs.generateEvent, + [FAKE_STACK_DS]: fakeStackDs.generateEvent, }; export const kibanaAssets: Record = { @@ -32,4 +35,5 @@ export const kibanaAssets: Record = { [FAKE_LOGS]: [], [FAKE_STACK]: fakeStack.kibanaAssets, [SERVICE_LOGS]: [], + [FAKE_STACK_DS]: [], }; diff --git a/x-pack/packages/kbn-data-forge/src/types/index.ts b/x-pack/packages/kbn-data-forge/src/types/index.ts index f9abc15b3ffdb..7672096d6a1d2 100644 --- a/x-pack/packages/kbn-data-forge/src/types/index.ts +++ b/x-pack/packages/kbn-data-forge/src/types/index.ts @@ -8,7 +8,7 @@ import type { Moment } from 'moment'; import { Client } from '@elastic/elasticsearch'; import * as rt from 'io-ts'; -import { FAKE_HOSTS, FAKE_LOGS, FAKE_STACK, SERVICE_LOGS } from '../constants'; +import { FAKE_HOSTS, FAKE_LOGS, FAKE_STACK, FAKE_STACK_DS, SERVICE_LOGS } from '../constants'; export interface Doc { namespace: string; @@ -27,6 +27,7 @@ export const DatasetRT = rt.keyof({ [FAKE_LOGS]: null, [FAKE_STACK]: null, [SERVICE_LOGS]: null, + [FAKE_STACK_DS]: null, }); export type Dataset = rt.TypeOf; diff --git a/x-pack/packages/kbn-entities-schema/index.ts b/x-pack/packages/kbn-entities-schema/index.ts index b77e7991068f2..1081b8afbaa6f 100644 --- a/x-pack/packages/kbn-entities-schema/index.ts +++ b/x-pack/packages/kbn-entities-schema/index.ts @@ -9,7 +9,10 @@ export * from './src/schema/entity_definition'; export * from './src/schema/entity'; export * from './src/schema/common'; export * from './src/schema/patterns'; -export * from './src/rest_spec/create'; -export * from './src/rest_spec/delete'; -export * from './src/rest_spec/reset'; -export * from './src/rest_spec/get'; +export * from './src/rest_spec/definition/delete'; +export * from './src/rest_spec/definition/reset'; +export * from './src/rest_spec/definition/get'; +export * from './src/rest_spec/definition/find'; +export * from './src/rest_spec/definition/create'; +export * from './src/rest_spec/entities/find'; +export * from './src/rest_spec/enablement/check'; diff --git a/x-pack/packages/kbn-entities-schema/src/rest_spec/create.ts b/x-pack/packages/kbn-entities-schema/src/rest_spec/definition/create.ts similarity index 100% rename from x-pack/packages/kbn-entities-schema/src/rest_spec/create.ts rename to x-pack/packages/kbn-entities-schema/src/rest_spec/definition/create.ts diff --git a/x-pack/packages/kbn-entities-schema/src/rest_spec/delete.ts b/x-pack/packages/kbn-entities-schema/src/rest_spec/definition/delete.ts similarity index 100% rename from x-pack/packages/kbn-entities-schema/src/rest_spec/delete.ts rename to x-pack/packages/kbn-entities-schema/src/rest_spec/definition/delete.ts diff --git a/x-pack/packages/kbn-entities-schema/src/rest_spec/definition/find.ts b/x-pack/packages/kbn-entities-schema/src/rest_spec/definition/find.ts new file mode 100644 index 0000000000000..16587d13b4fc9 --- /dev/null +++ b/x-pack/packages/kbn-entities-schema/src/rest_spec/definition/find.ts @@ -0,0 +1,59 @@ +/* + * 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 { z } from '@kbn/zod'; +import { + IngestGetPipelineResponse, + TransformGetTransformStatsTransformStats, + TransformGetTransformTransformSummary, +} from '@elastic/elasticsearch/lib/api/types'; +import { entityDefinitionSchema } from '../../schema/entity_definition'; + +export const findEntityDefinitionQuerySchema = z.object({ + page: z.optional(z.coerce.number()), + perPage: z.optional(z.coerce.number()), +}); + +export type FindEntityDefinitionQuery = z.infer; + +export const entitiyDefinitionWithStateSchema = entityDefinitionSchema.extend({ + state: z.object({ + installed: z.boolean(), + running: z.boolean(), + avgCheckpointDuration: z.object({ + history: z.number().or(z.null()), + latest: z.number().or(z.null()), + }), + }), + stats: z.object({ + entityCount: z.number(), + totalDocs: z.number(), + lastSeenTimestamp: z.string().or(z.null()), + }), +}); + +export type EntityDefinitionWithState = z.infer & { + resources: { + ingestPipelines: IngestGetPipelineResponse; + transforms: { + history?: TransformGetTransformTransformSummary; + latest?: TransformGetTransformTransformSummary; + stats: { + history?: TransformGetTransformStatsTransformStats; + latest?: TransformGetTransformStatsTransformStats; + }; + }; + }; +}; + +export const entityDefintionResponseSchema = z.object({ + definitions: z.array(entitiyDefinitionWithStateSchema), +}); + +export interface EntityDefintionResponse { + definitions: EntityDefinitionWithState[]; +} diff --git a/x-pack/packages/kbn-entities-schema/src/rest_spec/get.ts b/x-pack/packages/kbn-entities-schema/src/rest_spec/definition/get.ts similarity index 79% rename from x-pack/packages/kbn-entities-schema/src/rest_spec/get.ts rename to x-pack/packages/kbn-entities-schema/src/rest_spec/definition/get.ts index 2eadfdb039cae..fe1cac75c62a6 100644 --- a/x-pack/packages/kbn-entities-schema/src/rest_spec/get.ts +++ b/x-pack/packages/kbn-entities-schema/src/rest_spec/definition/get.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { z } from '@kbn/zod'; import { BooleanFromString } from '@kbn/zod-helpers'; @@ -13,5 +12,9 @@ export const getEntityDefinitionQuerySchema = z.object({ perPage: z.optional(z.coerce.number()), includeState: z.optional(BooleanFromString).default(false), }); - export type GetEntityDefinitionQuerySchema = z.infer; + +export const getEntityDefinitionParamsSchema = z.object({ + id: z.string(), +}); +export type GetEntityDefinitionParams = z.infer; diff --git a/x-pack/packages/kbn-entities-schema/src/rest_spec/reset.ts b/x-pack/packages/kbn-entities-schema/src/rest_spec/definition/reset.ts similarity index 100% rename from x-pack/packages/kbn-entities-schema/src/rest_spec/reset.ts rename to x-pack/packages/kbn-entities-schema/src/rest_spec/definition/reset.ts diff --git a/x-pack/packages/kbn-entities-schema/src/rest_spec/enablement/check.ts b/x-pack/packages/kbn-entities-schema/src/rest_spec/enablement/check.ts new file mode 100644 index 0000000000000..06165ad88ce44 --- /dev/null +++ b/x-pack/packages/kbn-entities-schema/src/rest_spec/enablement/check.ts @@ -0,0 +1,14 @@ +/* + * 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 { z } from 'zod'; + +export const entityManagerEnablementResponseSchema = z.object({ + enabled: z.boolean(), + reason: z.optional(z.string()), +}); + +export type EntityManagerEnablementResponse = z.infer; diff --git a/x-pack/packages/kbn-entities-schema/src/rest_spec/entities/find.test.ts b/x-pack/packages/kbn-entities-schema/src/rest_spec/entities/find.test.ts new file mode 100644 index 0000000000000..a0fcad5a3df31 --- /dev/null +++ b/x-pack/packages/kbn-entities-schema/src/rest_spec/entities/find.test.ts @@ -0,0 +1,26 @@ +/* + * 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 { searchAfterSchema } from './find'; +import rison from '@kbn/rison'; + +describe('FindEntitiesResponse Schema', () => { + describe('searchAfterSchema', () => { + it('should parse from rison.encode to schema', () => { + const input = rison.encode(['example', 123]); + const result = searchAfterSchema.safeParse(input); + expect(result).toHaveProperty('success', true); + expect(result.data).toEqual(['example', 123]); + }); + it('should work with regular arrays', () => { + const input = ['example', 123]; + const result = searchAfterSchema.safeParse(input); + expect(result).toHaveProperty('success', true); + expect(result.data).toEqual(['example', 123]); + }); + }); +}); diff --git a/x-pack/packages/kbn-entities-schema/src/rest_spec/entities/find.ts b/x-pack/packages/kbn-entities-schema/src/rest_spec/entities/find.ts new file mode 100644 index 0000000000000..acee75db7425f --- /dev/null +++ b/x-pack/packages/kbn-entities-schema/src/rest_spec/entities/find.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. + */ + +import { z } from '@kbn/zod'; +import rison from '@kbn/rison'; +import { entityLatestSchema } from '../../schema/entity'; + +const searchAfterArraySchema = z.array(z.string().or(z.number())); + +export const searchAfterSchema = z + .string() + .transform((value: string, ctx: z.RefinementCtx) => { + try { + const result = rison.decode(value); + return searchAfterArraySchema.parse(result); + } catch (e) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: e.message }); + } + return z.NEVER; + }) + .or(searchAfterArraySchema); + +export const findEntitiesQuerySchema = z.object({ + perPage: z.coerce.number().default(10), + query: z.optional(z.string()), + searchAfter: z.optional(searchAfterSchema), + sortField: z.string().default('entity.displayName.keyword'), + sortDirection: z.enum(['asc', 'desc']).default('asc'), +}); + +export type FindEntitiesQuery = z.infer; + +export const findEntitiesResponseSchema = z.object({ + entities: z.array(entityLatestSchema), + total: z.number(), + searchAfter: z.optional(z.string()), +}); + +export type FindEntitiesResponse = z.infer; diff --git a/x-pack/packages/kbn-entities-schema/src/schema/entity.ts b/x-pack/packages/kbn-entities-schema/src/schema/entity.ts index eae6873356c14..8827d2ef13a34 100644 --- a/x-pack/packages/kbn-entities-schema/src/schema/entity.ts +++ b/x-pack/packages/kbn-entities-schema/src/schema/entity.ts @@ -41,9 +41,13 @@ export const entityLatestSchema = z }) .and(entityMetadataSchema); +export type EntityLatestDoc = z.infer; + export const entityHistorySchema = z .object({ '@timestamp': z.string(), entity: entityBaseSchema, }) .and(entityMetadataSchema); + +export type EntityHistoryDoc = z.infer; diff --git a/x-pack/plugins/entity_manager/common/locators/paths.ts b/x-pack/plugins/entity_manager/common/locators/paths.ts new file mode 100644 index 0000000000000..1ac7b7bc0b80b --- /dev/null +++ b/x-pack/plugins/entity_manager/common/locators/paths.ts @@ -0,0 +1,17 @@ +/* + * 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 ENTITY_MANAGER_BASE_PATH = '/app/entity_manager' as const; +export const ENTITY_MANAGER_OVERVIEW = '/' as const; +export const ENTITY_MANAGER_CREATE = '/create' as const; +export const ENTITY_MANAGER_DETAIL = '/:entityId' as const; + +export const paths = { + entities: `${ENTITY_MANAGER_BASE_PATH}${ENTITY_MANAGER_OVERVIEW}`, + entitiesCreate: `${ENTITY_MANAGER_BASE_PATH}${ENTITY_MANAGER_CREATE}`, + entitieDetail: (id: string) => `${ENTITY_MANAGER_BASE_PATH}/${id}`, +}; diff --git a/x-pack/plugins/entity_manager/common/translations.ts b/x-pack/plugins/entity_manager/common/translations.ts new file mode 100644 index 0000000000000..35b332077ff5f --- /dev/null +++ b/x-pack/plugins/entity_manager/common/translations.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const ENTITY_MANAGER_LABEL = i18n.translate('xpack.entityManager.entityManagerLabel', { + defaultMessage: 'Entity Manager', +}); diff --git a/x-pack/plugins/entity_manager/kibana.jsonc b/x-pack/plugins/entity_manager/kibana.jsonc index efd6d3a445b3f..d9e4b82439ab2 100644 --- a/x-pack/plugins/entity_manager/kibana.jsonc +++ b/x-pack/plugins/entity_manager/kibana.jsonc @@ -6,9 +6,31 @@ "plugin": { "id": "entityManager", "configPath": ["xpack", "entityManager"], - "requiredPlugins": ["security", "encryptedSavedObjects", "licensing"], "browser": true, "server": true, - "requiredBundles": [] + "requiredPlugins": [ + "data", + "dataViews", + "dataViewEditor", + "dataViewFieldEditor", + "security", + "encryptedSavedObjects", + "lens", + "observability", + "observabilityShared", + "embeddable", + "presentationUtil", + "usageCollection", + "licensing", + "transform" + ], + "optionalPlugins": [ + "cloud", + "serverless" + ], + "requiredBundles": [ + "kibanaReact", + "kibanaUtils" + ] } } diff --git a/x-pack/plugins/entity_manager/public/application.tsx b/x-pack/plugins/entity_manager/public/application.tsx new file mode 100644 index 0000000000000..008ac09128ffe --- /dev/null +++ b/x-pack/plugins/entity_manager/public/application.tsx @@ -0,0 +1,131 @@ +/* + * 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 { AppMountParameters, APP_WRAPPER_CLASS, CoreStart } from '@kbn/core/public'; +import { PerformanceContextProvider } from '@kbn/ebt-tools'; +import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import { Route, Router, Routes } from '@kbn/shared-ux-router'; +import { PluginContext } from './context/plugin_context'; +import { EntityManagerPluginStart } from './types'; +import { getRoutes } from './routes'; + +function App() { + const routes = getRoutes(); + return ( + + {Object.keys(routes).map((path) => { + const { handler, exact } = routes[path]; + const Wrapper = () => { + return handler(); + }; + return ; + })} + + ); +} + +export function renderApp({ + core, + plugins, + appMountParameters, + ObservabilityPageTemplate, + usageCollection, + isDev, + kibanaVersion, + isServerless, +}: { + core: CoreStart; + plugins: EntityManagerPluginStart; + appMountParameters: AppMountParameters; + ObservabilityPageTemplate: React.ComponentType; + usageCollection: UsageCollectionSetup; + isDev?: boolean; + kibanaVersion: string; + isServerless?: boolean; +}) { + const { element, history, theme$ } = appMountParameters; + const isDarkMode = core.theme.getTheme().darkMode; + + // ensure all divs are .kbnAppWrappers + element.classList.add(APP_WRAPPER_CLASS); + + const queryClient = new QueryClient(); + + const ApplicationUsageTrackingProvider = + usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment; + + const CloudProvider = plugins.cloud?.CloudContextProvider ?? React.Fragment; + + const PresentationContextProvider = plugins.presentationUtil?.ContextProvider ?? React.Fragment; + + ReactDOM.render( + + + + + + + + + + + + + + + + + + + + + + + + + , + element + ); + + return () => { + // This needs to be present to fix https://github.com/elastic/kibana/issues/155704 + // as the Overview page renders the UX Section component. That component renders a Lens embeddable + // via the ExploratoryView app, which uses search sessions. Therefore on unmounting we need to clear + // these sessions. + plugins.data.search.session.clear(); + ReactDOM.unmountComponentAtNode(element); + }; +} diff --git a/x-pack/plugins/entity_manager/public/components/badges.tsx b/x-pack/plugins/entity_manager/public/components/badges.tsx new file mode 100644 index 0000000000000..c3331dba28b89 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/components/badges.tsx @@ -0,0 +1,79 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EntityDefinitionWithState } from '@kbn/entities-schema'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +interface Props { + definition: EntityDefinitionWithState; +} + +function VersionBadge({ definition }: Props) { + return ( + + v{definition.version} + + ); +} + +function TypeBadge({ definition }: Props) { + return ( + + {definition.type} + + ); +} + +function ManagedBadge({ definition }: Props) { + if (!definition.managed) return null; + return ( + + + {i18n.translate('xpack.entityManager.managedBadge.managedBadgeLabel', { + defaultMessage: 'Managed', + })} + + + ); +} + +function StatusBadge({ definition }: Props) { + if (definition.state.running) { + return ( + + + {i18n.translate('xpack.entityManager.statusBadge.runningBadgeLabel', { + defaultMessage: 'Running', + })} + + + ); + } + + return ( + + + {i18n.translate('xpack.entityManager.statusBadge.notRunningBadgeLabel', { + defaultMessage: 'Stopped', + })} + + + ); +} + +export function Badges({ definition }: Props) { + return ( + + + + + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/components/entities_listing.tsx b/x-pack/plugins/entity_manager/public/components/entities_listing.tsx new file mode 100644 index 0000000000000..00adc5608ebe9 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/components/entities_listing.tsx @@ -0,0 +1,146 @@ +/* + * 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, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EntityDefinitionWithState, EntityLatestDoc } from '@kbn/entities-schema'; +import { + Criteria, + EuiBadge, + EuiBasicTable, + EuiBasicTableColumn, + EuiSpacer, + EuiTableSortingType, +} from '@elastic/eui'; +import { capitalize, has, lowerCase } from 'lodash'; +import moment from 'moment'; +import numeral from '@elastic/numeral'; +import { useFetchEntities } from '../hooks/use_fetch_entities'; +import { Pagination } from './pagination'; + +interface EntitiesListingProps { + definition?: EntityDefinitionWithState; + defaultPerPage?: number; +} + +export function EntitiesListing({ definition, defaultPerPage = 10 }: EntitiesListingProps) { + const [searchAfter, setSearchAfter] = useState(); + const [perPage, setPerPage] = useState(defaultPerPage); + const [sortField, setSortField] = useState('entity.type'); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + + const { data } = useFetchEntities({ + query: definition ? `entity.definitionId: "${definition.id}"` : '', + sortField, + sortDirection, + perPage, + searchAfter, + }); + + const columns: Array> = [ + { + field: 'entity.displayName', + name: i18n.translate('xpack.entityManager.entitiesListing.nameLabel', { + defaultMessage: 'Name', + }), + truncateText: true, + sortable: true, + }, + { + field: 'entity.type', + name: i18n.translate('xpack.entityManager.entitiesListing.typeLabel', { + defaultMessage: 'Type', + }), + render: (type: string) => {type}, + sortable: true, + }, + { + field: 'entity.firstSeenTimestamp', + name: i18n.translate('xpack.entityManager.entitiesListing.firstSeenLabel', { + defaultMessage: 'First seen', + }), + render: (value: string) => moment(value).format('ll LT'), + truncateText: true, + sortable: true, + }, + { + field: 'entity.lastSeenTimestamp', + name: i18n.translate('xpack.entityManager.entitiesListing.lastSeenLabel', { + defaultMessage: 'Last seen', + }), + render: (value: string) => moment(value).format('ll LT'), + truncateText: true, + sortable: true, + }, + ]; + + if (definition) { + definition?.metrics?.forEach((metric) => { + if (data && data.entities.some((entity) => has(entity, ['entity', 'metrics', metric.name]))) { + columns.push({ + field: `entity.metrics.${metric.name}`, + name: capitalize(lowerCase(metric.name)), + render: (value: number) => (value != null ? numeral(value).format('0,0[.0]') : '--'), + sortable: true, + }); + } + }); + } else { + columns.push({ + field: 'entity.metrics.logRate', + name: 'Log rate', + render: (value: number) => (value != null ? numeral(value).format('0,0[.0]') : '--'), + sortable: true, + }); + } + + const sorting: EuiTableSortingType = { + sort: { + field: (sortField === 'entity.displayName.keyword' + ? 'entity.displayName' + : sortField) as 'entity', + direction: sortDirection, + }, + }; + + const handleTableChange = ({ sort }: Criteria) => { + if (sort) { + if (sort.field === ('entity.displayName' as 'entity')) { + setSortField('entity.displayName.keyword'); + } else { + setSortField(sort.field); + } + setSortDirection(sort.direction); + setSearchAfter(undefined); + } + }; + + const handlePerPageChange = (newPerPage: number) => { + setPerPage(newPerPage); + setSearchAfter(undefined); + }; + + return ( + <> + + + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/components/pagination.tsx b/x-pack/plugins/entity_manager/public/components/pagination.tsx new file mode 100644 index 0000000000000..009ad9a9d238d --- /dev/null +++ b/x-pack/plugins/entity_manager/public/components/pagination.tsx @@ -0,0 +1,127 @@ +/* + * 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 { + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiTableSortingType, + EuiText, +} from '@elastic/eui'; +import React, { useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { isEqual } from 'lodash'; +import { RowsPerPage } from './rows_per_page'; + +interface PaginationProps { + total?: number; + searchAfter?: string; + onPageChange: (searchAfter?: string) => void; + perPage?: number; + onPerPageChange: (perPage: number) => void; + itemsPerPage?: number[]; + sort: EuiTableSortingType; +} + +export function Pagination({ + total, + onPageChange, + onPerPageChange, + searchAfter, + perPage = 10, + itemsPerPage = [10, 20, 50, 100], + sort, +}: PaginationProps) { + const [paginationHistory, setPaginationHistory] = useState([]); + const [sorting, setSorting] = useState>(sort); + + // When the sorting changes we need to reset the pagination history + useEffect(() => { + if (!isEqual(sorting, sort)) { + setPaginationHistory([]); + setSorting(sort); + } + }, [sorting, sort]); + + const nextOnClickHandler = () => { + setPaginationHistory((prev) => { + if (searchAfter) { + prev.push(searchAfter); + onPageChange(searchAfter); + } + return prev; + }); + }; + + const previousOnClickHandler = () => { + setPaginationHistory((prev) => { + prev.pop(); // this is the current page + onPageChange(prev[prev.length - 1]); // this is the actual previous page + return prev; + }); + }; + + // When the per page changes, we need to reset the pagination history + const handlePerPageChange = (newPerPage: number) => { + setPaginationHistory([]); + onPerPageChange(newPerPage); + }; + + if (!total) return null; + + const disablePrevious = paginationHistory.length === 0; + const disableNext = Math.ceil(total / perPage) === paginationHistory.length + 1; + + return ( + + + + + + + + + {i18n.translate('xpack.entityManager.pagination.pageOfFlexItemLabel', { + defaultMessage: 'Page {current} of {total}', + values: { + current: paginationHistory.length + 1, + total: Math.ceil(total / perPage), + }, + })} + + + + + + + + + + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/components/rows_per_page.tsx b/x-pack/plugins/entity_manager/public/components/rows_per_page.tsx new file mode 100644 index 0000000000000..855b078904863 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/components/rows_per_page.tsx @@ -0,0 +1,93 @@ +/* + * 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, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonEmpty, + EuiContextMenuPanel, + EuiContextMenuItem, + EuiPopover, + useGeneratedHtmlId, +} from '@elastic/eui'; + +interface RowsPerPageProps { + perPage?: number; + itemsPerPage?: number[]; + onPerPageChange: (perPage: number) => void; +} + +export function RowsPerPage({ + onPerPageChange, + perPage = 10, + itemsPerPage = [10, 20, 50], +}: RowsPerPageProps) { + const [isPopoverOpen, setPopover] = useState(false); + + const singleContextMenuPopoverId = useGeneratedHtmlId({ + prefix: 'singleContextMenuPopover', + }); + + const onButtonClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const isSelectedProps = (size: number) => { + return size === perPage + ? { icon: 'check', 'aria-current': true } + : { icon: 'empty', 'aria-current': false }; + }; + + const button = ( + + {i18n.translate('xpack.entityManager.rowsPerPage.buttonLabel', { + defaultMessage: 'Rows per page: {perPage}', + values: { perPage }, + })} + + ); + + const items = itemsPerPage.map((page) => ( + { + closePopover(); + onPerPageChange(page); + }} + > + {i18n.translate('xpack.entityManager.rowsPerPage.contextMenuLabel', { + defaultMessage: '{pagePer} rows', + values: { pagePer: page }, + })} + + )); + + return ( + + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/components/stats/entity_count.tsx b/x-pack/plugins/entity_manager/public/components/stats/entity_count.tsx new file mode 100644 index 0000000000000..ea5c040d708b0 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/components/stats/entity_count.tsx @@ -0,0 +1,27 @@ +/* + * 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 { EuiStat } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import numeral from '@elastic/numeral'; +import { DefinitionStatProps } from './types'; + +export function EntityCountStat({ definition, titleSize, textAlign }: DefinitionStatProps) { + return ( + 100 ? '0.0a' : '0,0' + )} + textAlign={textAlign} + description={i18n.translate('xpack.entityManager.defintionStat.entityCount.label', { + defaultMessage: 'Total entities', + })} + /> + ); +} diff --git a/x-pack/plugins/entity_manager/public/components/stats/history_checkpoint_duration.tsx b/x-pack/plugins/entity_manager/public/components/stats/history_checkpoint_duration.tsx new file mode 100644 index 0000000000000..df777caafa018 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/components/stats/history_checkpoint_duration.tsx @@ -0,0 +1,34 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import numeral from '@elastic/numeral'; +import { EuiStat } from '@elastic/eui'; +import { DefinitionStatProps } from './types'; + +export function HistoryCheckpointDurationStat({ + definition, + textAlign, + titleSize, +}: DefinitionStatProps) { + return ( + + ); +} diff --git a/x-pack/plugins/entity_manager/public/components/stats/history_count.tsx b/x-pack/plugins/entity_manager/public/components/stats/history_count.tsx new file mode 100644 index 0000000000000..e79e2fda1b8ac --- /dev/null +++ b/x-pack/plugins/entity_manager/public/components/stats/history_count.tsx @@ -0,0 +1,27 @@ +/* + * 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 { EuiStat } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import numeral from '@elastic/numeral'; +import { DefinitionStatProps } from './types'; + +export function HistoryCountStat({ definition, titleSize, textAlign }: DefinitionStatProps) { + return ( + 1000 ? '0.0a' : '0' + )} + textAlign={textAlign} + description={i18n.translate('xpack.entityManager.listing.historyCount.label', { + defaultMessage: 'History count', + })} + /> + ); +} diff --git a/x-pack/plugins/entity_manager/public/components/stats/last_seen.tsx b/x-pack/plugins/entity_manager/public/components/stats/last_seen.tsx new file mode 100644 index 0000000000000..4d5c7317982da --- /dev/null +++ b/x-pack/plugins/entity_manager/public/components/stats/last_seen.tsx @@ -0,0 +1,25 @@ +/* + * 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 moment from 'moment'; +import { EuiStat } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { DefinitionStatProps } from './types'; + +export function LastSeenStat({ definition, titleSize, textAlign }: DefinitionStatProps) { + return ( + + ); +} diff --git a/x-pack/plugins/entity_manager/public/components/stats/latest_checkpoint_duration.tsx b/x-pack/plugins/entity_manager/public/components/stats/latest_checkpoint_duration.tsx new file mode 100644 index 0000000000000..477b1badd1930 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/components/stats/latest_checkpoint_duration.tsx @@ -0,0 +1,34 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import numeral from '@elastic/numeral'; +import { EuiStat } from '@elastic/eui'; +import { DefinitionStatProps } from './types'; + +export function LatestCheckpointDurationStat({ + definition, + textAlign, + titleSize, +}: DefinitionStatProps) { + return ( + + ); +} diff --git a/x-pack/plugins/entity_manager/public/components/stats/types.ts b/x-pack/plugins/entity_manager/public/components/stats/types.ts new file mode 100644 index 0000000000000..0cdeb66bf7cc9 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/components/stats/types.ts @@ -0,0 +1,15 @@ +/* + * 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 { EntityDefinitionWithState } from '@kbn/entities-schema'; +import { EuiStatProps } from '@elastic/eui'; + +export interface DefinitionStatProps { + definition: EntityDefinitionWithState; + titleSize?: EuiStatProps['titleSize']; + textAlign?: EuiStatProps['textAlign']; +} diff --git a/x-pack/plugins/entity_manager/public/context/plugin_context.tsx b/x-pack/plugins/entity_manager/public/context/plugin_context.tsx new file mode 100644 index 0000000000000..dfe9f5d17ee42 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/context/plugin_context.tsx @@ -0,0 +1,19 @@ +/* + * 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 { createContext } from 'react'; +import type { AppMountParameters } from '@kbn/core/public'; +import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; + +export interface PluginContextValue { + isDev?: boolean; + isServerless?: boolean; + appMountParameters?: AppMountParameters; + ObservabilityPageTemplate: React.ComponentType; +} + +export const PluginContext = createContext(null); diff --git a/x-pack/plugins/entity_manager/public/hooks/query_key_factory.ts b/x-pack/plugins/entity_manager/public/hooks/query_key_factory.ts new file mode 100644 index 0000000000000..3f1db2a877010 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/hooks/query_key_factory.ts @@ -0,0 +1,35 @@ +/* + * 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 entityManagerKeys = { + all: ['entity_manager'], + definitions: () => [...entityManagerKeys.all, 'definitions'], + enablement: () => [...entityManagerKeys.all, 'enablement'], + definition: (id: string) => [...entityManagerKeys.all, 'definition', id], + entities: ( + query: string, + sortField: string, + sortDirection: string, + searchAfter?: string, + perPage?: number + ) => [ + ...entityManagerKeys.all, + 'entities', + query, + sortField, + sortDirection, + searchAfter || '', + perPage || 10, + ], + transformMessages: (id: string, sortField = 'timestamp', sortDirection = 'desc') => [ + ...entityManagerKeys.all, + 'transformMessages', + id, + sortField, + sortDirection, + ], +}; diff --git a/x-pack/plugins/entity_manager/public/hooks/use_enable_enablement.ts b/x-pack/plugins/entity_manager/public/hooks/use_enable_enablement.ts new file mode 100644 index 0000000000000..7c7a0172451bd --- /dev/null +++ b/x-pack/plugins/entity_manager/public/hooks/use_enable_enablement.ts @@ -0,0 +1,26 @@ +/* + * 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 { useMutation, useQueryClient } from '@tanstack/react-query'; +import { EntityManagerEnablementResponse } from '@kbn/entities-schema'; +import { IHttpFetchError } from '@kbn/core/public'; +import { useKibana } from './use_kibana'; +import { entityManagerKeys } from './query_key_factory'; +type ServerError = IHttpFetchError; + +export function useEnableEnablement() { + const queryClient = useQueryClient(); + const { http } = useKibana().services; + return useMutation({ + mutationFn: () => + http.put('/internal/entities/managed/enablement'), + onSuccess: () => { + queryClient.invalidateQueries(entityManagerKeys.enablement()); + queryClient.invalidateQueries(entityManagerKeys.definitions()); + }, + }); +} diff --git a/x-pack/plugins/entity_manager/public/hooks/use_fetch_enablement_status.ts b/x-pack/plugins/entity_manager/public/hooks/use_fetch_enablement_status.ts new file mode 100644 index 0000000000000..a271bf90c5aea --- /dev/null +++ b/x-pack/plugins/entity_manager/public/hooks/use_fetch_enablement_status.ts @@ -0,0 +1,35 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { EntityManagerEnablementResponse } from '@kbn/entities-schema'; +import { useKibana } from './use_kibana'; +import { entityManagerKeys } from './query_key_factory'; + +export function useFetchEnablementStatus() { + const { http } = useKibana().services; + + const { isLoading, isError, isSuccess, data, refetch } = useQuery({ + queryKey: entityManagerKeys.enablement(), + queryFn: async ({ signal }) => { + try { + const response = await http.get( + '/internal/entities/managed/enablement', + { + signal, + } + ); + return response; + } catch (e) { + throw new Error(`Something went wrong: ${e}`); + } + }, + retry: false, + refetchOnWindowFocus: false, + }); + + return { isLoading, isError, isSuccess, data, refetch }; +} diff --git a/x-pack/plugins/entity_manager/public/hooks/use_fetch_entities.ts b/x-pack/plugins/entity_manager/public/hooks/use_fetch_entities.ts new file mode 100644 index 0000000000000..b81854ef4d961 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/hooks/use_fetch_entities.ts @@ -0,0 +1,52 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { FindEntitiesResponse } from '@kbn/entities-schema'; +import { useKibana } from './use_kibana'; +import { entityManagerKeys } from './query_key_factory'; + +export function useFetchEntities({ + perPage, + query, + searchAfter, + sortField, + sortDirection, +}: { + perPage: number; + query: string; + searchAfter?: string; + sortField: string; + sortDirection: 'asc' | 'desc'; +}) { + const { http } = useKibana().services; + + const { isLoading, isError, isSuccess, data, refetch } = useQuery({ + queryKey: entityManagerKeys.entities(query, sortField, sortDirection, searchAfter, perPage), + queryFn: async ({ signal }) => { + try { + const response = await http.get(`/internal/entities/_find`, { + signal, + query: { + query, + sortField, + sortDirection, + perPage, + searchAfter, + }, + }); + return response; + } catch (e) { + throw new Error(`Something went wrong: ${e}`); + } + }, + retry: false, + refetchOnWindowFocus: false, + }); + + return { isLoading, isError, isSuccess, data, refetch }; +} diff --git a/x-pack/plugins/entity_manager/public/hooks/use_fetch_entity_definition.ts b/x-pack/plugins/entity_manager/public/hooks/use_fetch_entity_definition.ts new file mode 100644 index 0000000000000..330caf20b3cb8 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/hooks/use_fetch_entity_definition.ts @@ -0,0 +1,36 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { EntityDefinitionWithState, GetEntityDefinitionParams } from '@kbn/entities-schema'; +import { useKibana } from './use_kibana'; +import { entityManagerKeys } from './query_key_factory'; + +export function useFetchEntityDefinition({ id }: GetEntityDefinitionParams) { + const { http } = useKibana().services; + + const { isLoading, isError, isSuccess, data, refetch } = useQuery({ + queryKey: entityManagerKeys.definition(id), + queryFn: async ({ signal }) => { + try { + const response = await http.get( + `/internal/entities/definition/${id}`, + { + signal, + } + ); + return response; + } catch (e) { + throw new Error(`Something went wrong: ${e}`); + } + }, + retry: false, + refetchOnWindowFocus: false, + }); + + return { isLoading, isError, isSuccess, data, refetch }; +} diff --git a/x-pack/plugins/entity_manager/public/hooks/use_fetch_entity_definitions.ts b/x-pack/plugins/entity_manager/public/hooks/use_fetch_entity_definitions.ts new file mode 100644 index 0000000000000..49acba1419f6d --- /dev/null +++ b/x-pack/plugins/entity_manager/public/hooks/use_fetch_entity_definitions.ts @@ -0,0 +1,33 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { EntityDefintionResponse } from '@kbn/entities-schema'; +import { useKibana } from './use_kibana'; +import { entityManagerKeys } from './query_key_factory'; + +export function useFetchEntityDefinitions() { + const { http } = useKibana().services; + + const { isLoading, isError, isSuccess, data, refetch } = useQuery({ + queryKey: entityManagerKeys.definitions(), + queryFn: async ({ signal }) => { + try { + const response = await http.get('/internal/entities/definition', { + signal, + }); + return response.definitions; + } catch (e) { + throw new Error(`Something went wrong: ${e}`); + } + }, + retry: false, + refetchOnWindowFocus: false, + }); + + return { isLoading, isError, isSuccess, data, refetch }; +} diff --git a/x-pack/plugins/entity_manager/public/hooks/use_fetch_transform_messages.ts b/x-pack/plugins/entity_manager/public/hooks/use_fetch_transform_messages.ts new file mode 100644 index 0000000000000..dc4a8ff839e1f --- /dev/null +++ b/x-pack/plugins/entity_manager/public/hooks/use_fetch_transform_messages.ts @@ -0,0 +1,50 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { useKibana } from './use_kibana'; +import { entityManagerKeys } from './query_key_factory'; + +export interface AuditMessageBase { + message: string; + level: string; + timestamp: number; + node_name: string; + text?: string; +} + +export interface TransformMessage extends AuditMessageBase { + transform_id: string; +} + +export interface GetTransformsAuditMessagesResponseSchema { + messages: TransformMessage[]; + total: number; +} + +export const useFetchTransformMessages = ( + transformId: string, + sortField: keyof TransformMessage, + sortDirection: 'asc' | 'desc' +) => { + const { http } = useKibana().services; + const query = { sortField, sortDirection }; + + return useQuery( + entityManagerKeys.transformMessages(transformId, sortField, sortDirection), + ({ signal }) => + http.get( + `/internal/transform/transforms/${transformId}/messages`, + { + query, + version: '1', + signal, + } + ) + ); +}; diff --git a/x-pack/plugins/entity_manager/public/hooks/use_kibana.ts b/x-pack/plugins/entity_manager/public/hooks/use_kibana.ts new file mode 100644 index 0000000000000..c6b15dc2c2c81 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/hooks/use_kibana.ts @@ -0,0 +1,22 @@ +/* + * 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 { CoreStart } from '@kbn/core/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { EntityManagerPluginStart } from '../types'; + +export type StartServices = CoreStart & + EntityManagerPluginStart & + AdditionalServices & { + storage: Storage; + kibanaVersion: string; + }; +const useTypedKibana = () => + useKibana>(); + +export { useTypedKibana as useKibana }; diff --git a/x-pack/plugins/entity_manager/public/hooks/use_plugin_context.ts b/x-pack/plugins/entity_manager/public/hooks/use_plugin_context.ts new file mode 100644 index 0000000000000..d0640deb575b2 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/hooks/use_plugin_context.ts @@ -0,0 +1,19 @@ +/* + * 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 { useContext } from 'react'; +import { PluginContext } from '../context/plugin_context'; +import type { PluginContextValue } from '../context/plugin_context'; + +export function usePluginContext(): PluginContextValue { + const context = useContext(PluginContext); + if (!context) { + throw new Error('Plugin context value is missing!'); + } + + return context; +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/components/built_in_definition_notice.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/components/built_in_definition_notice.tsx new file mode 100644 index 0000000000000..11a35e6afd546 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/components/built_in_definition_notice.tsx @@ -0,0 +1,55 @@ +/* + * 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 { EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useFetchEnablementStatus } from '../../../hooks/use_fetch_enablement_status'; +import { ERROR_API_KEY_NOT_FOUND } from '../../../../common/errors'; +import { useEnableEnablement } from '../../../hooks/use_enable_enablement'; +export function BuiltInDefinitionNotice() { + const { isLoading, data } = useFetchEnablementStatus(); + const { isLoading: isEnablementLoading, mutate } = useEnableEnablement(); + + if (isLoading || data?.enabled === true) { + return null; + } + + if (data?.enabled === false && data.reason === ERROR_API_KEY_NOT_FOUND) { + return ( + <> + +

+ {i18n.translate('xpack.entityManager.builtInDefinitionNotice.otbDescription', { + defaultMessage: + 'The Observability solution ships with out-of-the-box entity defintions for services, hosts, containers, and many more. To take advantage of these definitions, click the button below.', + })} +

+ mutate()} + isLoading={isEnablementLoading} + > + {i18n.translate( + 'xpack.entityManager.builtInDefinitionNotice.enableBuiltinDefinitionsButtonLabel', + { defaultMessage: 'Enable built-in definitions' } + )} + +
+ + + ); + } + + return null; +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/components/create_entity_definition_btn.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/components/create_entity_definition_btn.tsx new file mode 100644 index 0000000000000..0f4e54f937a9b --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/components/create_entity_definition_btn.tsx @@ -0,0 +1,20 @@ +/* + * 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 { EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export function CreateEntityDefinitionBtn() { + return ( + + {i18n.translate('xpack.entityManager.createEntityDefinitionBtn.label', { + defaultMessage: 'Create Definition', + })} + + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/components/definition_listing.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/components/definition_listing.tsx new file mode 100644 index 0000000000000..97591759df0d4 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/components/definition_listing.tsx @@ -0,0 +1,97 @@ +/* + * 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 { EntityDefinitionWithState, EntityDefintionResponse } from '@kbn/entities-schema'; +import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import numeral from '@elastic/numeral'; +import { Badges } from '../../../components/badges'; +import { useKibana } from '../../../hooks/use_kibana'; +import { paths } from '../../../../common/locators/paths'; +import { LastSeenStat } from '../../../components/stats/last_seen'; +import { EntityCountStat } from '../../../components/stats/entity_count'; +import { HistoryCountStat } from '../../../components/stats/history_count'; +import { useFetchEntityDefinitions } from '../../../hooks/use_fetch_entity_definitions'; + +interface ListingProps { + definition: EntityDefinitionWithState; +} + +function Listing({ definition }: ListingProps) { + const { + http: { basePath }, + } = useKibana().services; + const entityDetailUrl = basePath.prepend(paths.entitieDetail(definition.id)); + + return ( + + + + + + +

+ {definition.name} +

+
+
+ +
+
+ + + + + + + + + + + + + + + +
+
+ ); +} + +interface Props { + definitions?: EntityDefintionResponse['definitions']; + isLoading?: boolean; +} + +export function DefinitionListing() { + const { data } = useFetchEntityDefinitions(); + if (!data) return null; + const defintions = data?.map((definition) => { + return ; + }); + + return {defintions}; +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/entity_manager.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/entity_manager.tsx new file mode 100644 index 0000000000000..757cdc38c636e --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/entity_manager.tsx @@ -0,0 +1,86 @@ +/* + * 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, { useMemo, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; +import { paths } from '../../../common/locators/paths'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { useKibana } from '../../hooks/use_kibana'; +import { ENTITY_MANAGER_LABEL } from '../../../common/translations'; +import { CreateEntityDefinitionBtn } from './components/create_entity_definition_btn'; +import { BuiltInDefinitionNotice } from './components/built_in_definition_notice'; +import { DefinitionListing } from './components/definition_listing'; +import { EntitiesListing } from '../../components/entities_listing'; + +export function EntityManagerPage() { + const { + http: { basePath }, + } = useKibana().services; + const { ObservabilityPageTemplate } = usePluginContext(); + const [selectedTabId, setSelectedTabId] = useState('entities'); + + useBreadcrumbs([ + { + href: basePath.prepend(paths.entities), + text: ENTITY_MANAGER_LABEL, + deepLinkId: 'entityManager', + }, + ]); + + const tabs = useMemo( + () => [ + { + id: 'entities', + name: i18n.translate('xpack.entityManager.overview.entitiesTabLabel', { + defaultMessage: 'Entities', + }), + content: , + }, + { + id: 'definitions', + name: i18n.translate('xpack.entityManager.overview.definitionTabLabel', { + defaultMessage: 'Definitions', + }), + content: , + }, + ], + [] + ); + + const selectedTabContent = useMemo(() => { + return tabs.find((obj) => obj.id === selectedTabId)?.content; + }, [selectedTabId, tabs]); + + const onSelectedTabChanged = (id: string) => { + setSelectedTabId(id); + }; + + const renderTabs = () => { + return tabs.map((tab, index) => ({ + key: index, + onClick: () => onSelectedTabChanged(tab.id), + isSelected: tab.id === selectedTabId, + label: tab.name, + })); + }; + + return ( + ], + tabs: renderTabs(), + }} + > + + {selectedTabContent} + + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/definition_details.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/definition_details.tsx new file mode 100644 index 0000000000000..a5cdbb24a0a36 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/definition_details.tsx @@ -0,0 +1,40 @@ +/* + * 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 { EntityDefinitionWithState } from '@kbn/entities-schema'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { LastSeenStat } from '../../../components/stats/last_seen'; +import { EntityCountStat } from '../../../components/stats/entity_count'; +import { HistoryCountStat } from '../../../components/stats/history_count'; +import { HistoryCheckpointDurationStat } from '../../../components/stats/history_checkpoint_duration'; +import { LatestCheckpointDurationStat } from '../../../components/stats/latest_checkpoint_duration'; + +interface DefinitionDetailsProps { + definition: EntityDefinitionWithState; +} + +export function DefinitionDetails({ definition }: DefinitionDetailsProps) { + return ( + + + + + + + + + + + + + + + + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/entities.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/entities.tsx new file mode 100644 index 0000000000000..4bd3598b81b06 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/entities.tsx @@ -0,0 +1,32 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EntityDefinitionWithState } from '@kbn/entities-schema'; +import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { EntitiesListing } from '../../../components/entities_listing'; + +interface EntitiesListingProps { + definition?: EntityDefinitionWithState; +} + +export function Entities({ definition }: EntitiesListingProps) { + return ( + + +

+ {i18n.translate('xpack.entityManager.entitiesListing.h2.entitiesLabel', { + defaultMessage: 'Entities', + })} +

+
+ + +
+ ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/header_details.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/header_details.tsx new file mode 100644 index 0000000000000..29636f53cbae8 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/header_details.tsx @@ -0,0 +1,23 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import { EntityDefinitionWithState } from '@kbn/entities-schema'; +import { Badges } from '../../../components/badges'; +interface Props { + definition: EntityDefinitionWithState; +} + +export function HeaderDetails({ definition }: Props) { + return ( + + + + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/pagination.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/pagination.tsx new file mode 100644 index 0000000000000..009ad9a9d238d --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/pagination.tsx @@ -0,0 +1,127 @@ +/* + * 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 { + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiTableSortingType, + EuiText, +} from '@elastic/eui'; +import React, { useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { isEqual } from 'lodash'; +import { RowsPerPage } from './rows_per_page'; + +interface PaginationProps { + total?: number; + searchAfter?: string; + onPageChange: (searchAfter?: string) => void; + perPage?: number; + onPerPageChange: (perPage: number) => void; + itemsPerPage?: number[]; + sort: EuiTableSortingType; +} + +export function Pagination({ + total, + onPageChange, + onPerPageChange, + searchAfter, + perPage = 10, + itemsPerPage = [10, 20, 50, 100], + sort, +}: PaginationProps) { + const [paginationHistory, setPaginationHistory] = useState([]); + const [sorting, setSorting] = useState>(sort); + + // When the sorting changes we need to reset the pagination history + useEffect(() => { + if (!isEqual(sorting, sort)) { + setPaginationHistory([]); + setSorting(sort); + } + }, [sorting, sort]); + + const nextOnClickHandler = () => { + setPaginationHistory((prev) => { + if (searchAfter) { + prev.push(searchAfter); + onPageChange(searchAfter); + } + return prev; + }); + }; + + const previousOnClickHandler = () => { + setPaginationHistory((prev) => { + prev.pop(); // this is the current page + onPageChange(prev[prev.length - 1]); // this is the actual previous page + return prev; + }); + }; + + // When the per page changes, we need to reset the pagination history + const handlePerPageChange = (newPerPage: number) => { + setPaginationHistory([]); + onPerPageChange(newPerPage); + }; + + if (!total) return null; + + const disablePrevious = paginationHistory.length === 0; + const disableNext = Math.ceil(total / perPage) === paginationHistory.length + 1; + + return ( + + + + + + + + + {i18n.translate('xpack.entityManager.pagination.pageOfFlexItemLabel', { + defaultMessage: 'Page {current} of {total}', + values: { + current: paginationHistory.length + 1, + total: Math.ceil(total / perPage), + }, + })} + + + + + + + + + + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/rows_per_page.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/rows_per_page.tsx new file mode 100644 index 0000000000000..855b078904863 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/rows_per_page.tsx @@ -0,0 +1,93 @@ +/* + * 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, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButtonEmpty, + EuiContextMenuPanel, + EuiContextMenuItem, + EuiPopover, + useGeneratedHtmlId, +} from '@elastic/eui'; + +interface RowsPerPageProps { + perPage?: number; + itemsPerPage?: number[]; + onPerPageChange: (perPage: number) => void; +} + +export function RowsPerPage({ + onPerPageChange, + perPage = 10, + itemsPerPage = [10, 20, 50], +}: RowsPerPageProps) { + const [isPopoverOpen, setPopover] = useState(false); + + const singleContextMenuPopoverId = useGeneratedHtmlId({ + prefix: 'singleContextMenuPopover', + }); + + const onButtonClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const isSelectedProps = (size: number) => { + return size === perPage + ? { icon: 'check', 'aria-current': true } + : { icon: 'empty', 'aria-current': false }; + }; + + const button = ( + + {i18n.translate('xpack.entityManager.rowsPerPage.buttonLabel', { + defaultMessage: 'Rows per page: {perPage}', + values: { perPage }, + })} + + ); + + const items = itemsPerPage.map((page) => ( + { + closePopover(); + onPerPageChange(page); + }} + > + {i18n.translate('xpack.entityManager.rowsPerPage.contextMenuLabel', { + defaultMessage: '{pagePer} rows', + values: { pagePer: page }, + })} + + )); + + return ( + + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform.tsx new file mode 100644 index 0000000000000..338494d02d399 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform.tsx @@ -0,0 +1,95 @@ +/* + * 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 { EntityDefinitionWithState } from '@kbn/entities-schema'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiStat, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import numeral from '@elastic/numeral'; +import { TransformHealthBadge } from './transform_health_badge'; +import { TransformStatusBadge } from './transform_status_badge'; +import { TransformContent } from './transform_content'; + +interface TransformProps { + type: 'history' | 'latest'; + definition: EntityDefinitionWithState; +} + +const LATEST_TITLE = i18n.translate('xpack.entityManager.transfrom.latestTitle', { + defaultMessage: 'Latest transform', +}); + +const HISTORY_TITLE = i18n.translate('xpack.entityManager.transfrom.historyTitle', { + defaultMessage: 'History transform', +}); + +export function Transform({ definition, type }: TransformProps) { + const title = type === 'history' ? HISTORY_TITLE : LATEST_TITLE; + const transform = definition.resources.transforms[type]; + const stats = definition.resources.transforms.stats[type]; + + if (!transform || !stats) return null; + + return ( + + + + +

{title}

+
+ + + + + + + + + +
+ + + + + + + + + +
+ + +
+ ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_content.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_content.tsx new file mode 100644 index 0000000000000..5f42cf17199ec --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_content.tsx @@ -0,0 +1,96 @@ +/* + * 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 { + TransformGetTransformStatsTransformStats, + TransformGetTransformTransformSummary, +} from '@elastic/elasticsearch/lib/api/types'; +import React, { useMemo, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiCodeBlock, EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui'; +import { TransformStatsPanel } from './transform_stats_panel'; +import { TransformDetailsPanel } from './transform_details_panel'; +import { TransformMessages } from './transform_messages'; + +interface TransformContentProps { + transform: TransformGetTransformTransformSummary; + stats: TransformGetTransformStatsTransformStats; +} + +export function TransformContent({ transform, stats }: TransformContentProps) { + const [selectedTabId, setSelectedTabId] = useState('details'); + + const tabs = useMemo( + () => [ + { + id: 'details', + name: i18n.translate('xpack.entityManager.transformContet.detailsTabLabel', { + defaultMessage: 'Details', + }), + content: , + }, + { + id: 'stats', + name: i18n.translate('xpack.entityManager.transformContet.statsTabLabel', { + defaultMessage: 'Stats', + }), + content: ( + <> + + + + ), + }, + { + id: 'json', + name: i18n.translate('xpack.entityManager.transformContet.jsonTabLabel', { + defaultMessage: 'JSON', + }), + content: ( + + {JSON.stringify(transform, null, 2)} + + ), + }, + { + id: 'messages', + name: i18n.translate('xpack.entityManager.transformContet.messagesTabLabel', { + defaultMessage: 'Messages', + }), + content: , + }, + ], + [stats, transform] + ); + + const selectedTabContent = useMemo(() => { + return tabs.find((obj) => obj.id === selectedTabId)?.content; + }, [selectedTabId, tabs]); + + const onSelectedTabChanged = (id: string) => { + setSelectedTabId(id); + }; + + const renderTabs = () => { + return tabs.map((tab, index) => ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + > + {tab.name} + + )); + }; + + return ( + <> + {renderTabs()} + {selectedTabContent} + + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_details_panel.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_details_panel.tsx new file mode 100644 index 0000000000000..5f7afbe1e9a5b --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_details_panel.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 { + TransformGetTransformStatsTransformStats, + TransformGetTransformTransformSummary, +} from '@elastic/elasticsearch/lib/api/types'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiBasicTable, EuiBasicTableColumn, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { isString, isNumber } from 'lodash'; +import numeral from '@elastic/numeral'; +import moment from 'moment'; + +interface TransformDetailsPanelProps { + transform: TransformGetTransformTransformSummary; + stats: TransformGetTransformStatsTransformStats; +} + +interface Stat { + name: string; + value: string | number | undefined; +} + +export function TransformDetailsPanel({ stats, transform }: TransformDetailsPanelProps) { + const generalStats = [ + { name: 'ID', value: transform.id }, + { name: 'Version', value: transform.version }, + { name: 'Description', value: transform.description }, + { name: 'Created', value: moment(transform.create_time).format('ll LTS') }, + { name: 'Source index', value: [transform.source.index].flat().join(',') }, + { name: 'Dest pipeline', value: transform.dest.pipeline }, + { name: 'Authorization', value: JSON.stringify(transform.authorization) }, + ]; + + const checkpointStats = [ + { + name: 'Last detected chagnes', + value: moment(stats.checkpointing.changes_last_detected_at).format('ll LTS'), + }, + { + name: 'Last checkpoint', + value: `${stats.checkpointing.last.checkpoint}`, + }, + { + name: 'Last timestamp', + value: + stats.checkpointing.last.timestamp_millis != null + ? moment(stats.checkpointing.last.timestamp_millis).format('ll LTS') + : '--', + }, + { + name: 'Last search', + value: + stats.checkpointing.last_search_time != null + ? moment(stats.checkpointing.last_search_time).format('ll LTS') + : '--', + }, + ]; + + const columns: Array> = [ + { + field: 'name', + render: (value: string) => {value}, + } as unknown as EuiBasicTableColumn, + { + field: 'value', + align: 'right', + render: (value: number | undefined | string) => + isString(value) ? value : isNumber(value) ? numeral(value).format('0,0[.0]') : '--', + } as unknown as EuiBasicTableColumn, + ]; + + return ( + <> + + +

+ {i18n.translate('xpack.entityManager.transformDetailsPanel.h4.generalLabel', { + defaultMessage: 'General', + })} +

+
+ + + +

+ {i18n.translate('xpack.entityManager.transformDetailsPanel.h4.checkpointingLabel', { + defaultMessage: 'Checkpointing', + })} +

+
+ + + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_health_badge.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_health_badge.tsx new file mode 100644 index 0000000000000..2a2bcdf515d82 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_health_badge.tsx @@ -0,0 +1,45 @@ +/* + * 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 { EuiBadge } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { HealthStatus } from '@elastic/elasticsearch/lib/api/types'; +import { lowerCase } from 'lodash'; + +type TransformState = 'green' | 'unknown' | 'yellow' | 'red'; + +interface TransformHealthBadgeProps { + healthStatus: HealthStatus | 'unknown'; +} + +const stateToEuiColor: Record = { + green: 'success', + unknown: 'default', + yellow: 'warning', + red: 'critical', +}; + +const stateToLabel: Record = { + green: i18n.translate('xpack.entityManager.transformBadge.greenLabel', { + defaultMessage: 'Healthy', + }), + unknown: i18n.translate('xpack.entityManager.transformBadge.unknownLabel', { + defaultMessage: 'Unknown', + }), + yellow: i18n.translate('xpack.entityManager.transformBadge.degradedLabel', { + defaultMessage: 'Degraded', + }), + red: i18n.translate('xpack.entityManager.transformBadge.unavailableLabel', { + defaultMessage: 'Unavailable', + }), +}; + +export function TransformHealthBadge({ healthStatus }: TransformHealthBadgeProps) { + const state = lowerCase(healthStatus) as TransformState; + return {stateToLabel[state]}; +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_messages.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_messages.tsx new file mode 100644 index 0000000000000..a7977d895ffcf --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_messages.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 { TransformGetTransformTransformSummary } from '@elastic/elasticsearch/lib/api/types'; +import React, { useState } from 'react'; +import { + Criteria, + EuiBasicTable, + EuiBasicTableColumn, + EuiButtonIcon, + EuiIcon, + EuiSpacer, + EuiTableSortingType, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import { + TransformMessage, + useFetchTransformMessages, +} from '../../../hooks/use_fetch_transform_messages'; + +interface TransformMessagesProps { + transform: TransformGetTransformTransformSummary; +} + +export function TransformMessages({ transform }: TransformMessagesProps) { + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [sortField, setSortField] = useState('timestamp'); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); + const { data, refetch } = useFetchTransformMessages(transform.id, sortField, sortDirection); + + const columns: Array> = [ + { + field: 'icon', + name: ( + + refetch()} + data-test-subj="entityManagerTransformMessagesButton" + iconType="refresh" + /> + + ), + render: () => , + align: 'center', + width: '30px', + }, + { + field: 'timestamp', + name: i18n.translate('xpack.entityManager.transformMessages.timestampLabel', { + defaultMessage: 'Timestamp', + }), + sortable: true, + render: (value: string) => moment(value).format('ll HH:mm:ss'), + width: '20%', + }, + { + field: 'message', + name: i18n.translate('xpack.entityManager.transformMessages.messageLabel', { + defaultMessage: 'Message', + }), + sortable: false, + }, + ]; + + const sorting: EuiTableSortingType = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + + const handleTableChange = ({ sort, page }: Criteria) => { + if (sort) { + setSortField(sort.field); + setSortDirection(sort.direction); + } + + if (page) { + setPageIndex(page.index); + setPageSize(page.size); + } + }; + + const items = (data?.messages ?? []).slice(pageIndex * pageSize, pageIndex * pageSize + pageSize); + + const pagination = { + pageSize, + pageIndex, + totalItemCount: data?.total || 0, + pageSizeOptions: [10, 20, 50], + showPerPageOptions: true, + }; + + return ( + <> + + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_stats_panel.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_stats_panel.tsx new file mode 100644 index 0000000000000..7adfdb0401ddd --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_stats_panel.tsx @@ -0,0 +1,51 @@ +/* + * 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 { TransformGetTransformStatsTransformIndexerStats } from '@elastic/elasticsearch/lib/api/types'; +import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import numeral from '@elastic/numeral'; + +interface TransformStatsPanelProps { + stats: TransformGetTransformStatsTransformIndexerStats; +} + +type StatKey = keyof TransformGetTransformStatsTransformIndexerStats; + +interface Stats { + name: StatKey; + value?: number; +} + +export function TransformStatsPanel({ stats }: TransformStatsPanelProps) { + const data: Stats[] = (Object.keys(stats) as StatKey[]).map((name) => ({ + name, + value: stats[name], + })); + + const columns: Array> = [ + { + field: 'name', + name: i18n.translate('xpack.entityManager.transformStatsPanel.nameColumnLabel', { + defaultMessage: 'Name', + }), + render: (value: string) => {value}, + }, + { + field: 'value', + name: i18n.translate('xpack.entityManager.transformStatsPanel.valueColumnLabel', { + defaultMessage: 'Value', + }), + align: 'right', + render: (value: number | undefined) => + value != null ? numeral(value).format('0,0[.0]') : '--', + }, + ]; + + return ; +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_status_badge.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_status_badge.tsx new file mode 100644 index 0000000000000..3355ab509a397 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_status_badge.tsx @@ -0,0 +1,73 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EuiBadge } from '@elastic/eui'; + +interface TransformStatusBadgeProps { + status: string; +} + +function statusToColorAndLabel(status: string) { + switch (status) { + case 'aborting': + return { + color: 'danger', + label: i18n.translate('xpack.entityManager.transformStatusBadge.abortingLabel', { + defaultMessage: 'Aborting', + }), + }; + case 'failed': + return { + color: 'danger', + label: i18n.translate('xpack.entityManager.transformStatusBadge.faliedLabel', { + defaultMessage: 'Failed', + }), + }; + case 'indexing': + return { + color: 'primary', + label: i18n.translate('xpack.entityManager.transformStatusBadge.indexingLabel', { + defaultMessage: 'Indexing', + }), + }; + case 'started': + return { + color: 'primary', + label: i18n.translate('xpack.entityManager.transformStatusBadge.startedLabel', { + defaultMessage: 'Started', + }), + }; + case 'stopped': + return { + color: 'danger', + label: i18n.translate('xpack.entityManager.transformStatusBadge.stoppedLabel', { + defaultMessage: 'Stopped', + }), + }; + case 'stopping': + return { + color: 'danger', + label: i18n.translate('xpack.entityManager.transformStatusBadge.stoppingLabel', { + defaultMessage: 'Stopping', + }), + }; + default: + return { + color: 'default', + label: i18n.translate('xpack.entityManager.transformStatusBadge.unkonwnLabel', { + defaultMessage: 'Unknown', + }), + }; + } +} + +export function TransformStatusBadge({ status }: TransformStatusBadgeProps) { + const { color, label } = statusToColorAndLabel(status); + return {label}; +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transforms.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transforms.tsx new file mode 100644 index 0000000000000..939d8ebe5cd8f --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transforms.tsx @@ -0,0 +1,39 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { EntityDefinitionWithState } from '@kbn/entities-schema'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { Transform } from './transform'; + +interface TransformsProps { + definition: EntityDefinitionWithState; +} + +export function Transforms({ definition }: TransformsProps) { + return ( + + +

+ {i18n.translate('xpack.entityManager.transforms.h2.transformsLabel', { + defaultMessage: 'Transforms', + })} +

+
+ + + + + + + + + +
+ ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/entity_manager_detail.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/entity_manager_detail.tsx new file mode 100644 index 0000000000000..2a413e6e0c40c --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/entity_manager_detail.tsx @@ -0,0 +1,62 @@ +/* + * 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 { useParams } from 'react-router-dom'; +import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; +import { EuiSpacer } from '@elastic/eui'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { useKibana } from '../../hooks/use_kibana'; +import { paths } from '../../../common/locators/paths'; +import { ENTITY_MANAGER_LABEL } from '../../../common/translations'; +import { useFetchEntityDefinition } from '../../hooks/use_fetch_entity_definition'; +import { HeaderDetails } from './components/header_details'; +import { Transforms } from './components/transforms'; +import { DefinitionDetails } from './components/definition_details'; +import { Entities } from './components/entities'; + +export function EntityManagerDetailPage() { + const { + http: { basePath }, + } = useKibana().services; + + const { entityId } = useParams<{ entityId: string }>(); + const { data: definition } = useFetchEntityDefinition({ id: entityId }); + + useBreadcrumbs([ + { + href: basePath.prepend(paths.entities), + text: ENTITY_MANAGER_LABEL, + deepLinkId: 'entityManager', + }, + { + text: definition ? definition.name : entityId, + }, + ]); + + const { ObservabilityPageTemplate } = usePluginContext(); + + if (!definition) { + return null; // TODO: loading screen goes here + } + + return ( + , + }} + > + + + + + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/plugin.ts b/x-pack/plugins/entity_manager/public/plugin.ts index 6d6d56a95b757..644a4af6f9e4e 100644 --- a/x-pack/plugins/entity_manager/public/plugin.ts +++ b/x-pack/plugins/entity_manager/public/plugin.ts @@ -5,23 +5,76 @@ * 2.0. */ -import { CoreSetup, CoreStart, PluginInitializerContext } from '@kbn/core/public'; +import { + App, + AppMountParameters, + AppUpdater, + CoreSetup, + CoreStart, + DEFAULT_APP_CATEGORIES, + PluginInitializerContext, +} from '@kbn/core/public'; import { Logger } from '@kbn/logging'; -import { EntityManagerPluginClass } from './types'; +import { BehaviorSubject } from 'rxjs'; +import { + EntityManagerPluginClass, + EntityManagerPluginSetup, + EntityManagerPluginStart, + EntityManagerPublicPluginStart, +} from './types'; import type { EntityManagerPublicConfig } from '../common/config'; import { EntityClient } from './lib/entity_client'; export class Plugin implements EntityManagerPluginClass { public config: EntityManagerPublicConfig; public logger: Logger; + private readonly appUpdater$ = new BehaviorSubject(() => ({})); - constructor(context: PluginInitializerContext<{}>) { + constructor(private readonly context: PluginInitializerContext<{}>) { this.config = context.config.get(); this.logger = context.logger.get(); } - setup(core: CoreSetup) { + setup( + core: CoreSetup, + pluginSetup: EntityManagerPluginSetup + ) { + const kibanaVersion = this.context.env.packageInfo.version; + + const mount = async (params: AppMountParameters) => { + const { renderApp } = await import('./application'); + const [coreStart, pluginsStart] = await core.getStartServices(); + + return renderApp({ + appMountParameters: params, + core: coreStart, + isDev: this.context.env.mode.dev, + kibanaVersion, + usageCollection: pluginSetup.usageCollection, + ObservabilityPageTemplate: pluginsStart.observabilityShared.navigation.PageTemplate, + plugins: pluginsStart, + isServerless: !!pluginsStart.serverless, + }); + }; + + const appUpdater$ = this.appUpdater$; + const app: App = { + id: 'entity_manager', + title: 'Entity Manager', + order: 8002, + updater$: appUpdater$, + euiIconType: 'logoObservability', + appRoute: '/app/entity_manager', + category: DEFAULT_APP_CATEGORIES.observability, + mount, + visibleIn: ['sideNav'], + keywords: ['observability', 'monitor', 'entities'], + }; + + // Register an application into the side navigation menu + core.application.register(app); + const entityClient = new EntityClient(core); return { entityClient, diff --git a/x-pack/plugins/entity_manager/public/routes.tsx b/x-pack/plugins/entity_manager/public/routes.tsx new file mode 100644 index 0000000000000..ac826024ec160 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/routes.tsx @@ -0,0 +1,34 @@ +/* + * 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 { ENTITY_MANAGER_DETAIL, ENTITY_MANAGER_OVERVIEW } from '../common/locators/paths'; +import { EntityManagerPage } from './pages/entity_manager/entity_manager'; +import { EntityManagerDetailPage } from './pages/entity_manager_detail/entity_manager_detail'; + +interface RouteDef { + [key: string]: { + handler: () => React.ReactElement; + params: Record; + exact: boolean; + }; +} + +export function getRoutes(): RouteDef { + return { + [ENTITY_MANAGER_OVERVIEW]: { + handler: () => , + params: {}, + exact: true, + }, + [ENTITY_MANAGER_DETAIL]: { + handler: () => , + params: {}, + exact: true, + }, + }; +} diff --git a/x-pack/plugins/entity_manager/public/types.ts b/x-pack/plugins/entity_manager/public/types.ts index 66499479299dc..47f20e8754c7c 100644 --- a/x-pack/plugins/entity_manager/public/types.ts +++ b/x-pack/plugins/entity_manager/public/types.ts @@ -4,9 +4,51 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { + ObservabilityPublicSetup, + ObservabilityPublicStart, +} from '@kbn/observability-plugin/public'; +import type { + ObservabilitySharedPluginSetup, + ObservabilitySharedPluginStart, +} from '@kbn/observability-shared-plugin/public'; +import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { CloudStart } from '@kbn/cloud-plugin/public'; +import type { LensPublicStart } from '@kbn/lens-plugin/public'; +import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; + import type { Plugin as PluginClass } from '@kbn/core/public'; +import { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; +import { UsageCollectionSetup, UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import type { EntityClient } from './lib/entity_client'; +export interface EntityManagerPluginSetup { + data: DataPublicPluginSetup; + observability: ObservabilityPublicSetup; + observabilityShared: ObservabilitySharedPluginSetup; + serverless?: ServerlessPluginSetup; + usageCollection: UsageCollectionSetup; +} + +export interface EntityManagerPluginStart { + data: DataPublicPluginStart; + dataViews: DataViewsPublicPluginStart; + dataViewEditor: DataViewEditorStart; + dataViewFieldEditor: DataViewFieldEditorStart; + lens: LensPublicStart; + presentationUtil: PresentationUtilPluginStart; + charts: ChartsPluginStart; + cloud?: CloudStart; + serverless?: ServerlessPluginStart; + usageCollection: UsageCollectionStart; + observability: ObservabilityPublicStart; + observabilityShared: ObservabilitySharedPluginStart; +} + export interface EntityManagerPublicPluginSetup { entityClient: EntityClient; } @@ -17,5 +59,7 @@ export interface EntityManagerPublicPluginStart { export type EntityManagerPluginClass = PluginClass< EntityManagerPublicPluginSetup, - EntityManagerPublicPluginStart + EntityManagerPublicPluginStart, + EntityManagerPluginSetup, + EntityManagerPluginStart >; diff --git a/x-pack/plugins/entity_manager/server/lib/entities/find_entities.ts b/x-pack/plugins/entity_manager/server/lib/entities/find_entities.ts new file mode 100644 index 0000000000000..12596e63bf478 --- /dev/null +++ b/x-pack/plugins/entity_manager/server/lib/entities/find_entities.ts @@ -0,0 +1,81 @@ +/* + * 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 { + QueryDslQueryContainer, + SearchRequest, + SearchTotalHits, +} from '@elastic/elasticsearch/lib/api/types'; +import { + entitiesIndexPattern, + ENTITY_SCHEMA_VERSION_V1, + EntityLatestDoc, +} from '@kbn/entities-schema'; +import { ElasticsearchClient } from '@kbn/core/server'; +import { last } from 'lodash'; +import rison from '@kbn/rison'; +import { getElasticsearchQueryOrThrow } from './helpers/get_elasticsearch_query_or_throw'; + +export async function findEntities( + esClient: ElasticsearchClient, + perPage: number, + query?: string, + searchAfter?: Array, + sortObj?: { field: string; direction: 'asc' | 'desc' } +) { + const filter: QueryDslQueryContainer[] = []; + if (query) { + filter.push(getElasticsearchQueryOrThrow(query)); + } + + const sortField = sortObj?.field ?? 'entity.displayName.keyword'; + const sortDirection = sortObj?.direction ?? 'asc'; + + const sort = + sortField === 'entity.displayName.keyword' + ? [ + { + [sortField]: sortDirection, + }, + ] + : [ + { + [sortField]: sortDirection, + }, + { + ['entity.displayName.keyword']: sortDirection, + }, + ]; + + const params: SearchRequest = { + index: entitiesIndexPattern({ + schemaVersion: ENTITY_SCHEMA_VERSION_V1, + dataset: 'latest', + definitionId: '*', + }), + track_total_hits: true, + size: perPage, + query: { + bool: { + filter, + }, + }, + sort, + }; + + if (searchAfter) { + params.search_after = searchAfter; + } + + const response = await esClient.search(params); + const lastHit = last(response.hits.hits); + return { + entities: response.hits.hits.map((doc) => doc._source), + total: (response.hits.total as SearchTotalHits).value, + searchAfter: lastHit != null ? rison.encode(lastHit?.sort) : undefined, + }; +} diff --git a/x-pack/plugins/entity_manager/server/lib/entities/get_entity_definition_stats.ts b/x-pack/plugins/entity_manager/server/lib/entities/get_entity_definition_stats.ts new file mode 100644 index 0000000000000..6d91da9f3a5b2 --- /dev/null +++ b/x-pack/plugins/entity_manager/server/lib/entities/get_entity_definition_stats.ts @@ -0,0 +1,70 @@ +/* + * 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 { ElasticsearchClient } from '@kbn/core/server'; +import { EntityDefinition } from '@kbn/entities-schema'; +import { SearchRequest, SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; +import { generateHistoryIndexName, generateLatestIndexName } from './helpers/generate_component_id'; + +interface EntityCountAggregationResponse { + entityCount: { + value: number; + }; +} + +export async function getEntityDefinitionStats( + esClient: ElasticsearchClient, + definition: EntityDefinition +) { + const entityCount = await getEntityCount(esClient, definition); + const historyStats = await getHistoryStats(esClient, definition); + return { ...historyStats, entityCount }; +} + +export async function getHistoryStats(esClient: ElasticsearchClient, definition: EntityDefinition) { + const params: SearchRequest = { + ignore_unavailable: true, + allow_no_indices: true, + track_total_hits: true, + index: `${generateHistoryIndexName(definition)}.*`, + size: 1, + _source: ['@timestamp'], + sort: [ + { + '@timestamp': { order: 'desc' }, + }, + ], + }; + + const response = await esClient.search<{ '@timestamp': string }>(params); + const total = response.hits.total as SearchTotalHits; + return { + totalDocs: total.value, + lastSeenTimestamp: + total.value && response.hits.hits[0]._source + ? response.hits.hits[0]._source['@timestamp'] + : null, + }; +} + +export async function getEntityCount(esClient: ElasticsearchClient, definition: EntityDefinition) { + const params: SearchRequest = { + ignore_unavailable: true, + allow_no_indices: true, + index: generateLatestIndexName(definition), + size: 0, + aggs: { + entityCount: { + cardinality: { + field: 'entity.id', + }, + }, + }, + }; + const response = await esClient.search(params); + return response.aggregations?.entityCount.value ?? 0; +} diff --git a/x-pack/plugins/entity_manager/server/lib/entity_client.ts b/x-pack/plugins/entity_manager/server/lib/entity_client.ts index 5bd9154ec9daf..5fae81f3571a9 100644 --- a/x-pack/plugins/entity_manager/server/lib/entity_client.ts +++ b/x-pack/plugins/entity_manager/server/lib/entity_client.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EntityDefinition } from '@kbn/entities-schema'; +import { EntityDefinition, FindEntitiesQuery } from '@kbn/entities-schema'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { Logger } from '@kbn/logging'; @@ -16,6 +16,7 @@ import { uninstallEntityDefinition } from './entities/uninstall_entity_definitio import { EntityDefinitionNotFound } from './entities/errors/entity_not_found'; import { stopTransforms } from './entities/stop_transforms'; +import { findEntities } from './entities/find_entities'; export class EntityClient { constructor( @@ -93,6 +94,29 @@ export class EntityClient { return { definitions }; } + async getEntityDefinition({ id }: { id: string }) { + const definitions = await findEntityDefinitions({ + esClient: this.options.esClient, + soClient: this.options.soClient, + id, + }); + + return definitions[0] || null; + } + + async findEntities({ + perPage = 10, + query = '', + searchAfter, + sortField = '@timestamp', + sortDirection = 'asc', + }: FindEntitiesQuery) { + return await findEntities(this.options.esClient, perPage, query, searchAfter, { + field: sortField, + direction: sortDirection, + }); + } + async startEntityDefinition(definition: EntityDefinition) { return startTransforms(this.options.esClient, definition, this.options.logger); } diff --git a/x-pack/plugins/entity_manager/server/routes/entities/create.ts b/x-pack/plugins/entity_manager/server/routes/entities/definition/create.ts similarity index 82% rename from x-pack/plugins/entity_manager/server/routes/entities/create.ts rename to x-pack/plugins/entity_manager/server/routes/entities/definition/create.ts index 114764444632f..4e4ca05bfc107 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/create.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/definition/create.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { createEntityDefinitionQuerySchema, entityDefinitionSchema } from '@kbn/entities-schema'; import { z } from '@kbn/zod'; -import { EntityDefinitionIdInvalid } from '../../lib/entities/errors/entity_definition_id_invalid'; -import { EntityIdConflict } from '../../lib/entities/errors/entity_id_conflict_error'; -import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; -import { InvalidTransformError } from '../../lib/entities/errors/invalid_transform_error'; -import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; +import { entityDefinitionSchema, createEntityDefinitionQuerySchema } from '@kbn/entities-schema'; +import { EntityDefinitionIdInvalid } from '../../../lib/entities/errors/entity_definition_id_invalid'; +import { createEntityManagerServerRoute } from '../../create_entity_manager_server_route'; +import { EntityIdConflict } from '../../../lib/entities/errors/entity_id_conflict_error'; +import { EntitySecurityException } from '../../../lib/entities/errors/entity_security_exception'; +import { InvalidTransformError } from '../../../lib/entities/errors/invalid_transform_error'; /** * @openapi diff --git a/x-pack/plugins/entity_manager/server/routes/entities/delete.ts b/x-pack/plugins/entity_manager/server/routes/entities/definition/delete.ts similarity index 87% rename from x-pack/plugins/entity_manager/server/routes/entities/delete.ts rename to x-pack/plugins/entity_manager/server/routes/entities/definition/delete.ts index 840f3746bc9bb..48ccea7b1eb36 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/delete.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/definition/delete.ts @@ -10,10 +10,10 @@ import { deleteEntityDefinitionQuerySchema, } from '@kbn/entities-schema'; import { z } from '@kbn/zod'; -import { EntityDefinitionNotFound } from '../../lib/entities/errors/entity_not_found'; -import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; -import { InvalidTransformError } from '../../lib/entities/errors/invalid_transform_error'; -import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; +import { EntityDefinitionNotFound } from '../../../lib/entities/errors/entity_not_found'; +import { EntitySecurityException } from '../../../lib/entities/errors/entity_security_exception'; +import { InvalidTransformError } from '../../../lib/entities/errors/invalid_transform_error'; +import { createEntityManagerServerRoute } from '../../create_entity_manager_server_route'; /** * @openapi diff --git a/x-pack/plugins/entity_manager/server/routes/entities/get.ts b/x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts similarity index 76% rename from x-pack/plugins/entity_manager/server/routes/entities/get.ts rename to x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts index 8ec2489136fb1..78d3b05920973 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/get.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts @@ -4,12 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +<<<<<<<< HEAD:x-pack/plugins/entity_manager/server/routes/entities/get.ts import { getEntityDefinitionQuerySchema } from '@kbn/entities-schema'; +======== + +import { findEntityDefinitionQuerySchema } from '@kbn/entities-schema'; +>>>>>>>> e83ab3a71f9 ([EEM] UI Poc):x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts import { z } from '@kbn/zod'; -import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; +import { createEntityManagerServerRoute } from '../../create_entity_manager_server_route'; -/** - * @openapi +/** @openapi * /internal/entities/definition: * get: * description: Get all installed entity definitions. @@ -48,11 +52,18 @@ import { createEntityManagerServerRoute } from '../create_entity_manager_server_ * allOf: * - $ref: '#/components/schemas/entityDefinitionSchema' */ +<<<<<<<< HEAD:x-pack/plugins/entity_manager/server/routes/entities/get.ts export const getEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'GET /internal/entities/definition/{id?}', params: z.object({ query: getEntityDefinitionQuerySchema, path: z.object({ id: z.optional(z.string()) }), +======== +export const findEntityDefinitionRoute = createEntityManagerServerRoute({ + endpoint: 'GET /internal/entities/definition', + params: z.object({ + query: findEntityDefinitionQuerySchema, +>>>>>>>> e83ab3a71f9 ([EEM] UI Poc):x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts }), handler: async ({ request, response, params, logger, getScopedClient }) => { try { diff --git a/x-pack/plugins/entity_manager/server/routes/entities/definition/get.ts b/x-pack/plugins/entity_manager/server/routes/entities/definition/get.ts new file mode 100644 index 0000000000000..08b902ba74425 --- /dev/null +++ b/x-pack/plugins/entity_manager/server/routes/entities/definition/get.ts @@ -0,0 +1,74 @@ +/* + * 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 { getEntityDefinitionParamsSchema } from '@kbn/entities-schema'; +import { z } from '@kbn/zod'; +import { createEntityManagerServerRoute } from '../../create_entity_manager_server_route'; + +/** @openapi + * /internal/entities/definition: + * get: + * description: Get all installed entity definitions. + * tags: + * - definitions + * parameters: + * - in: query + * name: page + * schema: + * $ref: '#/components/schemas/getEntityDefinitionQuerySchema/properties/page' + * - in: query + * name: perPage + * schema: + * $ref: '#/components/schemas/getEntityDefinitionQuerySchema/properties/perPage' + * responses: + * 200: + * description: OK + * content: + * application/json: + * schema: + * type: object + * properties: + * definitions: + * type: array + * items: + * allOf: + * - $ref: '#/components/schemas/entityDefinitionSchema' + * - type: object + * properties: + * state: + * type: object + * properties: + * installed: + * type: boolean + * running: + * type: boolean + * 404: + * description: Not found + */ +export const getEntityDefinitionRoute = createEntityManagerServerRoute({ + endpoint: 'GET /internal/entities/definition/{id}', + params: z.object({ + path: getEntityDefinitionParamsSchema, + }), + handler: async ({ request, response, params, logger, getScopedClient }) => { + try { + const client = await getScopedClient({ request }); + const result = await client.getEntityDefinition({ + id: params.path.id, + }); + + if (result === null) { + return response.notFound(); + } + + return response.ok({ body: result }); + } catch (e) { + logger.error(e); + return response.customError({ body: e, statusCode: 500 }); + } + }, +}); diff --git a/x-pack/plugins/entity_manager/server/routes/entities/reset.ts b/x-pack/plugins/entity_manager/server/routes/entities/definition/reset.ts similarity index 71% rename from x-pack/plugins/entity_manager/server/routes/entities/reset.ts rename to x-pack/plugins/entity_manager/server/routes/entities/definition/reset.ts index a59c38b3acf7c..a5c8663d75bdc 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/reset.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/definition/reset.ts @@ -8,32 +8,31 @@ import { resetEntityDefinitionParamsSchema } from '@kbn/entities-schema'; import { z } from '@kbn/zod'; -import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; -import { InvalidTransformError } from '../../lib/entities/errors/invalid_transform_error'; -import { readEntityDefinition } from '../../lib/entities/read_entity_definition'; - +import { EntitySecurityException } from '../../../lib/entities/errors/entity_security_exception'; +import { InvalidTransformError } from '../../../lib/entities/errors/invalid_transform_error'; +import { readEntityDefinition } from '../../../lib/entities/read_entity_definition'; import { deleteHistoryIngestPipeline, deleteLatestIngestPipeline, -} from '../../lib/entities/delete_ingest_pipeline'; -import { deleteIndices } from '../../lib/entities/delete_index'; +} from '../../../lib/entities/delete_ingest_pipeline'; +import { deleteIndices } from '../../../lib/entities/delete_index'; import { createAndInstallHistoryIngestPipeline, createAndInstallLatestIngestPipeline, -} from '../../lib/entities/create_and_install_ingest_pipeline'; +} from '../../../lib/entities/create_and_install_ingest_pipeline'; import { createAndInstallHistoryBackfillTransform, createAndInstallHistoryTransform, createAndInstallLatestTransform, -} from '../../lib/entities/create_and_install_transform'; -import { startTransforms } from '../../lib/entities/start_transforms'; -import { EntityDefinitionNotFound } from '../../lib/entities/errors/entity_not_found'; +} from '../../../lib/entities/create_and_install_transform'; +import { startTransforms } from '../../../lib/entities/start_transforms'; +import { EntityDefinitionNotFound } from '../../../lib/entities/errors/entity_not_found'; -import { isBackfillEnabled } from '../../lib/entities/helpers/is_backfill_enabled'; +import { isBackfillEnabled } from '../../../lib/entities/helpers/is_backfill_enabled'; -import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; -import { deleteTransforms } from '../../lib/entities/delete_transforms'; -import { stopTransforms } from '../../lib/entities/stop_transforms'; +import { createEntityManagerServerRoute } from '../../create_entity_manager_server_route'; +import { deleteTransforms } from '../../../lib/entities/delete_transforms'; +import { stopTransforms } from '../../../lib/entities/stop_transforms'; export const resetEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'POST /internal/entities/definition/{id}/_reset', diff --git a/x-pack/plugins/entity_manager/server/routes/entities/update.ts b/x-pack/plugins/entity_manager/server/routes/entities/definition/update.ts similarity index 87% rename from x-pack/plugins/entity_manager/server/routes/entities/update.ts rename to x-pack/plugins/entity_manager/server/routes/entities/definition/update.ts index 9cf72a9298d42..5aa6eb902a851 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/update.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/definition/update.ts @@ -10,16 +10,16 @@ import { entityDefinitionUpdateSchema, } from '@kbn/entities-schema'; import { z } from '@kbn/zod'; -import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; -import { InvalidTransformError } from '../../lib/entities/errors/invalid_transform_error'; -import { findEntityDefinitionById } from '../../lib/entities/find_entity_definition'; -import { startTransforms } from '../../lib/entities/start_transforms'; +import { EntitySecurityException } from '../../../lib/entities/errors/entity_security_exception'; +import { InvalidTransformError } from '../../../lib/entities/errors/invalid_transform_error'; +import { findEntityDefinitionById } from '../../../lib/entities/find_entity_definition'; +import { startTransforms } from '../../../lib/entities/start_transforms'; import { installationInProgress, reinstallEntityDefinition, -} from '../../lib/entities/install_entity_definition'; +} from '../../../lib/entities/install_entity_definition'; -import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; +import { createEntityManagerServerRoute } from '../../create_entity_manager_server_route'; /** * @openapi diff --git a/x-pack/plugins/entity_manager/server/routes/entities/find.ts b/x-pack/plugins/entity_manager/server/routes/entities/find.ts new file mode 100644 index 0000000000000..c03aa778ae255 --- /dev/null +++ b/x-pack/plugins/entity_manager/server/routes/entities/find.ts @@ -0,0 +1,26 @@ +/* + * 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 { findEntitiesQuerySchema } from '@kbn/entities-schema'; +import { z } from '@kbn/zod'; +import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; + +export const findEntitiesRoute = createEntityManagerServerRoute({ + endpoint: 'GET /internal/entities/_find', + params: z.object({ + query: findEntitiesQuerySchema, + }), + handler: async ({ request, response, params, logger, getScopedClient }) => { + try { + const client = await getScopedClient({ request }); + const body = await client.findEntities(params.query); + return response.ok({ body }); + } catch (e) { + logger.error(e); + return response.customError({ body: e, statusCode: 500 }); + } + }, +}); diff --git a/x-pack/plugins/entity_manager/server/routes/entities/index.ts b/x-pack/plugins/entity_manager/server/routes/entities/index.ts index 539423c6a5e17..fb7dc250344f0 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/index.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/index.ts @@ -5,16 +5,21 @@ * 2.0. */ -import { createEntityDefinitionRoute } from './create'; -import { deleteEntityDefinitionRoute } from './delete'; -import { getEntityDefinitionRoute } from './get'; -import { resetEntityDefinitionRoute } from './reset'; -import { updateEntityDefinitionRoute } from './update'; +import { createEntityDefinitionRoute } from './definition/create'; +import { deleteEntityDefinitionRoute } from './definition/delete'; +import { findEntityDefinitionRoute } from './definition/find'; +import { resetEntityDefinitionRoute } from './definition/reset'; +import { updateEntityDefinitionRoute } from './definition/update'; +import { getEntityDefinitionRoute } from './definition/get'; + +import { findEntitiesRoute } from './find'; export const entitiesRoutes = { ...createEntityDefinitionRoute, ...deleteEntityDefinitionRoute, - ...getEntityDefinitionRoute, + ...findEntityDefinitionRoute, ...resetEntityDefinitionRoute, ...updateEntityDefinitionRoute, + ...getEntityDefinitionRoute, + ...findEntitiesRoute, }; diff --git a/x-pack/plugins/entity_manager/tsconfig.json b/x-pack/plugins/entity_manager/tsconfig.json index 29c100ee4c9d2..7c2224648a787 100644 --- a/x-pack/plugins/entity_manager/tsconfig.json +++ b/x-pack/plugins/entity_manager/tsconfig.json @@ -15,24 +15,31 @@ ], "kbn_references": [ "@kbn/config-schema", - "@kbn/entities-schema", "@kbn/core", - "@kbn/core-plugins-server", - "@kbn/server-route-repository-client", - "@kbn/logging", - "@kbn/core-http-server", - "@kbn/security-plugin", - "@kbn/es-query", + "@kbn/core", + "@kbn/core-elasticsearch-client-server-mocks", "@kbn/core-elasticsearch-server", + "@kbn/core-http-server", + "@kbn/core-plugins-server", + "@kbn/core-plugins-server", "@kbn/core-saved-objects-api-server", - "@kbn/core-elasticsearch-client-server-mocks", "@kbn/core-saved-objects-api-server-mocks", - "@kbn/logging-mocks", "@kbn/datemath", - "@kbn/server-route-repository", - "@kbn/zod", - "@kbn/zod-helpers", "@kbn/encrypted-saved-objects-plugin", + "@kbn/entities-schema", + "@kbn/es-query", + "@kbn/i18n", + "@kbn/i18n-react", "@kbn/licensing-plugin", + "@kbn/logging", + "@kbn/logging-mocks", + "@kbn/observability-plugin", + "@kbn/observability-shared-plugin", + "@kbn/security-plugin", + "@kbn/server-route-repository", + "@kbn/server-route-repository-client", + "@kbn/shared-ux-router", + "@kbn/zod", + "@kbn/zod-helpers" ] } diff --git a/x-pack/plugins/observability_solution/observability/public/plugin.ts b/x-pack/plugins/observability_solution/observability/public/plugin.ts index 5866a082556bb..4222c58b5d22b 100644 --- a/x-pack/plugins/observability_solution/observability/public/plugin.ts +++ b/x-pack/plugins/observability_solution/observability/public/plugin.ts @@ -387,6 +387,17 @@ export class Plugin ] : []; + const entityManagerLink: NavigationEntry[] = [ + { + label: i18n.translate('xpack.observability.entityManagerLinkTitle', { + defaultMessage: 'Entity Manager', + }), + app: 'entity_manager', + isTechnicalPreview: true, + path: '/', + }, + ]; + // Reformat the visible links to be NavigationEntry objects instead of // AppDeepLink objects. // @@ -424,6 +435,7 @@ export class Plugin ...sloLink, ...casesLink, ...aiAssistantLink, + ...entityManagerLink, ], }, ]; From 1698dd1b616145df02eaab6c31b39ff7e31f60ce Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Fri, 27 Sep 2024 14:21:07 -0600 Subject: [PATCH 02/12] Fixing things broken by the merge --- .../src/rest_spec/definition/find.ts | 43 ++++++++----- .../common/helpers/generate_component_id.ts | 60 +++++++++++++++++++ .../stats/history_checkpoint_duration.tsx | 14 ++++- .../stats/latest_checkpoint_duration.tsx | 13 +++- .../hooks/use_fetch_entity_definitions.ts | 3 + .../components/definition_listing.tsx | 21 +++++-- .../components/transform.tsx | 17 +++++- .../lib/entities/find_entity_definition.ts | 31 +++++++--- .../entities/helpers/generate_component_id.ts | 58 +----------------- .../server/lib/entity_client.ts | 47 ++++++++++----- .../plugins/entity_manager/server/plugin.ts | 4 +- .../server/routes/entities/definition/find.ts | 41 ++++--------- .../server/routes/entities/index.ts | 4 +- 13 files changed, 213 insertions(+), 143 deletions(-) create mode 100644 x-pack/plugins/entity_manager/common/helpers/generate_component_id.ts diff --git a/x-pack/packages/kbn-entities-schema/src/rest_spec/definition/find.ts b/x-pack/packages/kbn-entities-schema/src/rest_spec/definition/find.ts index 16587d13b4fc9..9551155a89d2b 100644 --- a/x-pack/packages/kbn-entities-schema/src/rest_spec/definition/find.ts +++ b/x-pack/packages/kbn-entities-schema/src/rest_spec/definition/find.ts @@ -7,15 +7,17 @@ import { z } from '@kbn/zod'; import { - IngestGetPipelineResponse, + IndicesGetIndexTemplateIndexTemplateItem, TransformGetTransformStatsTransformStats, TransformGetTransformTransformSummary, } from '@elastic/elasticsearch/lib/api/types'; +import { BooleanFromString } from '@kbn/zod-helpers'; import { entityDefinitionSchema } from '../../schema/entity_definition'; export const findEntityDefinitionQuerySchema = z.object({ page: z.optional(z.coerce.number()), perPage: z.optional(z.coerce.number()), + includeState: z.optional(BooleanFromString).default(false), }); export type FindEntityDefinitionQuery = z.infer; @@ -24,10 +26,6 @@ export const entitiyDefinitionWithStateSchema = entityDefinitionSchema.extend({ state: z.object({ installed: z.boolean(), running: z.boolean(), - avgCheckpointDuration: z.object({ - history: z.number().or(z.null()), - latest: z.number().or(z.null()), - }), }), stats: z.object({ entityCount: z.number(), @@ -36,17 +34,34 @@ export const entitiyDefinitionWithStateSchema = entityDefinitionSchema.extend({ }), }); +interface IngestPipelineState { + id: string; + installed: boolean; + stats: { + count: number; + failed: number; + }; +} + +interface IndexTemplateState { + id: string; + installed: boolean; + template: IndicesGetIndexTemplateIndexTemplateItem; +} + +interface TransformState { + id: string; + installed: boolean; + running: boolean; + summary: TransformGetTransformTransformSummary; + stats: TransformGetTransformStatsTransformStats; +} + export type EntityDefinitionWithState = z.infer & { resources: { - ingestPipelines: IngestGetPipelineResponse; - transforms: { - history?: TransformGetTransformTransformSummary; - latest?: TransformGetTransformTransformSummary; - stats: { - history?: TransformGetTransformStatsTransformStats; - latest?: TransformGetTransformStatsTransformStats; - }; - }; + ingestPipelines: IngestPipelineState[]; + indexTemplates: IndexTemplateState[]; + transforms: TransformState[]; }; }; diff --git a/x-pack/plugins/entity_manager/common/helpers/generate_component_id.ts b/x-pack/plugins/entity_manager/common/helpers/generate_component_id.ts new file mode 100644 index 0000000000000..4cf833a0fd7e6 --- /dev/null +++ b/x-pack/plugins/entity_manager/common/helpers/generate_component_id.ts @@ -0,0 +1,60 @@ +/* + * 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 { + ENTITY_BASE_PREFIX, + ENTITY_HISTORY, + ENTITY_LATEST, + ENTITY_SCHEMA_VERSION_V1, + EntityDefinition, + entitiesIndexPattern, +} from '@kbn/entities-schema'; +import { ENTITY_HISTORY_PREFIX_V1, ENTITY_LATEST_PREFIX_V1 } from '../constants_entities'; + +// History +function generateHistoryId(definition: EntityDefinition) { + return `${ENTITY_HISTORY_PREFIX_V1}-${definition.id}` as const; +} + +// History Backfill +export function generateHistoryBackfillTransformId(definition: EntityDefinition) { + return `${ENTITY_HISTORY_PREFIX_V1}-backfill-${definition.id}` as const; +} + +export const generateHistoryTransformId = generateHistoryId; +export const generateHistoryIngestPipelineId = generateHistoryId; + +export function generateHistoryIndexName(definition: EntityDefinition) { + return entitiesIndexPattern({ + schemaVersion: ENTITY_SCHEMA_VERSION_V1, + dataset: ENTITY_HISTORY, + definitionId: definition.id, + }); +} + +export function generateHistoryIndexTemplateId(definition: EntityDefinition) { + return `${ENTITY_BASE_PREFIX}_${ENTITY_SCHEMA_VERSION_V1}_${ENTITY_HISTORY}_${definition.id}_index_template` as const; +} + +// Latest +function generateLatestId(definition: EntityDefinition) { + return `${ENTITY_LATEST_PREFIX_V1}-${definition.id}` as const; +} + +export const generateLatestTransformId = generateLatestId; +export const generateLatestIngestPipelineId = generateLatestId; + +export function generateLatestIndexName(definition: EntityDefinition) { + return entitiesIndexPattern({ + schemaVersion: ENTITY_SCHEMA_VERSION_V1, + dataset: ENTITY_LATEST, + definitionId: definition.id, + }); +} + +export const generateLatestIndexTemplateId = (definition: EntityDefinition) => + `${ENTITY_BASE_PREFIX}_${ENTITY_SCHEMA_VERSION_V1}_${ENTITY_LATEST}_${definition.id}_index_template` as const; diff --git a/x-pack/plugins/entity_manager/public/components/stats/history_checkpoint_duration.tsx b/x-pack/plugins/entity_manager/public/components/stats/history_checkpoint_duration.tsx index df777caafa018..3d2318f6a15a2 100644 --- a/x-pack/plugins/entity_manager/public/components/stats/history_checkpoint_duration.tsx +++ b/x-pack/plugins/entity_manager/public/components/stats/history_checkpoint_duration.tsx @@ -10,18 +10,26 @@ import { i18n } from '@kbn/i18n'; import numeral from '@elastic/numeral'; import { EuiStat } from '@elastic/eui'; import { DefinitionStatProps } from './types'; +import { generateHistoryTransformId } from '../../../common/helpers/generate_component_id'; export function HistoryCheckpointDurationStat({ definition, textAlign, titleSize, }: DefinitionStatProps) { + const transformState = definition.resources.transforms.find( + (doc) => doc.id === generateHistoryTransformId(definition) + ); + const value = + transformState != null + ? numeral(transformState.stats.stats.exponential_avg_checkpoint_duration_ms).format('0,0') + + 'ms' + : 'N/A'; + return ( doc.id === generateLatestTransformId(definition) + ); + const value = + transformState != null + ? numeral(transformState.stats.stats.exponential_avg_checkpoint_duration_ms).format('0,0') + + 'ms' + : 'N/A'; return ( ('/internal/entities/definition', { signal, + query: { + includeState: true, + }, }); return response.definitions; } catch (e) { diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/components/definition_listing.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/components/definition_listing.tsx index 97591759df0d4..60b5874b81042 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/components/definition_listing.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/components/definition_listing.tsx @@ -28,6 +28,19 @@ function Listing({ definition }: ListingProps) { } = useKibana().services; const entityDetailUrl = basePath.prepend(paths.entitieDetail(definition.id)); + const checkpointDuration = definition.resources.transforms.reduce( + (acc, transformState) => { + if (transformState.stats.stats.exponential_avg_checkpoint_duration_ms > acc.duration) { + return { + duration: transformState.stats.stats.exponential_avg_checkpoint_duration_ms, + id: transformState.id, + }; + } + return acc; + }, + { duration: 0, id: '' } + ); + return ( @@ -55,16 +68,12 @@ function Listing({ definition }: ListingProps) { diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform.tsx index 338494d02d399..fe9842d7bd2e0 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform.tsx @@ -13,6 +13,10 @@ import numeral from '@elastic/numeral'; import { TransformHealthBadge } from './transform_health_badge'; import { TransformStatusBadge } from './transform_status_badge'; import { TransformContent } from './transform_content'; +import { + generateHistoryTransformId, + generateLatestTransformId, +} from '../../../../common/helpers/generate_component_id'; interface TransformProps { type: 'history' | 'latest'; @@ -28,9 +32,16 @@ const HISTORY_TITLE = i18n.translate('xpack.entityManager.transfrom.historyTitle }); export function Transform({ definition, type }: TransformProps) { - const title = type === 'history' ? HISTORY_TITLE : LATEST_TITLE; - const transform = definition.resources.transforms[type]; - const stats = definition.resources.transforms.stats[type]; + const isHistory = type === 'history'; + const title = isHistory ? HISTORY_TITLE : LATEST_TITLE; + + const id = isHistory + ? generateHistoryTransformId(definition) + : generateLatestTransformId(definition); + + const transformState = definition.resources.transforms.find((doc) => doc.id === id); + const transform = transformState?.summary; + const stats = transformState?.stats; if (!transform || !stats) return null; diff --git a/x-pack/plugins/entity_manager/server/lib/entities/find_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/find_entity_definition.ts index 6932394a14e69..f921d3436eb5e 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/find_entity_definition.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/find_entity_definition.ts @@ -20,8 +20,9 @@ import { generateLatestIndexTemplateId, } from './helpers/generate_component_id'; import { BUILT_IN_ID_PREFIX } from './built_in'; -import { EntityDefinitionState, EntityDefinitionWithState } from './types'; +import { EntityDefinitionWithState } from './types'; import { isBackfillEnabled } from './helpers/is_backfill_enabled'; +import { getEntityDefinitionStats } from './get_entity_definition_stats'; export async function findEntityDefinitions({ soClient, @@ -59,8 +60,14 @@ export async function findEntityDefinitions({ return Promise.all( response.saved_objects.map(async ({ attributes }) => { - const state = await getEntityDefinitionState(esClient, attributes); - return { ...attributes, state }; + const { state, resources } = await getEntityDefinitionState(esClient, attributes); + const stats = await getEntityDefinitionStats(esClient, attributes); + return { + ...attributes, + state, + resources, + stats, + }; }) ); } @@ -90,7 +97,7 @@ export async function findEntityDefinitionById({ async function getEntityDefinitionState( esClient: ElasticsearchClient, definition: EntityDefinition -): Promise { +) { const [ingestPipelines, transforms, indexTemplates] = await Promise.all([ getIngestPipelineState({ definition, esClient }), getTransformState({ definition, esClient }), @@ -104,9 +111,8 @@ async function getEntityDefinitionState( const running = transforms.every((transform) => transform.running); return { - installed, - running, - components: { transforms, ingestPipelines, indexTemplates }, + state: { installed, running }, + resources: { transforms, ingestPipelines, indexTemplates }, }; } @@ -127,9 +133,15 @@ async function getTransformState({ transformIds.map((id) => esClient.transform.getTransformStats({ transform_id: id })) ).then((results) => results.map(({ transforms }) => transforms).flat()); + const transformSummaries = await Promise.all( + transformIds.map((id) => esClient.transform.getTransform({ transform_id: id })) + ).then((results) => results.map(({ transforms }) => transforms).flat()); + return transformIds.map((id) => { + const summary = transformSummaries.find((transform) => transform.id === id); const stats = transformStats.find((transform) => transform.id === id); - if (!stats) { + + if (!stats || !summary) { return { id, installed: false, running: false }; } @@ -137,6 +149,7 @@ async function getTransformState({ id, installed: true, running: stats.state === 'started' || stats.state === 'indexing', + summary, stats, }; }); @@ -209,7 +222,7 @@ async function getIndexTemplatesState({ return { id, installed: true, - stats: template.index_template, + template: template.index_template, }; }); } diff --git a/x-pack/plugins/entity_manager/server/lib/entities/helpers/generate_component_id.ts b/x-pack/plugins/entity_manager/server/lib/entities/helpers/generate_component_id.ts index 3afb6034e6a17..3b0764dc61373 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/helpers/generate_component_id.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/helpers/generate_component_id.ts @@ -4,60 +4,4 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { - ENTITY_BASE_PREFIX, - ENTITY_HISTORY, - ENTITY_LATEST, - ENTITY_SCHEMA_VERSION_V1, - EntityDefinition, - entitiesIndexPattern, -} from '@kbn/entities-schema'; -import { - ENTITY_HISTORY_PREFIX_V1, - ENTITY_LATEST_PREFIX_V1, -} from '../../../../common/constants_entities'; - -// History -function generateHistoryId(definition: EntityDefinition) { - return `${ENTITY_HISTORY_PREFIX_V1}-${definition.id}` as const; -} - -// History Backfill -export function generateHistoryBackfillTransformId(definition: EntityDefinition) { - return `${ENTITY_HISTORY_PREFIX_V1}-backfill-${definition.id}` as const; -} - -export const generateHistoryTransformId = generateHistoryId; -export const generateHistoryIngestPipelineId = generateHistoryId; - -export function generateHistoryIndexName(definition: EntityDefinition) { - return entitiesIndexPattern({ - schemaVersion: ENTITY_SCHEMA_VERSION_V1, - dataset: ENTITY_HISTORY, - definitionId: definition.id, - }); -} - -export function generateHistoryIndexTemplateId(definition: EntityDefinition) { - return `${ENTITY_BASE_PREFIX}_${ENTITY_SCHEMA_VERSION_V1}_${ENTITY_HISTORY}_${definition.id}_index_template` as const; -} - -// Latest -function generateLatestId(definition: EntityDefinition) { - return `${ENTITY_LATEST_PREFIX_V1}-${definition.id}` as const; -} - -export const generateLatestTransformId = generateLatestId; -export const generateLatestIngestPipelineId = generateLatestId; - -export function generateLatestIndexName(definition: EntityDefinition) { - return entitiesIndexPattern({ - schemaVersion: ENTITY_SCHEMA_VERSION_V1, - dataset: ENTITY_LATEST, - definitionId: definition.id, - }); -} - -export const generateLatestIndexTemplateId = (definition: EntityDefinition) => - `${ENTITY_BASE_PREFIX}_${ENTITY_SCHEMA_VERSION_V1}_${ENTITY_LATEST}_${definition.id}_index_template` as const; +export * from '../../../../common/helpers/generate_component_id'; diff --git a/x-pack/plugins/entity_manager/server/lib/entity_client.ts b/x-pack/plugins/entity_manager/server/lib/entity_client.ts index 5fae81f3571a9..c1d6870b7ac8d 100644 --- a/x-pack/plugins/entity_manager/server/lib/entity_client.ts +++ b/x-pack/plugins/entity_manager/server/lib/entity_client.ts @@ -7,7 +7,7 @@ import { EntityDefinition, FindEntitiesQuery } from '@kbn/entities-schema'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { Logger } from '@kbn/logging'; import { installEntityDefinition } from './entities/install_entity_definition'; import { startTransforms } from './entities/start_transforms'; @@ -21,7 +21,7 @@ import { findEntities } from './entities/find_entities'; export class EntityClient { constructor( private options: { - esClient: ElasticsearchClient; + scopedClusterClient: IScopedClusterClient; soClient: SavedObjectsClientContract; logger: Logger; } @@ -37,12 +37,16 @@ export class EntityClient { const installedDefinition = await installEntityDefinition({ definition, soClient: this.options.soClient, - esClient: this.options.esClient, + esClient: this.options.scopedClusterClient.asSecondaryAuthUser, logger: this.options.logger, }); if (!installOnly) { - await startTransforms(this.options.esClient, definition, this.options.logger); + await startTransforms( + this.options.scopedClusterClient.asSecondaryAuthUser, + definition, + this.options.logger + ); } return installedDefinition; @@ -53,7 +57,7 @@ export class EntityClient { id, perPage: 1, soClient: this.options.soClient, - esClient: this.options.esClient, + esClient: this.options.scopedClusterClient.asSecondaryAuthUser, }); if (!definition) { @@ -66,7 +70,7 @@ export class EntityClient { definition, deleteData, soClient: this.options.soClient, - esClient: this.options.esClient, + esClient: this.options.scopedClusterClient.asSecondaryAuthUser, logger: this.options.logger, }); } @@ -83,7 +87,7 @@ export class EntityClient { includeState?: boolean; }) { const definitions = await findEntityDefinitions({ - esClient: this.options.esClient, + esClient: this.options.scopedClusterClient.asCurrentUser, soClient: this.options.soClient, page, perPage, @@ -96,9 +100,10 @@ export class EntityClient { async getEntityDefinition({ id }: { id: string }) { const definitions = await findEntityDefinitions({ - esClient: this.options.esClient, + esClient: this.options.scopedClusterClient.asCurrentUser, soClient: this.options.soClient, id, + includeState: true, }); return definitions[0] || null; @@ -111,17 +116,31 @@ export class EntityClient { sortField = '@timestamp', sortDirection = 'asc', }: FindEntitiesQuery) { - return await findEntities(this.options.esClient, perPage, query, searchAfter, { - field: sortField, - direction: sortDirection, - }); + return await findEntities( + this.options.scopedClusterClient.asCurrentUser, + perPage, + query, + searchAfter, + { + field: sortField, + direction: sortDirection, + } + ); } async startEntityDefinition(definition: EntityDefinition) { - return startTransforms(this.options.esClient, definition, this.options.logger); + return startTransforms( + this.options.scopedClusterClient.asSecondaryAuthUser, + definition, + this.options.logger + ); } async stopEntityDefinition(definition: EntityDefinition) { - return stopTransforms(this.options.esClient, definition, this.options.logger); + return stopTransforms( + this.options.scopedClusterClient.asSecondaryAuthUser, + definition, + this.options.logger + ); } } diff --git a/x-pack/plugins/entity_manager/server/plugin.ts b/x-pack/plugins/entity_manager/server/plugin.ts index 2677b78042620..b5f37a4ed3e54 100644 --- a/x-pack/plugins/entity_manager/server/plugin.ts +++ b/x-pack/plugins/entity_manager/server/plugin.ts @@ -99,9 +99,9 @@ export class EntityManagerServerPlugin request: KibanaRequest; coreStart: CoreStart; }) { - const esClient = coreStart.elasticsearch.client.asScoped(request).asSecondaryAuthUser; + const scopedClusterClient = coreStart.elasticsearch.client.asScoped(request); const soClient = coreStart.savedObjects.getScopedClient(request); - return new EntityClient({ esClient, soClient, logger: this.logger }); + return new EntityClient({ scopedClusterClient, soClient, logger: this.logger }); } public start( diff --git a/x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts b/x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts index 78d3b05920973..f107347f711a6 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts @@ -4,12 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -<<<<<<<< HEAD:x-pack/plugins/entity_manager/server/routes/entities/get.ts -import { getEntityDefinitionQuerySchema } from '@kbn/entities-schema'; -======== import { findEntityDefinitionQuerySchema } from '@kbn/entities-schema'; ->>>>>>>> e83ab3a71f9 ([EEM] UI Poc):x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts import { z } from '@kbn/zod'; import { createEntityManagerServerRoute } from '../../create_entity_manager_server_route'; @@ -20,12 +16,6 @@ import { createEntityManagerServerRoute } from '../../create_entity_manager_serv * tags: * - definitions * parameters: - * - in: path - * name: id - * description: The entity definition ID - * schema: - * $ref: '#/components/schemas/deleteEntityDefinitionParamsSchema/properties/id' - * required: false * - in: query * name: page * schema: @@ -34,10 +24,6 @@ import { createEntityManagerServerRoute } from '../../create_entity_manager_serv * name: perPage * schema: * $ref: '#/components/schemas/getEntityDefinitionQuerySchema/properties/perPage' - * - in: query - * name: includeState - * schema: - * $ref: '#/components/schemas/getEntityDefinitionQuerySchema/properties/includeState' * responses: * 200: * description: OK @@ -51,30 +37,25 @@ import { createEntityManagerServerRoute } from '../../create_entity_manager_serv * items: * allOf: * - $ref: '#/components/schemas/entityDefinitionSchema' + * - type: object + * properties: + * state: + * type: object + * properties: + * installed: + * type: boolean + * running: + * type: boolean */ -<<<<<<<< HEAD:x-pack/plugins/entity_manager/server/routes/entities/get.ts -export const getEntityDefinitionRoute = createEntityManagerServerRoute({ - endpoint: 'GET /internal/entities/definition/{id?}', - params: z.object({ - query: getEntityDefinitionQuerySchema, - path: z.object({ id: z.optional(z.string()) }), -======== -export const findEntityDefinitionRoute = createEntityManagerServerRoute({ +export const findEntityDefinitionsRoute = createEntityManagerServerRoute({ endpoint: 'GET /internal/entities/definition', params: z.object({ query: findEntityDefinitionQuerySchema, ->>>>>>>> e83ab3a71f9 ([EEM] UI Poc):x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts }), handler: async ({ request, response, params, logger, getScopedClient }) => { try { const client = await getScopedClient({ request }); - const result = await client.getEntityDefinitions({ - id: params.path?.id, - page: params.query.page, - perPage: params.query.perPage, - includeState: params.query.includeState, - }); - + const result = await client.getEntityDefinitions(params.query); return response.ok({ body: result }); } catch (e) { logger.error(e); diff --git a/x-pack/plugins/entity_manager/server/routes/entities/index.ts b/x-pack/plugins/entity_manager/server/routes/entities/index.ts index fb7dc250344f0..2a7e5bf85b08b 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/index.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/index.ts @@ -7,19 +7,19 @@ import { createEntityDefinitionRoute } from './definition/create'; import { deleteEntityDefinitionRoute } from './definition/delete'; -import { findEntityDefinitionRoute } from './definition/find'; import { resetEntityDefinitionRoute } from './definition/reset'; import { updateEntityDefinitionRoute } from './definition/update'; import { getEntityDefinitionRoute } from './definition/get'; +import { findEntityDefinitionsRoute } from './definition/find'; import { findEntitiesRoute } from './find'; export const entitiesRoutes = { ...createEntityDefinitionRoute, ...deleteEntityDefinitionRoute, - ...findEntityDefinitionRoute, ...resetEntityDefinitionRoute, ...updateEntityDefinitionRoute, ...getEntityDefinitionRoute, + ...findEntityDefinitionsRoute, ...findEntitiesRoute, }; From 178c2ac8186c75f7008a47cd68362d5c03040fab Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Mon, 30 Sep 2024 15:01:56 -0600 Subject: [PATCH 03/12] Initial UI work for the create form. --- .../entity_manager/public/application.tsx | 4 +- .../public/hooks/use_create_data_view.ts | 40 +++++++++ .../public/hooks/use_fetch_indices.ts | 29 ++++++ .../public/hooks/use_index_fields.ts | 27 ++++++ .../create/components/index_field_input.tsx | 20 +++++ .../create/components/index_pattern.tsx | 70 +++++++++++++++ .../pages/entity_manager/create/create.tsx | 89 +++++++++++++++++++ .../components/definition_details.tsx | 10 +-- .../details}/components/entities.tsx | 2 +- .../details}/components/header_details.tsx | 2 +- .../details}/components/pagination.tsx | 0 .../details}/components/rows_per_page.tsx | 0 .../details}/components/transform.tsx | 2 +- .../details}/components/transform_content.tsx | 0 .../components/transform_details_panel.tsx | 0 .../components/transform_health_badge.tsx | 0 .../components/transform_messages.tsx | 2 +- .../components/transform_stats_panel.tsx | 0 .../components/transform_status_badge.tsx | 0 .../details}/components/transforms.tsx | 0 .../details/details.tsx} | 12 +-- .../public/pages/entity_manager/index.ts | 10 +++ .../components/built_in_definition_notice.tsx | 6 +- .../create_entity_definition_btn.tsx | 13 ++- .../components/definition_listing.tsx | 14 +-- .../overview.tsx} | 12 +-- .../plugins/entity_manager/public/routes.tsx | 22 +++-- 27 files changed, 346 insertions(+), 40 deletions(-) create mode 100644 x-pack/plugins/entity_manager/public/hooks/use_create_data_view.ts create mode 100644 x-pack/plugins/entity_manager/public/hooks/use_fetch_indices.ts create mode 100644 x-pack/plugins/entity_manager/public/hooks/use_index_fields.ts create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/index_field_input.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/index_pattern.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/definition_details.tsx (72%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/entities.tsx (92%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/header_details.tsx (92%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/pagination.tsx (100%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/rows_per_page.tsx (100%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/transform.tsx (98%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/transform_content.tsx (100%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/transform_details_panel.tsx (100%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/transform_health_badge.tsx (100%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/transform_messages.tsx (98%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/transform_stats_panel.tsx (100%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/transform_status_badge.tsx (100%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail => entity_manager/details}/components/transforms.tsx (100%) rename x-pack/plugins/entity_manager/public/pages/{entity_manager_detail/entity_manager_detail.tsx => entity_manager/details/details.tsx} (81%) create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager/index.ts rename x-pack/plugins/entity_manager/public/pages/entity_manager/{ => overview}/components/built_in_definition_notice.tsx (88%) rename x-pack/plugins/entity_manager/public/pages/entity_manager/{ => overview}/components/create_entity_definition_btn.tsx (62%) rename x-pack/plugins/entity_manager/public/pages/entity_manager/{ => overview}/components/definition_listing.tsx (86%) rename x-pack/plugins/entity_manager/public/pages/entity_manager/{entity_manager.tsx => overview/overview.tsx} (86%) diff --git a/x-pack/plugins/entity_manager/public/application.tsx b/x-pack/plugins/entity_manager/public/application.tsx index 008ac09128ffe..b3568135f553c 100644 --- a/x-pack/plugins/entity_manager/public/application.tsx +++ b/x-pack/plugins/entity_manager/public/application.tsx @@ -29,9 +29,7 @@ function App() { {Object.keys(routes).map((path) => { const { handler, exact } = routes[path]; - const Wrapper = () => { - return handler(); - }; + const Wrapper = () => handler(); return ; })} diff --git a/x-pack/plugins/entity_manager/public/hooks/use_create_data_view.ts b/x-pack/plugins/entity_manager/public/hooks/use_create_data_view.ts new file mode 100644 index 0000000000000..d630ce24c4751 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/hooks/use_create_data_view.ts @@ -0,0 +1,40 @@ +/* + * 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 { useFetcher } from '@kbn/observability-shared-plugin/public'; +import { useKibana } from './use_kibana'; + +interface UseCreateDataViewProps { + indexPatternString?: string; + dataViewId?: string; +} + +export function useCreateDataView({ indexPatternString, dataViewId }: UseCreateDataViewProps) { + const { dataViews } = useKibana().services; + + const { data: dataView, loading } = useFetcher(async () => { + if (dataViewId) { + try { + return await dataViews.get(dataViewId); + } catch (e) { + return dataViews.create({ + id: `${indexPatternString}-id`, + title: indexPatternString, + allowNoIndex: true, + }); + } + } else if (indexPatternString) { + return dataViews.create({ + id: `${indexPatternString}-id`, + title: indexPatternString, + allowNoIndex: true, + }); + } + }, [dataViewId, dataViews, indexPatternString]); + + return { dataView, loading: Boolean(loading) }; +} diff --git a/x-pack/plugins/entity_manager/public/hooks/use_fetch_indices.ts b/x-pack/plugins/entity_manager/public/hooks/use_fetch_indices.ts new file mode 100644 index 0000000000000..9f01407de4e98 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/hooks/use_fetch_indices.ts @@ -0,0 +1,29 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { useKibana } from './use_kibana'; + +export function useFetchIndices({ indexPatterns }: { indexPatterns: string[] }) { + const { dataViews } = useKibana().services; + + return useQuery({ + queryKey: ['getIndices', ...indexPatterns], + queryFn: async () => { + const searchPattern = indexPatterns.join(','); + const response = await dataViews.getIndices({ + pattern: searchPattern, + isRollupIndex: () => true, + }); + return response; + }, + retry: false, + enabled: Boolean(indexPatterns.join(',')), + refetchOnWindowFocus: false, + keepPreviousData: true, + }); +} diff --git a/x-pack/plugins/entity_manager/public/hooks/use_index_fields.ts b/x-pack/plugins/entity_manager/public/hooks/use_index_fields.ts new file mode 100644 index 0000000000000..5b45d327a8ca2 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/hooks/use_index_fields.ts @@ -0,0 +1,27 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { useKibana } from './use_kibana'; + +export function useIndexFields({ indexPatterns = [] }: { indexPatterns?: string[] }) { + const { dataViews } = useKibana().services; + + return useQuery({ + queryKey: ['indexFields', indexPatterns.join(',')], + queryFn: async () => { + return dataViews.getFieldsForWildcard({ + pattern: indexPatterns.join(','), + allowHidden: true, + allowNoIndex: true, + }); + }, + retry: false, + enabled: Boolean(indexPatterns.join(',')), + keepPreviousData: true, + }); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/index_field_input.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/index_field_input.tsx new file mode 100644 index 0000000000000..42c0738126ae1 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/index_field_input.tsx @@ -0,0 +1,20 @@ +/* + * 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 { useIndexFields } from '../../../../hooks/use_index_fields'; + +interface IndexFieldInputProps { + fieldName: string; + indexPatterns?: string[]; + label: string; + defaultValue: string; + asArray: boolean; +} + +export function IndexFieldInput({ indexPatterns }: IndexFieldInputProps) { + const { data: fields } = useIndexFields({ indexPatterns }); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/index_pattern.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/index_pattern.tsx new file mode 100644 index 0000000000000..610d3d2b6c8b7 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/index_pattern.tsx @@ -0,0 +1,70 @@ +/* + * 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, { useState } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import { EntityDefinition } from '@kbn/entities-schema'; +import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useFetchIndices } from '../../../../hooks/use_fetch_indices'; + +export function IndexPatternInput() { + const { control, watch } = useFormContext(); + const [options, setOptions] = useState([]); + const indexPatterns = watch('indexPatterns'); + const { data } = useFetchIndices({ indexPatterns }); + const indexCount = data != null ? data.length : 0; + + const helpText = indexPatterns.length + ? i18n.translate('xpack.entityManager.indexPatternInput.helpText', { + defaultMessage: + 'Matches {indexCount, number} {indexCount, plural, one {index} other {indices}}.', + values: { indexCount }, + }) + : null; + + return ( + ( + + { + if (selected.length) { + return field.onChange(selected.map((selection) => selection.value)); + } + field.onChange([]); + }} + onCreateOption={(newValue: string, existingOptions: EuiComboBoxOptionOption[]) => { + const normalizedValue = newValue.trim(); + const alreadyExists = field.value.some((v) => v === normalizedValue); + const optionAlreadyExists = options.some((v) => v === normalizedValue); + if (!normalizedValue || (alreadyExists && optionAlreadyExists)) { + return; + } + if (!alreadyExists) { + field.onChange([...field.value, normalizedValue]); + } + if (!optionAlreadyExists) { + setOptions((previous) => [...previous, normalizedValue]); + } + }} + options={options.map((value) => ({ value, label: value }))} + selectedOptions={field.value.map((value) => ({ value, label: value }))} + /> + + )} + /> + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx new file mode 100644 index 0000000000000..961edabb14335 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx @@ -0,0 +1,89 @@ +/* + * 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 { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { Controller, FormProvider, SubmitHandler, useForm } from 'react-hook-form'; +import { EntityDefinition } from '@kbn/entities-schema'; +import { EuiButton, EuiFieldText, EuiForm, EuiFormRow } from '@elastic/eui'; +import { usePluginContext } from '../../../hooks/use_plugin_context'; +import { useKibana } from '../../../hooks/use_kibana'; +import { paths } from '../../../../common/locators/paths'; +import { ENTITY_MANAGER_LABEL } from '../../../../common/translations'; +import { IndexPatternInput } from './components/index_pattern'; + +const PAGE_TITLE = i18n.translate('xpack.entityManager.createPage.title', { + defaultMessage: 'Create new definition', +}); + +const DEFAULT_VALUES = { + id: '', + name: '', + indexPatterns: [], +}; + +export function EntityManagerCreatePage() { + const { + http: { basePath }, + } = useKibana().services; + + useBreadcrumbs([ + { + href: basePath.prepend(paths.entities), + text: ENTITY_MANAGER_LABEL, + deepLinkId: 'entityManager', + }, + { + text: PAGE_TITLE, + }, + ]); + + const { ObservabilityPageTemplate } = usePluginContext(); + + const methods = useForm({ + defaultValues: DEFAULT_VALUES, + mode: 'all', + }); + const { control, handleSubmit } = methods; + + const onSubmit: SubmitHandler = (data) => { + console.log(data); + }; + + return ( + + + + ( + + + + )} + /> + ( + + + + )} + /> + + Submit + + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/definition_details.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/definition_details.tsx similarity index 72% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/definition_details.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/definition_details.tsx index a5cdbb24a0a36..8ded1cbaf3c09 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/definition_details.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/definition_details.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { EntityDefinitionWithState } from '@kbn/entities-schema'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { LastSeenStat } from '../../../components/stats/last_seen'; -import { EntityCountStat } from '../../../components/stats/entity_count'; -import { HistoryCountStat } from '../../../components/stats/history_count'; -import { HistoryCheckpointDurationStat } from '../../../components/stats/history_checkpoint_duration'; -import { LatestCheckpointDurationStat } from '../../../components/stats/latest_checkpoint_duration'; +import { LastSeenStat } from '../../../../components/stats/last_seen'; +import { EntityCountStat } from '../../../../components/stats/entity_count'; +import { HistoryCountStat } from '../../../../components/stats/history_count'; +import { HistoryCheckpointDurationStat } from '../../../../components/stats/history_checkpoint_duration'; +import { LatestCheckpointDurationStat } from '../../../../components/stats/latest_checkpoint_duration'; interface DefinitionDetailsProps { definition: EntityDefinitionWithState; diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/entities.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/entities.tsx similarity index 92% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/entities.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/entities.tsx index 4bd3598b81b06..b5a67b2fe16d7 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/entities.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/entities.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EntityDefinitionWithState } from '@kbn/entities-schema'; import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { EntitiesListing } from '../../../components/entities_listing'; +import { EntitiesListing } from '../../../../components/entities_listing'; interface EntitiesListingProps { definition?: EntityDefinitionWithState; diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/header_details.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/header_details.tsx similarity index 92% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/header_details.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/header_details.tsx index 29636f53cbae8..4b54e40d92320 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/header_details.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/header_details.tsx @@ -7,7 +7,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; import { EntityDefinitionWithState } from '@kbn/entities-schema'; -import { Badges } from '../../../components/badges'; +import { Badges } from '../../../../components/badges'; interface Props { definition: EntityDefinitionWithState; } diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/pagination.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/pagination.tsx similarity index 100% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/pagination.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/pagination.tsx diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/rows_per_page.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/rows_per_page.tsx similarity index 100% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/rows_per_page.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/rows_per_page.tsx diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform.tsx similarity index 98% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform.tsx index fe9842d7bd2e0..fa49307f78dd4 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform.tsx @@ -16,7 +16,7 @@ import { TransformContent } from './transform_content'; import { generateHistoryTransformId, generateLatestTransformId, -} from '../../../../common/helpers/generate_component_id'; +} from '../../../../../common/helpers/generate_component_id'; interface TransformProps { type: 'history' | 'latest'; diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_content.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_content.tsx similarity index 100% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_content.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_content.tsx diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_details_panel.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_details_panel.tsx similarity index 100% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_details_panel.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_details_panel.tsx diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_health_badge.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_health_badge.tsx similarity index 100% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_health_badge.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_health_badge.tsx diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_messages.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_messages.tsx similarity index 98% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_messages.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_messages.tsx index a7977d895ffcf..0138b32120c1d 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_messages.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_messages.tsx @@ -22,7 +22,7 @@ import moment from 'moment'; import { TransformMessage, useFetchTransformMessages, -} from '../../../hooks/use_fetch_transform_messages'; +} from '../../../../hooks/use_fetch_transform_messages'; interface TransformMessagesProps { transform: TransformGetTransformTransformSummary; diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_stats_panel.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_stats_panel.tsx similarity index 100% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_stats_panel.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_stats_panel.tsx diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_status_badge.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_status_badge.tsx similarity index 100% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transform_status_badge.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_status_badge.tsx diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transforms.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transforms.tsx similarity index 100% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/components/transforms.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transforms.tsx diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/entity_manager_detail.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/details.tsx similarity index 81% rename from x-pack/plugins/entity_manager/public/pages/entity_manager_detail/entity_manager_detail.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/details/details.tsx index 2a413e6e0c40c..42b808bffd995 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager_detail/entity_manager_detail.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/details.tsx @@ -8,17 +8,17 @@ import React from 'react'; import { useParams } from 'react-router-dom'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; import { EuiSpacer } from '@elastic/eui'; -import { usePluginContext } from '../../hooks/use_plugin_context'; -import { useKibana } from '../../hooks/use_kibana'; -import { paths } from '../../../common/locators/paths'; -import { ENTITY_MANAGER_LABEL } from '../../../common/translations'; -import { useFetchEntityDefinition } from '../../hooks/use_fetch_entity_definition'; +import { usePluginContext } from '../../../hooks/use_plugin_context'; +import { useKibana } from '../../../hooks/use_kibana'; +import { paths } from '../../../../common/locators/paths'; +import { ENTITY_MANAGER_LABEL } from '../../../../common/translations'; +import { useFetchEntityDefinition } from '../../../hooks/use_fetch_entity_definition'; import { HeaderDetails } from './components/header_details'; import { Transforms } from './components/transforms'; import { DefinitionDetails } from './components/definition_details'; import { Entities } from './components/entities'; -export function EntityManagerDetailPage() { +export function EntityManagerDetailsPage() { const { http: { basePath }, } = useKibana().services; diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/index.ts b/x-pack/plugins/entity_manager/public/pages/entity_manager/index.ts new file mode 100644 index 0000000000000..9064ec9b31a99 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/index.ts @@ -0,0 +1,10 @@ +/* + * 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 { EntityManagerOverviewPage } from './overview/overview'; +export { EntityManagerDetailsPage } from './details/details'; +export { EntityManagerCreatePage } from './create/create'; diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/components/built_in_definition_notice.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/built_in_definition_notice.tsx similarity index 88% rename from x-pack/plugins/entity_manager/public/pages/entity_manager/components/built_in_definition_notice.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/built_in_definition_notice.tsx index 11a35e6afd546..00013cd70822a 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/components/built_in_definition_notice.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/built_in_definition_notice.tsx @@ -7,9 +7,9 @@ import React from 'react'; import { EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useFetchEnablementStatus } from '../../../hooks/use_fetch_enablement_status'; -import { ERROR_API_KEY_NOT_FOUND } from '../../../../common/errors'; -import { useEnableEnablement } from '../../../hooks/use_enable_enablement'; +import { useFetchEnablementStatus } from '../../../../hooks/use_fetch_enablement_status'; +import { ERROR_API_KEY_NOT_FOUND } from '../../../../../common/errors'; +import { useEnableEnablement } from '../../../../hooks/use_enable_enablement'; export function BuiltInDefinitionNotice() { const { isLoading, data } = useFetchEnablementStatus(); const { isLoading: isEnablementLoading, mutate } = useEnableEnablement(); diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/components/create_entity_definition_btn.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/create_entity_definition_btn.tsx similarity index 62% rename from x-pack/plugins/entity_manager/public/pages/entity_manager/components/create_entity_definition_btn.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/create_entity_definition_btn.tsx index 0f4e54f937a9b..9f48316e0f9c7 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/components/create_entity_definition_btn.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/create_entity_definition_btn.tsx @@ -8,10 +8,21 @@ import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { useKibana } from '../../../../hooks/use_kibana'; +import { paths } from '../../../../../common/locators/paths'; export function CreateEntityDefinitionBtn() { + const { + http: { basePath }, + } = useKibana().services; + return ( - + {i18n.translate('xpack.entityManager.createEntityDefinitionBtn.label', { defaultMessage: 'Create Definition', })} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/components/definition_listing.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/definition_listing.tsx similarity index 86% rename from x-pack/plugins/entity_manager/public/pages/entity_manager/components/definition_listing.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/definition_listing.tsx index 60b5874b81042..958b497d5e851 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/components/definition_listing.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/definition_listing.tsx @@ -10,13 +10,13 @@ import { EntityDefinitionWithState, EntityDefintionResponse } from '@kbn/entitie import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import numeral from '@elastic/numeral'; -import { Badges } from '../../../components/badges'; -import { useKibana } from '../../../hooks/use_kibana'; -import { paths } from '../../../../common/locators/paths'; -import { LastSeenStat } from '../../../components/stats/last_seen'; -import { EntityCountStat } from '../../../components/stats/entity_count'; -import { HistoryCountStat } from '../../../components/stats/history_count'; -import { useFetchEntityDefinitions } from '../../../hooks/use_fetch_entity_definitions'; +import { Badges } from '../../../../components/badges'; +import { useKibana } from '../../../../hooks/use_kibana'; +import { paths } from '../../../../../common/locators/paths'; +import { LastSeenStat } from '../../../../components/stats/last_seen'; +import { EntityCountStat } from '../../../../components/stats/entity_count'; +import { HistoryCountStat } from '../../../../components/stats/history_count'; +import { useFetchEntityDefinitions } from '../../../../hooks/use_fetch_entity_definitions'; interface ListingProps { definition: EntityDefinitionWithState; diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/entity_manager.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/overview.tsx similarity index 86% rename from x-pack/plugins/entity_manager/public/pages/entity_manager/entity_manager.tsx rename to x-pack/plugins/entity_manager/public/pages/entity_manager/overview/overview.tsx index 757cdc38c636e..b5250a8d3da4a 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/entity_manager.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/overview.tsx @@ -8,16 +8,16 @@ import React, { useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; -import { paths } from '../../../common/locators/paths'; -import { usePluginContext } from '../../hooks/use_plugin_context'; -import { useKibana } from '../../hooks/use_kibana'; -import { ENTITY_MANAGER_LABEL } from '../../../common/translations'; +import { paths } from '../../../../common/locators/paths'; +import { usePluginContext } from '../../../hooks/use_plugin_context'; +import { useKibana } from '../../../hooks/use_kibana'; +import { ENTITY_MANAGER_LABEL } from '../../../../common/translations'; import { CreateEntityDefinitionBtn } from './components/create_entity_definition_btn'; import { BuiltInDefinitionNotice } from './components/built_in_definition_notice'; import { DefinitionListing } from './components/definition_listing'; -import { EntitiesListing } from '../../components/entities_listing'; +import { EntitiesListing } from '../../../components/entities_listing'; -export function EntityManagerPage() { +export function EntityManagerOverviewPage() { const { http: { basePath }, } = useKibana().services; diff --git a/x-pack/plugins/entity_manager/public/routes.tsx b/x-pack/plugins/entity_manager/public/routes.tsx index ac826024ec160..0bf229c3655fe 100644 --- a/x-pack/plugins/entity_manager/public/routes.tsx +++ b/x-pack/plugins/entity_manager/public/routes.tsx @@ -6,9 +6,16 @@ */ import React from 'react'; -import { ENTITY_MANAGER_DETAIL, ENTITY_MANAGER_OVERVIEW } from '../common/locators/paths'; -import { EntityManagerPage } from './pages/entity_manager/entity_manager'; -import { EntityManagerDetailPage } from './pages/entity_manager_detail/entity_manager_detail'; +import { + ENTITY_MANAGER_CREATE, + ENTITY_MANAGER_DETAIL, + ENTITY_MANAGER_OVERVIEW, +} from '../common/locators/paths'; +import { + EntityManagerCreatePage, + EntityManagerDetailsPage, + EntityManagerOverviewPage, +} from './pages/entity_manager'; interface RouteDef { [key: string]: { @@ -21,12 +28,17 @@ interface RouteDef { export function getRoutes(): RouteDef { return { [ENTITY_MANAGER_OVERVIEW]: { - handler: () => , + handler: () => , + params: {}, + exact: true, + }, + [ENTITY_MANAGER_CREATE]: { + handler: () => , params: {}, exact: true, }, [ENTITY_MANAGER_DETAIL]: { - handler: () => , + handler: () => , params: {}, exact: true, }, From 777a2f7788527b46e44f04905ceba5c8ade08cf6 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:18:26 +0000 Subject: [PATCH 04/12] [CI] Auto-commit changed files from 'node scripts/notice' --- .../kbn-entities-schema/tsconfig.json | 1 + x-pack/plugins/entity_manager/tsconfig.json | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/x-pack/packages/kbn-entities-schema/tsconfig.json b/x-pack/packages/kbn-entities-schema/tsconfig.json index 0fdbba4b7e793..63961802350bb 100644 --- a/x-pack/packages/kbn-entities-schema/tsconfig.json +++ b/x-pack/packages/kbn-entities-schema/tsconfig.json @@ -17,5 +17,6 @@ "kbn_references": [ "@kbn/zod", "@kbn/zod-helpers", + "@kbn/rison", ] } diff --git a/x-pack/plugins/entity_manager/tsconfig.json b/x-pack/plugins/entity_manager/tsconfig.json index 4a12efee849bc..452b493172db0 100644 --- a/x-pack/plugins/entity_manager/tsconfig.json +++ b/x-pack/plugins/entity_manager/tsconfig.json @@ -27,7 +27,6 @@ "@kbn/entities-schema", "@kbn/es-query", "@kbn/i18n", - "@kbn/i18n-react", "@kbn/licensing-plugin", "@kbn/logging", "@kbn/logging-mocks", @@ -39,6 +38,24 @@ "@kbn/shared-ux-router", "@kbn/zod", "@kbn/zod-helpers", - "@kbn/core-saved-objects-server" + "@kbn/core-saved-objects-server", + "@kbn/ebt-tools", + "@kbn/kibana-react-plugin", + "@kbn/kibana-utils-plugin", + "@kbn/react-kibana-context-render", + "@kbn/react-kibana-context-theme", + "@kbn/shared-ux-link-redirect-app", + "@kbn/usage-collection-plugin", + "@kbn/core-http-browser", + "@kbn/charts-plugin", + "@kbn/serverless", + "@kbn/data-views-plugin", + "@kbn/data-plugin", + "@kbn/cloud-plugin", + "@kbn/lens-plugin", + "@kbn/data-view-editor-plugin", + "@kbn/data-view-field-editor-plugin", + "@kbn/presentation-util-plugin", + "@kbn/rison" ] } From 99a32ebffbb0be57ff79c53981b2acbd3c43b545 Mon Sep 17 00:00:00 2001 From: klacabane Date: Thu, 10 Oct 2024 17:55:12 +0200 Subject: [PATCH 05/12] remove history related logic --- .../public/components/entities_listing.tsx | 16 ------- .../public/components/stats/entity_count.tsx | 2 +- .../stats/history_checkpoint_duration.tsx | 42 ------------------- .../public/components/stats/history_count.tsx | 27 ------------ .../public/components/stats/last_seen.tsx | 5 ++- .../hooks/use_fetch_entity_definition.ts | 1 + .../details/components/definition_details.tsx | 8 ---- .../details/components/transform.tsx | 5 ++- .../details/components/transforms.tsx | 3 -- .../components/definition_listing.tsx | 4 -- .../entities/get_entity_definition_stats.ts | 29 +++++++------ .../server/lib/entity_client.ts | 10 ++--- .../server/routes/entities/definition/find.ts | 19 +++++---- .../server/routes/entities/definition/get.ts | 40 +++++++----------- 14 files changed, 54 insertions(+), 157 deletions(-) delete mode 100644 x-pack/plugins/entity_manager/public/components/stats/history_checkpoint_duration.tsx delete mode 100644 x-pack/plugins/entity_manager/public/components/stats/history_count.tsx diff --git a/x-pack/plugins/entity_manager/public/components/entities_listing.tsx b/x-pack/plugins/entity_manager/public/components/entities_listing.tsx index 00adc5608ebe9..3af26e0b13211 100644 --- a/x-pack/plugins/entity_manager/public/components/entities_listing.tsx +++ b/x-pack/plugins/entity_manager/public/components/entities_listing.tsx @@ -58,15 +58,6 @@ export function EntitiesListing({ definition, defaultPerPage = 10 }: EntitiesLis render: (type: string) => {type}, sortable: true, }, - { - field: 'entity.firstSeenTimestamp', - name: i18n.translate('xpack.entityManager.entitiesListing.firstSeenLabel', { - defaultMessage: 'First seen', - }), - render: (value: string) => moment(value).format('ll LT'), - truncateText: true, - sortable: true, - }, { field: 'entity.lastSeenTimestamp', name: i18n.translate('xpack.entityManager.entitiesListing.lastSeenLabel', { @@ -89,13 +80,6 @@ export function EntitiesListing({ definition, defaultPerPage = 10 }: EntitiesLis }); } }); - } else { - columns.push({ - field: 'entity.metrics.logRate', - name: 'Log rate', - render: (value: number) => (value != null ? numeral(value).format('0,0[.0]') : '--'), - sortable: true, - }); } const sorting: EuiTableSortingType = { diff --git a/x-pack/plugins/entity_manager/public/components/stats/entity_count.tsx b/x-pack/plugins/entity_manager/public/components/stats/entity_count.tsx index ea5c040d708b0..996802cf9cfc5 100644 --- a/x-pack/plugins/entity_manager/public/components/stats/entity_count.tsx +++ b/x-pack/plugins/entity_manager/public/components/stats/entity_count.tsx @@ -16,7 +16,7 @@ export function EntityCountStat({ definition, titleSize, textAlign }: Definition 100 ? '0.0a' : '0,0' + definition.stats.entityCount > 1000 ? '0.0a' : '0,0' )} textAlign={textAlign} description={i18n.translate('xpack.entityManager.defintionStat.entityCount.label', { diff --git a/x-pack/plugins/entity_manager/public/components/stats/history_checkpoint_duration.tsx b/x-pack/plugins/entity_manager/public/components/stats/history_checkpoint_duration.tsx deleted file mode 100644 index 3d2318f6a15a2..0000000000000 --- a/x-pack/plugins/entity_manager/public/components/stats/history_checkpoint_duration.tsx +++ /dev/null @@ -1,42 +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 React from 'react'; -import { i18n } from '@kbn/i18n'; -import numeral from '@elastic/numeral'; -import { EuiStat } from '@elastic/eui'; -import { DefinitionStatProps } from './types'; -import { generateHistoryTransformId } from '../../../common/helpers/generate_component_id'; - -export function HistoryCheckpointDurationStat({ - definition, - textAlign, - titleSize, -}: DefinitionStatProps) { - const transformState = definition.resources.transforms.find( - (doc) => doc.id === generateHistoryTransformId(definition) - ); - const value = - transformState != null - ? numeral(transformState.stats.stats.exponential_avg_checkpoint_duration_ms).format('0,0') + - 'ms' - : 'N/A'; - - return ( - - ); -} diff --git a/x-pack/plugins/entity_manager/public/components/stats/history_count.tsx b/x-pack/plugins/entity_manager/public/components/stats/history_count.tsx deleted file mode 100644 index e79e2fda1b8ac..0000000000000 --- a/x-pack/plugins/entity_manager/public/components/stats/history_count.tsx +++ /dev/null @@ -1,27 +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 React from 'react'; -import { EuiStat } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import numeral from '@elastic/numeral'; -import { DefinitionStatProps } from './types'; - -export function HistoryCountStat({ definition, titleSize, textAlign }: DefinitionStatProps) { - return ( - 1000 ? '0.0a' : '0' - )} - textAlign={textAlign} - description={i18n.translate('xpack.entityManager.listing.historyCount.label', { - defaultMessage: 'History count', - })} - /> - ); -} diff --git a/x-pack/plugins/entity_manager/public/components/stats/last_seen.tsx b/x-pack/plugins/entity_manager/public/components/stats/last_seen.tsx index 4d5c7317982da..7b102d7a05001 100644 --- a/x-pack/plugins/entity_manager/public/components/stats/last_seen.tsx +++ b/x-pack/plugins/entity_manager/public/components/stats/last_seen.tsx @@ -12,10 +12,13 @@ import { i18n } from '@kbn/i18n'; import { DefinitionStatProps } from './types'; export function LastSeenStat({ definition, titleSize, textAlign }: DefinitionStatProps) { + const date = definition.stats.lastSeenTimestamp + ? moment(definition.stats.lastSeenTimestamp).fromNow() + : '-'; return ( ( `/internal/entities/definition/${id}`, { + query: { includeState: true }, signal, } ); diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/definition_details.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/definition_details.tsx index 8ded1cbaf3c09..a6aec026498d6 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/definition_details.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/definition_details.tsx @@ -9,8 +9,6 @@ import { EntityDefinitionWithState } from '@kbn/entities-schema'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { LastSeenStat } from '../../../../components/stats/last_seen'; import { EntityCountStat } from '../../../../components/stats/entity_count'; -import { HistoryCountStat } from '../../../../components/stats/history_count'; -import { HistoryCheckpointDurationStat } from '../../../../components/stats/history_checkpoint_duration'; import { LatestCheckpointDurationStat } from '../../../../components/stats/latest_checkpoint_duration'; interface DefinitionDetailsProps { @@ -26,12 +24,6 @@ export function DefinitionDetails({ definition }: DefinitionDetailsProps) { - - - - - - diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform.tsx index fa49307f78dd4..ee586861e4220 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform.tsx @@ -66,7 +66,10 @@ export function Transform({ definition, type }: TransformProps) { 0 + ? numeral(stats.stats.index_failures / stats.stats.index_total).format('0,0[.0]') + + '%' + : 0 + '%' } textAlign="right" description={i18n.translate('xpack.entityManager.transform.indexFailureLabel', { diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transforms.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transforms.tsx index 939d8ebe5cd8f..768ef3286fdb7 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transforms.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transforms.tsx @@ -27,9 +27,6 @@ export function Transforms({ definition }: TransformsProps) { - - - diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/definition_listing.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/definition_listing.tsx index 958b497d5e851..89b0a27c6dfda 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/definition_listing.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/definition_listing.tsx @@ -15,7 +15,6 @@ import { useKibana } from '../../../../hooks/use_kibana'; import { paths } from '../../../../../common/locators/paths'; import { LastSeenStat } from '../../../../components/stats/last_seen'; import { EntityCountStat } from '../../../../components/stats/entity_count'; -import { HistoryCountStat } from '../../../../components/stats/history_count'; import { useFetchEntityDefinitions } from '../../../../hooks/use_fetch_entity_definitions'; interface ListingProps { @@ -62,9 +61,6 @@ function Listing({ definition }: ListingProps) { - - - (params); + const response = await esClient.search<{ entity: { lastSeenTimestamp: string } }>(params); const total = response.hits.total as SearchTotalHits; - return { - totalDocs: total.value, - lastSeenTimestamp: - total.value && response.hits.hits[0]._source - ? response.hits.hits[0]._source['@timestamp'] - : null, - }; + return total.value && response.hits.hits[0]._source + ? response.hits.hits[0]._source.entity.lastSeenTimestamp + : null; } export async function getEntityCount(esClient: ElasticsearchClient, definition: EntityDefinition) { diff --git a/x-pack/plugins/entity_manager/server/lib/entity_client.ts b/x-pack/plugins/entity_manager/server/lib/entity_client.ts index 21596b0009116..17ddf5c44f202 100644 --- a/x-pack/plugins/entity_manager/server/lib/entity_client.ts +++ b/x-pack/plugins/entity_manager/server/lib/entity_client.ts @@ -11,7 +11,7 @@ import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { Logger } from '@kbn/logging'; import { installEntityDefinition } from './entities/install_entity_definition'; import { startTransforms } from './entities/start_transforms'; -import { findEntityDefinitions } from './entities/find_entity_definition'; +import { findEntityDefinitionById, findEntityDefinitions } from './entities/find_entity_definition'; import { uninstallEntityDefinition } from './entities/uninstall_entity_definition'; import { EntityDefinitionNotFound } from './entities/errors/entity_not_found'; @@ -104,15 +104,15 @@ export class EntityClient { return { definitions }; } - async getEntityDefinition({ id }: { id: string }) { - const definitions = await findEntityDefinitions({ + async getEntityDefinition({ id, includeState = false }: { id: string; includeState?: boolean }) { + const definition = await findEntityDefinitionById({ esClient: this.options.scopedClusterClient.asCurrentUser, soClient: this.options.soClient, id, - includeState: true, + includeState, }); - return definitions[0] || null; + return definition; } async findEntities({ diff --git a/x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts b/x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts index f107347f711a6..62a55adde4c2a 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/definition/find.ts @@ -16,6 +16,12 @@ import { createEntityManagerServerRoute } from '../../create_entity_manager_serv * tags: * - definitions * parameters: + * - in: path + * name: id + * description: The entity definition ID + * schema: + * $ref: '#/components/schemas/deleteEntityDefinitionParamsSchema/properties/id' + * required: false * - in: query * name: page * schema: @@ -24,6 +30,10 @@ import { createEntityManagerServerRoute } from '../../create_entity_manager_serv * name: perPage * schema: * $ref: '#/components/schemas/getEntityDefinitionQuerySchema/properties/perPage' + * - in: query + * name: includeState + * schema: + * $ref: '#/components/schemas/getEntityDefinitionQuerySchema/properties/includeState' * responses: * 200: * description: OK @@ -37,15 +47,6 @@ import { createEntityManagerServerRoute } from '../../create_entity_manager_serv * items: * allOf: * - $ref: '#/components/schemas/entityDefinitionSchema' - * - type: object - * properties: - * state: - * type: object - * properties: - * installed: - * type: boolean - * running: - * type: boolean */ export const findEntityDefinitionsRoute = createEntityManagerServerRoute({ endpoint: 'GET /internal/entities/definition', diff --git a/x-pack/plugins/entity_manager/server/routes/entities/definition/get.ts b/x-pack/plugins/entity_manager/server/routes/entities/definition/get.ts index 08b902ba74425..407bd2f915d37 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/definition/get.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/definition/get.ts @@ -7,61 +7,51 @@ import { getEntityDefinitionParamsSchema } from '@kbn/entities-schema'; import { z } from '@kbn/zod'; +import { BooleanFromString } from '@kbn/zod-helpers'; import { createEntityManagerServerRoute } from '../../create_entity_manager_server_route'; /** @openapi - * /internal/entities/definition: + * /internal/entities/definition/{id}: * get: - * description: Get all installed entity definitions. + * description: Get an entity definition. * tags: * - definitions * parameters: - * - in: query - * name: page + * - in: path + * name: id * schema: - * $ref: '#/components/schemas/getEntityDefinitionQuerySchema/properties/page' + * $ref: '#/components/schemas/getEntityDefinitionParamsSchema/properties/id' * - in: query - * name: perPage + * name: includeState * schema: - * $ref: '#/components/schemas/getEntityDefinitionQuerySchema/properties/perPage' + * type: boolean * responses: * 200: * description: OK * content: * application/json: * schema: - * type: object - * properties: - * definitions: - * type: array - * items: - * allOf: - * - $ref: '#/components/schemas/entityDefinitionSchema' - * - type: object - * properties: - * state: - * type: object - * properties: - * installed: - * type: boolean - * running: - * type: boolean + * $ref: '#/components/schemas/entityDefinitionSchema' * 404: - * description: Not found + * description: Entity definition does not exist */ export const getEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'GET /internal/entities/definition/{id}', params: z.object({ path: getEntityDefinitionParamsSchema, + query: z.object({ + includeState: z.optional(BooleanFromString).default(false), + }), }), handler: async ({ request, response, params, logger, getScopedClient }) => { try { const client = await getScopedClient({ request }); const result = await client.getEntityDefinition({ id: params.path.id, + includeState: params.query.includeState, }); - if (result === null) { + if (!result) { return response.notFound(); } From 251e339d5844c6192b86f537ab040167457e5af6 Mon Sep 17 00:00:00 2001 From: klacabane Date: Thu, 10 Oct 2024 18:01:59 +0200 Subject: [PATCH 06/12] hide app from nav --- x-pack/plugins/entity_manager/public/plugin.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/entity_manager/public/plugin.ts b/x-pack/plugins/entity_manager/public/plugin.ts index 644a4af6f9e4e..78d8f6a652c07 100644 --- a/x-pack/plugins/entity_manager/public/plugin.ts +++ b/x-pack/plugins/entity_manager/public/plugin.ts @@ -8,6 +8,7 @@ import { App, AppMountParameters, + AppStatus, AppUpdater, CoreSetup, CoreStart, @@ -70,9 +71,9 @@ export class Plugin implements EntityManagerPluginClass { mount, visibleIn: ['sideNav'], keywords: ['observability', 'monitor', 'entities'], + status: AppStatus.inaccessible, }; - // Register an application into the side navigation menu core.application.register(app); const entityClient = new EntityClient(core); From b9ff82d1c43b3536d280abe78249252cd5a86a3a Mon Sep 17 00:00:00 2001 From: klacabane Date: Mon, 14 Oct 2024 13:17:56 +0200 Subject: [PATCH 07/12] remove entity manager link --- x-pack/plugins/entity_manager/public/plugin.ts | 2 +- .../observability/public/plugin.ts | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/x-pack/plugins/entity_manager/public/plugin.ts b/x-pack/plugins/entity_manager/public/plugin.ts index 78d8f6a652c07..6d8c242e09c9a 100644 --- a/x-pack/plugins/entity_manager/public/plugin.ts +++ b/x-pack/plugins/entity_manager/public/plugin.ts @@ -69,7 +69,7 @@ export class Plugin implements EntityManagerPluginClass { appRoute: '/app/entity_manager', category: DEFAULT_APP_CATEGORIES.observability, mount, - visibleIn: ['sideNav'], + visibleIn: [], keywords: ['observability', 'monitor', 'entities'], status: AppStatus.inaccessible, }; diff --git a/x-pack/plugins/observability_solution/observability/public/plugin.ts b/x-pack/plugins/observability_solution/observability/public/plugin.ts index 4222c58b5d22b..5866a082556bb 100644 --- a/x-pack/plugins/observability_solution/observability/public/plugin.ts +++ b/x-pack/plugins/observability_solution/observability/public/plugin.ts @@ -387,17 +387,6 @@ export class Plugin ] : []; - const entityManagerLink: NavigationEntry[] = [ - { - label: i18n.translate('xpack.observability.entityManagerLinkTitle', { - defaultMessage: 'Entity Manager', - }), - app: 'entity_manager', - isTechnicalPreview: true, - path: '/', - }, - ]; - // Reformat the visible links to be NavigationEntry objects instead of // AppDeepLink objects. // @@ -435,7 +424,6 @@ export class Plugin ...sloLink, ...casesLink, ...aiAssistantLink, - ...entityManagerLink, ], }, ]; From b4a085ae93d02de8841fbd8270bcfa5dc0552ecc Mon Sep 17 00:00:00 2001 From: klacabane Date: Wed, 16 Oct 2024 12:54:56 +0200 Subject: [PATCH 08/12] add id fields and metadata to form --- .../create/components/identity_fields.tsx | 73 +++++++++++++++ .../create/components/metadata.tsx | 88 +++++++++++++++++++ .../pages/entity_manager/create/create.tsx | 28 +++++- .../components/transform_details_panel.tsx | 2 +- 4 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/identity_fields.tsx create mode 100644 x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/metadata.tsx diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/identity_fields.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/identity_fields.tsx new file mode 100644 index 0000000000000..167ee4b541b79 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/identity_fields.tsx @@ -0,0 +1,73 @@ +/* + * 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 { Control, Controller, useFieldArray } from 'react-hook-form'; +import { EntityDefinition } from '@kbn/entities-schema'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFieldText, + EuiButtonIcon, + EuiFormRow, + EuiSpacer, +} from '@elastic/eui'; + +export function IdentityFieldsInput({ control }: { control: Control }) { + const identityFormFields = useFieldArray({ control, name: 'identityFields' }); + + return ( + identityFormFields.append({ field: '', optional: false })} + /> + } + > + <> + {identityFormFields.fields.map((item, index) => { + return ( + ( + <> + + + { + e.preventDefault(); + identityFormFields.update(index, { ...item, field: e.target.value }); + }} + fullWidth + /> + + + {identityFormFields.fields.length > 1 ? ( + identityFormFields.remove(index)} + /> + ) : null} + + + + + + )} + /> + ); + })} + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/metadata.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/metadata.tsx new file mode 100644 index 0000000000000..dcd1b2b9fda76 --- /dev/null +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/metadata.tsx @@ -0,0 +1,88 @@ +/* + * 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 { Control, Controller, useFieldArray } from 'react-hook-form'; +import { EntityDefinition } from '@kbn/entities-schema'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFieldText, + EuiButtonIcon, + EuiFormRow, + EuiSpacer, +} from '@elastic/eui'; + +export function MetadataFieldsInput({ control }: { control: Control }) { + const metadataFields = useFieldArray({ control, name: 'metadata' }); + + return ( + + metadataFields.append({ + source: '', + destination: '', + aggregation: { type: 'terms', limit: 10 }, + }) + } + /> + } + > + <> + {metadataFields.fields.map((item, index) => { + return ( + ( + <> + + + + metadataFields.update(index, { ...item, source: e.target.value }) + } + fullWidth + /> + + + + metadataFields.update(index, { ...item, destination: e.target.value }) + } + fullWidth + /> + + + metadataFields.remove(index)} + /> + + + + + + )} + /> + ); + })} + + + ); +} diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx index 961edabb14335..26041d727d8f9 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx @@ -15,6 +15,8 @@ import { useKibana } from '../../../hooks/use_kibana'; import { paths } from '../../../../common/locators/paths'; import { ENTITY_MANAGER_LABEL } from '../../../../common/translations'; import { IndexPatternInput } from './components/index_pattern'; +import { IdentityFieldsInput } from './components/identity_fields'; +import { MetadataFieldsInput } from './components/metadata'; const PAGE_TITLE = i18n.translate('xpack.entityManager.createPage.title', { defaultMessage: 'Create new definition', @@ -23,7 +25,10 @@ const PAGE_TITLE = i18n.translate('xpack.entityManager.createPage.title', { const DEFAULT_VALUES = { id: '', name: '', + type: '', indexPatterns: [], + identityFields: [{ field: '', optional: false }], + metadata: [], }; export function EntityManagerCreatePage() { @@ -50,9 +55,7 @@ export function EntityManagerCreatePage() { }); const { control, handleSubmit } = methods; - const onSubmit: SubmitHandler = (data) => { - console.log(data); - }; + const onSubmit: SubmitHandler = (data) => {}; return ( @@ -69,6 +72,19 @@ export function EntityManagerCreatePage() { )} /> + + ( + + + + )} + /> + )} /> + + + + + + Submit diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_details_panel.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_details_panel.tsx index 5f7afbe1e9a5b..6cd0d8d106520 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_details_panel.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/details/components/transform_details_panel.tsx @@ -39,7 +39,7 @@ export function TransformDetailsPanel({ stats, transform }: TransformDetailsPane const checkpointStats = [ { - name: 'Last detected chagnes', + name: 'Last detected changes', value: moment(stats.checkpointing.changes_last_detected_at).format('ll LTS'), }, { From a480882bb5b949c55defbf2238307646b60d7e66 Mon Sep 17 00:00:00 2001 From: klacabane Date: Wed, 16 Oct 2024 19:17:55 +0200 Subject: [PATCH 09/12] create form --- .../entity_manager/public/application.tsx | 2 + .../entity_manager/public/hooks/use_kibana.ts | 2 + .../create/components/identity_fields.tsx | 11 +++--- .../create/components/metadata.tsx | 11 ++++-- .../pages/entity_manager/create/create.tsx | 38 +++++++++++++++++-- 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/entity_manager/public/application.tsx b/x-pack/plugins/entity_manager/public/application.tsx index b3568135f553c..508ef190be1c2 100644 --- a/x-pack/plugins/entity_manager/public/application.tsx +++ b/x-pack/plugins/entity_manager/public/application.tsx @@ -22,6 +22,7 @@ import { Route, Router, Routes } from '@kbn/shared-ux-router'; import { PluginContext } from './context/plugin_context'; import { EntityManagerPluginStart } from './types'; import { getRoutes } from './routes'; +import { EntityClient } from './lib/entity_client'; function App() { const routes = getRoutes(); @@ -81,6 +82,7 @@ export function renderApp({ ...core, ...plugins, storage: new Storage(localStorage), + entityClient: new EntityClient(core), isDev, kibanaVersion, isServerless, diff --git a/x-pack/plugins/entity_manager/public/hooks/use_kibana.ts b/x-pack/plugins/entity_manager/public/hooks/use_kibana.ts index c6b15dc2c2c81..f6af1a8877763 100644 --- a/x-pack/plugins/entity_manager/public/hooks/use_kibana.ts +++ b/x-pack/plugins/entity_manager/public/hooks/use_kibana.ts @@ -9,12 +9,14 @@ import { CoreStart } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { EntityManagerPluginStart } from '../types'; +import { EntityClient } from '../lib/entity_client'; export type StartServices = CoreStart & EntityManagerPluginStart & AdditionalServices & { storage: Storage; kibanaVersion: string; + entityClient: EntityClient; }; const useTypedKibana = () => useKibana>(); diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/identity_fields.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/identity_fields.tsx index 167ee4b541b79..f0a73fac0fa98 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/identity_fields.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/identity_fields.tsx @@ -36,18 +36,19 @@ export function IdentityFieldsInput({ control }: { control: Control ( + rules={{ required: true }} + render={({ fieldState }) => ( <> { - e.preventDefault(); - identityFormFields.update(index, { ...item, field: e.target.value }); - }} + onChange={(e) => + identityFormFields.update(index, { ...item, field: e.target.value }) + } fullWidth + isInvalid={fieldState.invalid} /> diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/metadata.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/metadata.tsx index dcd1b2b9fda76..508fa0285e0b7 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/metadata.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/components/metadata.tsx @@ -50,9 +50,14 @@ export function MetadataFieldsInput({ control }: { control: Control - metadataFields.update(index, { ...item, source: e.target.value }) - } + onBlur={() => { + if (item.destination.length === 0) { + metadataFields.update(index, { ...item, destination: item.source }); + } + }} + onChange={(e) => { + metadataFields.update(index, { ...item, source: e.target.value }); + }} fullWidth /> diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx index 26041d727d8f9..c8378b377f27f 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx @@ -4,12 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; +import React, { useState } from 'react'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; import { i18n } from '@kbn/i18n'; import { Controller, FormProvider, SubmitHandler, useForm } from 'react-hook-form'; import { EntityDefinition } from '@kbn/entities-schema'; import { EuiButton, EuiFieldText, EuiForm, EuiFormRow } from '@elastic/eui'; +import { isHttpFetchError } from '@kbn/server-route-repository-client'; import { usePluginContext } from '../../../hooks/use_plugin_context'; import { useKibana } from '../../../hooks/use_kibana'; import { paths } from '../../../../common/locators/paths'; @@ -27,14 +28,23 @@ const DEFAULT_VALUES = { name: '', type: '', indexPatterns: [], + displayNameTemplate: '{{service.name}}', + version: '1.0.0', identityFields: [{ field: '', optional: false }], metadata: [], + metrics: [], + latest: { + timestampField: '@timestamp', + }, }; export function EntityManagerCreatePage() { const { http: { basePath }, + entityClient, } = useKibana().services; + const [isCreating, setIsCreating] = useState(false); + const [errors, setErrors] = useState([]); useBreadcrumbs([ { @@ -55,12 +65,30 @@ export function EntityManagerCreatePage() { }); const { control, handleSubmit } = methods; - const onSubmit: SubmitHandler = (data) => {}; + const onSubmit: SubmitHandler = async (data) => { + setErrors([]); + setIsCreating(true); + + try { + await entityClient.repositoryClient('POST /internal/entities/definition', { + params: { + query: { installOnly: false }, + body: data, + }, + }); + } catch (err) { + if (isHttpFetchError(err) && err.body?.message) { + setErrors([err.body.message]); + } + } finally { + setIsCreating(false); + } + }; return ( - + 0} error={errors}> - Submit + + {isCreating ? 'Creating...' : 'Create definition'} + From aef408b823da753f4c465fa88d9ee30caa554071 Mon Sep 17 00:00:00 2001 From: klacabane Date: Wed, 16 Oct 2024 19:31:04 +0200 Subject: [PATCH 10/12] rename clusterClient --- x-pack/plugins/entity_manager/server/plugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/entity_manager/server/plugin.ts b/x-pack/plugins/entity_manager/server/plugin.ts index b5f37a4ed3e54..152b1b59b3107 100644 --- a/x-pack/plugins/entity_manager/server/plugin.ts +++ b/x-pack/plugins/entity_manager/server/plugin.ts @@ -99,9 +99,9 @@ export class EntityManagerServerPlugin request: KibanaRequest; coreStart: CoreStart; }) { - const scopedClusterClient = coreStart.elasticsearch.client.asScoped(request); + const clusterClient = coreStart.elasticsearch.client.asScoped(request); const soClient = coreStart.savedObjects.getScopedClient(request); - return new EntityClient({ scopedClusterClient, soClient, logger: this.logger }); + return new EntityClient({ clusterClient, soClient, logger: this.logger }); } public start( From 21640b6db44d908dd4b1b5eace9c3d7d89ec1242 Mon Sep 17 00:00:00 2001 From: klacabane Date: Wed, 16 Oct 2024 20:06:54 +0200 Subject: [PATCH 11/12] redirect to definition page on creation --- .../public/pages/entity_manager/create/create.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx index c8378b377f27f..f6ddc44a08dd3 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/create/create.tsx @@ -5,6 +5,7 @@ * 2.0. */ import React, { useState } from 'react'; +import { useHistory } from 'react-router-dom'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; import { i18n } from '@kbn/i18n'; import { Controller, FormProvider, SubmitHandler, useForm } from 'react-hook-form'; @@ -43,6 +44,7 @@ export function EntityManagerCreatePage() { http: { basePath }, entityClient, } = useKibana().services; + const history = useHistory(); const [isCreating, setIsCreating] = useState(false); const [errors, setErrors] = useState([]); @@ -70,12 +72,14 @@ export function EntityManagerCreatePage() { setIsCreating(true); try { - await entityClient.repositoryClient('POST /internal/entities/definition', { + const definition = await entityClient.repositoryClient('POST /internal/entities/definition', { params: { query: { installOnly: false }, body: data, }, }); + + history.replace({ pathname: `/${definition.id}` }); } catch (err) { if (isHttpFetchError(err) && err.body?.message) { setErrors([err.body.message]); From 8c0cb1bdbc653af96ab2a1a49f313193514281f3 Mon Sep 17 00:00:00 2001 From: klacabane Date: Wed, 16 Oct 2024 21:08:02 +0200 Subject: [PATCH 12/12] delete definition button --- .../create_entity_definition_btn.tsx | 1 + .../components/definition_listing.tsx | 77 ++++++++++++++++--- .../entity_manager/overview/overview.tsx | 16 ++-- .../generate_latest_processors.ts | 2 +- 4 files changed, 78 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/create_entity_definition_btn.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/create_entity_definition_btn.tsx index 9f48316e0f9c7..c2aa0ee3e9c80 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/create_entity_definition_btn.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/create_entity_definition_btn.tsx @@ -20,6 +20,7 @@ export function CreateEntityDefinitionBtn() { diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/definition_listing.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/definition_listing.tsx index 89b0a27c6dfda..a06307d01fc3b 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/definition_listing.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/components/definition_listing.tsx @@ -5,9 +5,19 @@ * 2.0. */ -import React from 'react'; +import React, { useState } from 'react'; import { EntityDefinitionWithState, EntityDefintionResponse } from '@kbn/entities-schema'; -import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiText } from '@elastic/eui'; +import { + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiStat, + EuiText, + EuiPopover, + EuiContextMenuPanel, + EuiContextMenuItem, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import numeral from '@elastic/numeral'; import { Badges } from '../../../../components/badges'; @@ -24,8 +34,12 @@ interface ListingProps { function Listing({ definition }: ListingProps) { const { http: { basePath }, + entityClient, } = useKibana().services; + const [isPopoverOpen, setPopover] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); const entityDetailUrl = basePath.prepend(paths.entitieDetail(definition.id)); + const { refetch: refetchDefinitions } = useFetchEntityDefinitions(); const checkpointDuration = definition.resources.transforms.reduce( (acc, transformState) => { @@ -75,11 +89,55 @@ function Listing({ definition }: ListingProps) { /> - + setPopover(!isPopoverOpen)} + /> + } + isOpen={isPopoverOpen} + closePopover={() => setPopover(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + { + try { + setIsDeleting(true); + + await entityClient.repositoryClient( + 'DELETE /internal/entities/definition/{id}', + { + params: { + path: { id: definition.id }, + query: { deleteData: true }, + }, + } + ); + + refetchDefinitions(); + } finally { + setIsDeleting(false); + setPopover(false); + } + }} + > + Delete + , + ]} + /> + @@ -94,9 +152,10 @@ interface Props { export function DefinitionListing() { const { data } = useFetchEntityDefinitions(); if (!data) return null; - const defintions = data?.map((definition) => { + + const definitions = data?.map((definition) => { return ; }); - return {defintions}; + return {definitions}; } diff --git a/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/overview.tsx b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/overview.tsx index b5250a8d3da4a..4b8eb666a5060 100644 --- a/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/overview.tsx +++ b/x-pack/plugins/entity_manager/public/pages/entity_manager/overview/overview.tsx @@ -22,7 +22,7 @@ export function EntityManagerOverviewPage() { http: { basePath }, } = useKibana().services; const { ObservabilityPageTemplate } = usePluginContext(); - const [selectedTabId, setSelectedTabId] = useState('entities'); + const [selectedTabId, setSelectedTabId] = useState('definitions'); useBreadcrumbs([ { @@ -34,13 +34,6 @@ export function EntityManagerOverviewPage() { const tabs = useMemo( () => [ - { - id: 'entities', - name: i18n.translate('xpack.entityManager.overview.entitiesTabLabel', { - defaultMessage: 'Entities', - }), - content: , - }, { id: 'definitions', name: i18n.translate('xpack.entityManager.overview.definitionTabLabel', { @@ -48,6 +41,13 @@ export function EntityManagerOverviewPage() { }), content: , }, + { + id: 'entities', + name: i18n.translate('xpack.entityManager.overview.entitiesTabLabel', { + defaultMessage: 'Entities', + }), + content: , + }, ], [] ); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts b/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts index 0e3812de2e320..9554ceca6d129 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts @@ -193,7 +193,7 @@ export function generateLatestProcessors(definition: EntityDefinition) { set: { field, value: definition.staticFields![field] }, })) : []), - ...(definition.metadata != null + ...(definition.metadata != null && definition.metadata.length > 0 ? [{ script: { source: cleanScript(createMetadataPainlessScript(definition)) } }] : []), {