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"
]
}