From 62258016dc14c5345b47ea7c26c18d9dc494d2a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:38:01 +0100 Subject: [PATCH] [Inventory] Project restructure to show entities grid (#192991) Inventory plugin restructure. Creating server API to fetch entities and initial data grid load on the page. (cherry picked from commit 15c752cdc86c1ca5f7d0c30b3305e477cb14e669) --- .../inventory/common/entities.ts | 30 +++--- .../public/components/entities_grid/index.tsx | 68 +++++++++++++ .../entity_type_list/index.stories.tsx | 88 ----------------- .../components/entity_type_list/index.tsx | 96 ------------------- .../inventory_page_template/index.tsx | 63 ++---------- .../public/pages/inventory_page/index.tsx | 16 ++++ .../inventory/public/routes/config.tsx | 3 +- .../create_entities_es_client.ts | 80 ++++++++++++++++ .../routes/entities/get_latest_entities.ts | 27 ++++++ .../inventory/server/routes/entities/route.ts | 33 +++---- .../inventory/server/utils/with_apm_span.ts | 7 ++ .../inventory/tsconfig.json | 3 +- 12 files changed, 243 insertions(+), 271 deletions(-) create mode 100644 x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx delete mode 100644 x-pack/plugins/observability_solution/inventory/public/components/entity_type_list/index.stories.tsx delete mode 100644 x-pack/plugins/observability_solution/inventory/public/components/entity_type_list/index.tsx create mode 100644 x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx create mode 100644 x-pack/plugins/observability_solution/inventory/server/lib/create_es_client/create_entities_es_client.ts create mode 100644 x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts create mode 100644 x-pack/plugins/observability_solution/inventory/server/utils/with_apm_span.ts diff --git a/x-pack/plugins/observability_solution/inventory/common/entities.ts b/x-pack/plugins/observability_solution/inventory/common/entities.ts index af0e5c82b978f..d72fa46969b8a 100644 --- a/x-pack/plugins/observability_solution/inventory/common/entities.ts +++ b/x-pack/plugins/observability_solution/inventory/common/entities.ts @@ -5,16 +5,22 @@ * 2.0. */ -export interface EntityTypeDefinition { - type: string; - label: string; - icon: string; - count: number; -} - -export interface EntityDefinition { - type: string; - field: string; - filter?: string; - index: string[]; +export interface LatestEntity { + agent: { + name: string[]; + }; + data_stream: { + type: string[]; + }; + cloud: { + availability_zone: string[]; + }; + entity: { + firstSeenTimestamp: string; + lastSeenTimestamp: string; + type: string; + displayName: string; + id: string; + identityFields: string[]; + }; } diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx new file mode 100644 index 0000000000000..e689063882c40 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx @@ -0,0 +1,68 @@ +/* + * 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 { + EuiDataGrid, + EuiDataGridCellValueElementProps, + EuiDataGridColumn, + EuiLoadingSpinner, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useAbortableAsync } from '@kbn/observability-utils/hooks/use_abortable_async'; +import React, { useState } from 'react'; +import { useKibana } from '../../hooks/use_kibana'; + +const columns: EuiDataGridColumn[] = [ + { + id: 'entityName', + displayAsText: 'Entity name', + }, + { + id: 'entityType', + displayAsText: 'Type', + }, +]; + +export function EntitiesGrid() { + const { + services: { inventoryAPIClient }, + } = useKibana(); + const [visibleColumns, setVisibleColumns] = useState(columns.map(({ id }) => id)); + const { value = { entities: [] }, loading } = useAbortableAsync( + ({ signal }) => { + return inventoryAPIClient.fetch('GET /internal/inventory/entities', { + signal, + }); + }, + [inventoryAPIClient] + ); + + if (loading) { + return ; + } + + function CellValue({ rowIndex, columnId, setCellProps }: EuiDataGridCellValueElementProps) { + const data = value.entities[rowIndex]; + if (data === undefined) { + return null; + } + + return <>{data.entity.displayName}; + } + + return ( + + ); +} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_type_list/index.stories.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_type_list/index.stories.tsx deleted file mode 100644 index 570622406c9ae..0000000000000 --- a/x-pack/plugins/observability_solution/inventory/public/components/entity_type_list/index.stories.tsx +++ /dev/null @@ -1,88 +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 { Meta, StoryObj } from '@storybook/react'; -import React from 'react'; -import { mergePlainObjects } from '@kbn/investigate-plugin/common'; -import { EntityTypeListBase as Component } from '.'; -import { KibanaReactStorybookDecorator } from '../../../.storybook/storybook_decorator'; - -interface Args { - props: Omit, 'onLockAllClick' | 'onUnlockAllClick'>; -} - -type StoryMeta = Meta; -type Story = StoryObj; - -const meta: StoryMeta = { - component: Component, - title: 'app/Molecules/EntityTypeList', - decorators: [KibanaReactStorybookDecorator], -}; - -export default meta; - -const defaultStory: Story = { - args: { - props: { - definitions: [], - loading: true, - }, - }, - render: function Render(args) { - return ( -
- -
- ); - }, -}; - -export const Default: Story = { - ...defaultStory, - args: { - props: mergePlainObjects(defaultStory.args!.props!, { - loading: false, - definitions: [ - { - icon: 'node', - label: 'Services', - type: 'service', - count: 9, - }, - { - icon: 'pipeNoBreaks', - label: 'Datasets', - type: 'dataset', - count: 11, - }, - ], - }), - }, - name: 'default', -}; - -export const Empty: Story = { - ...defaultStory, - args: { - props: mergePlainObjects(defaultStory.args!.props!, { - definitions: [], - loading: false, - }), - }, - name: 'empty', -}; - -export const Loading: Story = { - ...defaultStory, - args: { - props: mergePlainObjects(defaultStory.args!.props!, { - loading: true, - }), - }, - name: 'loading', -}; diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_type_list/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_type_list/index.tsx deleted file mode 100644 index 47488f23f3252..0000000000000 --- a/x-pack/plugins/observability_solution/inventory/public/components/entity_type_list/index.tsx +++ /dev/null @@ -1,96 +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 { useAbortableAsync } from '@kbn/observability-utils/hooks/use_abortable_async'; -import { - EuiBadge, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiLink, - EuiLoadingSpinner, - EuiText, -} from '@elastic/eui'; -import { useKibana } from '../../hooks/use_kibana'; -import { EntityTypeDefinition } from '../../../common/entities'; -import { useInventoryRouter } from '../../hooks/use_inventory_router'; - -export function EntityTypeListItem({ - href, - icon, - label, - count, -}: { - href: string; - icon: string; - label: string; - count: number; -}) { - return ( - - - - - - - {label} - - - {count} - - - - ); -} - -export function EntityTypeListBase({ - definitions, - loading, - error, -}: { - loading?: boolean; - definitions?: EntityTypeDefinition[]; - error?: Error; -}) { - const router = useInventoryRouter(); - if (loading) { - return ; - } - - return ( - - {definitions?.map((definition) => { - return ( - - ); - })} - - ); -} - -export function EntityTypeList() { - const { - services: { inventoryAPIClient }, - } = useKibana(); - - const { value, loading, error } = useAbortableAsync( - ({ signal }) => { - return inventoryAPIClient.fetch('GET /internal/inventory/entity_types', { - signal, - }); - }, - [inventoryAPIClient] - ); - - return ; -} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/index.tsx index 386df9a51cae5..4dd8eaf3899ee 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/index.tsx @@ -4,13 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiFlexGroup, EuiPanel, EuiTitle } from '@elastic/eui'; -import { css } from '@emotion/css'; import { i18n } from '@kbn/i18n'; -import { useTheme } from '@kbn/observability-utils/hooks/use_theme'; import React from 'react'; import { useKibana } from '../../hooks/use_kibana'; -import { EntityTypeList } from '../entity_type_list'; export function InventoryPageTemplate({ children }: { children: React.ReactNode }) { const { @@ -19,60 +15,17 @@ export function InventoryPageTemplate({ children }: { children: React.ReactNode }, } = useKibana(); - const { PageTemplate } = observabilityShared.navigation; - - const theme = useTheme(); + const { PageTemplate: ObservabilityPageTemplate } = observabilityShared.navigation; return ( - - - - - -

- {i18n.translate('xpack.inventory.inventoryPageHeaderLabel', { - defaultMessage: 'Inventory', - })} -

-
- - - -
-
- - - {children} - -
-
+ {children} + ); } diff --git a/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx b/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx new file mode 100644 index 0000000000000..9389fdaca3ea0 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx @@ -0,0 +1,16 @@ +/* + * 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 { EntitiesGrid } from '../../components/entities_grid'; + +export function InventoryPage() { + return ( +
+ +
+ ); +} diff --git a/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx b/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx index 11d9d4836d981..74eeaac220bc2 100644 --- a/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx @@ -8,6 +8,7 @@ import * as t from 'io-ts'; import { createRouter, Outlet } from '@kbn/typed-react-router-config'; import React from 'react'; import { InventoryPageTemplate } from '../components/inventory_page_template'; +import { InventoryPage } from '../pages/inventory_page'; /** * The array of route definitions to be used when the application @@ -28,7 +29,7 @@ const inventoryRoutes = { }), }, '/': { - element: <>, + element: , }, }, }, diff --git a/x-pack/plugins/observability_solution/inventory/server/lib/create_es_client/create_entities_es_client.ts b/x-pack/plugins/observability_solution/inventory/server/lib/create_es_client/create_entities_es_client.ts new file mode 100644 index 0000000000000..983a4df3e96af --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/server/lib/create_es_client/create_entities_es_client.ts @@ -0,0 +1,80 @@ +/* + * 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 { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; +import type { KibanaRequest } from '@kbn/core/server'; +import { ElasticsearchClient } from '@kbn/core/server'; +import { entitiesAliasPattern, ENTITY_LATEST } from '@kbn/entities-schema'; +import { unwrapEsResponse } from '@kbn/observability-shared-plugin/common/utils/unwrap_es_response'; +// import { withApmSpan } from '../../utils/with_apm_span'; + +const ENTITIES_LATEST_ALIAS = entitiesAliasPattern({ + type: '*', + dataset: ENTITY_LATEST, +}); + +export function cancelEsRequestOnAbort>( + promise: T, + request: KibanaRequest, + controller: AbortController +): T { + const subscription = request.events.aborted$.subscribe(() => { + controller.abort(); + }); + + return promise.finally(() => subscription.unsubscribe()) as T; +} + +export interface EntitiesESClient { + searchLatest( + operationName: string, + searchRequest: TSearchRequest + ): Promise>; +} + +export function createEntitiesESClient({ + request, + esClient, +}: { + request: KibanaRequest; + esClient: ElasticsearchClient; +}) { + function search( + indexName: string, + operationName: string, + searchRequest: TSearchRequest + ): Promise> { + const controller = new AbortController(); + + const promise = // withApmSpan(operationName, () => { + cancelEsRequestOnAbort( + esClient.search( + { ...searchRequest, index: [indexName], ignore_unavailable: true }, + { + signal: controller.signal, + meta: true, + } + ) as unknown as Promise<{ + body: InferSearchResponseOf; + }>, + request, + controller + ); + // }); + // + return unwrapEsResponse(promise); + } + + return { + searchLatest( + operationName: string, + searchRequest: TSearchRequest + ): Promise> { + return search(ENTITIES_LATEST_ALIAS, operationName, searchRequest); + }, + }; +} diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts new file mode 100644 index 0000000000000..4ddcaaf75c9a4 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.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 { LatestEntity } from '../../../common/entities'; +import { EntitiesESClient } from '../../lib/create_es_client/create_entities_es_client'; + +const MAX_NUMBER_OF_ENTITIES = 500; + +export async function getLatestEntities({ + entitiesESClient, +}: { + entitiesESClient: EntitiesESClient; +}) { + const response = ( + await entitiesESClient.searchLatest('get_latest_entities', { + body: { + size: MAX_NUMBER_OF_ENTITIES, + }, + }) + ).hits.hits.map((hit) => hit._source); + + return response; +} diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts index 0622ed32ac9dc..093e5ff399ed1 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts @@ -4,31 +4,28 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { i18n } from '@kbn/i18n'; -import type { EntityTypeDefinition } from '../../../common/entities'; import { createInventoryServerRoute } from '../create_inventory_server_route'; +import { createEntitiesESClient } from '../../lib/create_es_client/create_entities_es_client'; +import { getLatestEntities } from './get_latest_entities'; -export const listEntityTypesRoute = createInventoryServerRoute({ - endpoint: 'GET /internal/inventory/entity_types', +export const listLatestEntitiesRoute = createInventoryServerRoute({ + endpoint: 'GET /internal/inventory/entities', options: { tags: ['access:inventory'], }, - handler: async ({ plugins, request }): Promise<{ definitions: EntityTypeDefinition[] }> => { - return { - definitions: [ - { - label: i18n.translate('xpack.inventory.entityTypeLabels.datasets', { - defaultMessage: 'Datasets', - }), - icon: 'pipeNoBreaks', - type: 'dataset', - count: 0, - }, - ], - }; + handler: async ({ plugins, request, context }) => { + const coreContext = await context.core; + const entitiesESClient = createEntitiesESClient({ + esClient: coreContext.elasticsearch.client.asCurrentUser, + request, + }); + + const latestEntities = await getLatestEntities({ entitiesESClient }); + + return { entities: latestEntities }; }, }); export const entitiesRoutes = { - ...listEntityTypesRoute, + ...listLatestEntitiesRoute, }; diff --git a/x-pack/plugins/observability_solution/inventory/server/utils/with_apm_span.ts b/x-pack/plugins/observability_solution/inventory/server/utils/with_apm_span.ts new file mode 100644 index 0000000000000..b9e79df6cb0d7 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/server/utils/with_apm_span.ts @@ -0,0 +1,7 @@ +/* + * 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 { withApmSpan } from '@kbn/apm-data-access-plugin/server/utils'; diff --git a/x-pack/plugins/observability_solution/inventory/tsconfig.json b/x-pack/plugins/observability_solution/inventory/tsconfig.json index 89fdd2e8fdf01..c0fc7c2692fde 100644 --- a/x-pack/plugins/observability_solution/inventory/tsconfig.json +++ b/x-pack/plugins/observability_solution/inventory/tsconfig.json @@ -24,7 +24,6 @@ "@kbn/server-route-repository", "@kbn/shared-ux-link-redirect-app", "@kbn/typed-react-router-config", - "@kbn/investigate-plugin", "@kbn/observability-utils", "@kbn/kibana-react-plugin", "@kbn/i18n", @@ -35,5 +34,7 @@ "@kbn/data-views-plugin", "@kbn/server-route-repository-client", "@kbn/react-kibana-context-render", + "@kbn/es-types", + "@kbn/entities-schema" ] }