From f07c18be87064f2a2e530a521659c0fc6f0f9130 Mon Sep 17 00:00:00 2001 From: Jenny Date: Fri, 22 Nov 2024 16:57:37 +0100 Subject: [PATCH 01/11] Change discover link to use entity definition --- .../public/lib/entity_client.ts | 21 +++- .../public/components/entities_grid/index.tsx | 19 +--- .../components/entity_actions/index.tsx | 68 +++++++------ .../index.tsx | 5 +- ...ry_data_view.ts => use_adhoc_data_view.ts} | 7 +- .../public/hooks/use_discover_redirect.ts | 98 +++++++++---------- .../public/hooks/use_entity_definition.ts | 28 ++++++ .../hooks/use_unified_search_context.ts | 5 +- 8 files changed, 145 insertions(+), 106 deletions(-) rename x-pack/plugins/observability_solution/inventory/public/hooks/{use_adhoc_inventory_data_view.ts => use_adhoc_data_view.ts} (82%) create mode 100644 x-pack/plugins/observability_solution/inventory/public/hooks/use_entity_definition.ts diff --git a/x-pack/plugins/entity_manager/public/lib/entity_client.ts b/x-pack/plugins/entity_manager/public/lib/entity_client.ts index 9db1c37888d4b..c5c4dd6200419 100644 --- a/x-pack/plugins/entity_manager/public/lib/entity_client.ts +++ b/x-pack/plugins/entity_manager/public/lib/entity_client.ts @@ -13,8 +13,9 @@ import { isHttpFetchError, } from '@kbn/server-route-repository-client'; import { type KueryNode, nodeTypes, toKqlExpression } from '@kbn/es-query'; -import type { EntityInstance, EntityMetadata } from '@kbn/entities-schema'; +import type { EntityDefinition, EntityInstance, EntityMetadata } from '@kbn/entities-schema'; import { castArray } from 'lodash'; +import type { EntityDefinitionWithState } from '../../server/lib/entities/types'; import { DisableManagedEntityResponse, EnableManagedEntityResponse, @@ -87,6 +88,24 @@ export class EntityClient { } } + async getEntityDefinition( + id: string + ): Promise<{ definitions: EntityDefinition[] | EntityDefinitionWithState[] }> { + try { + return await this.repositoryClient('GET /internal/entities/definition/{id}', { + params: { + path: { id }, + query: { page: 1, perPage: 1 }, + }, + }); + } catch (err) { + if (isHttpFetchError(err) && err.body?.statusCode === 403) { + throw new EntityManagerUnauthorizedError(err.body.message); + } + throw err; + } + } + asKqlFilter( entityInstance: { entity: Pick; 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 index ff4329955773d..34a8a452d1896 100644 --- 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 @@ -14,7 +14,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedDate, FormattedMessage, FormattedTime } from '@kbn/i18n-react'; import { last } from 'lodash'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; import { EntityColumnIds, InventoryEntity } from '../../../common/entities'; import { BadgeFilterWithPopover } from '../badge_filter_with_popover'; @@ -22,7 +22,6 @@ import { getColumns } from './grid_columns'; import { AlertsBadge } from '../alerts_badge/alerts_badge'; import { EntityName } from './entity_name'; import { EntityActions } from '../entity_actions'; -import { useDiscoverRedirect } from '../../hooks/use_discover_redirect'; interface Props { loading: boolean; @@ -45,7 +44,7 @@ export function EntitiesGrid({ onChangePage, onChangeSort, }: Props) { - const { getDiscoverRedirectUrl } = useDiscoverRedirect(); + const [showActions, setShowActions] = useState(true); const onSort: EuiDataGridSorting['onSort'] = useCallback( (newSortingColumns) => { @@ -62,8 +61,6 @@ export function EntitiesGrid({ [entities] ); - const showActions = useMemo(() => !!getDiscoverRedirectUrl(), [getDiscoverRedirectUrl]); - const columnVisibility = useMemo( () => ({ visibleColumns: getColumns({ showAlertsColumn, showActions }).map(({ id }) => id), @@ -81,7 +78,6 @@ export function EntitiesGrid({ const columnEntityTableId = columnId as EntityColumnIds; const entityType = entity.entityType; - const discoverUrl = getDiscoverRedirectUrl(entity); switch (columnEntityTableId) { case 'alertsCount': @@ -119,19 +115,12 @@ export function EntitiesGrid({ case 'entityDisplayName': return ; case 'actions': - return ( - discoverUrl && ( - - ) - ); + return ; default: return null; } }, - [entities, getDiscoverRedirectUrl] + [entities] ); if (loading) { diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx index 95a4050fba4e9..dab11e8d7ca9f 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx @@ -7,24 +7,36 @@ import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; +import React, { type SetStateAction } from 'react'; import { useBoolean } from '@kbn/react-hooks'; +import type { Dispatch } from '@kbn/kibana-utils-plugin/common'; +import type { InventoryEntity } from '../../../common/entities'; +import { useDiscoverRedirect } from '../../hooks/use_discover_redirect'; interface Props { - discoverUrl: string; - entityIdentifyingValue?: string; + entity: InventoryEntity; + setShowActions: Dispatch>; } -export const EntityActions = ({ discoverUrl, entityIdentifyingValue }: Props) => { +export const EntityActions = ({ entity, setShowActions }: Props) => { const [isPopoverOpen, { toggle: togglePopover, off: closePopover }] = useBoolean(false); - const actionButtonTestSubject = entityIdentifyingValue - ? `inventoryEntityActionsButton-${entityIdentifyingValue}` + const actionButtonTestSubject = entity.identifyingValue + ? `inventoryEntityActionsButton-${entity.identifyingValue}` : 'inventoryEntityActionsButton'; + const { getDiscoverEntitiesRedirectUrl } = useDiscoverRedirect(entity); + + const discoverUrl = getDiscoverEntitiesRedirectUrl(); + + if (!discoverUrl) { + setShowActions(false); + return null; + } + const actions = [ ]; return ( - <> - - } - closePopover={closePopover} - > - - - + + } + closePopover={closePopover} + > + + ); }; diff --git a/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx b/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx index f5a71e80bd9a3..d43cba80dd177 100644 --- a/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx @@ -7,7 +7,8 @@ import React, { createContext, useContext, type ReactChild } from 'react'; import { Subject } from 'rxjs'; import { DataView } from '@kbn/data-views-plugin/common'; -import { useAdHocInventoryDataView } from '../../hooks/use_adhoc_inventory_data_view'; +import { ENTITIES_LATEST_ALIAS } from '../../../common/entities'; +import { useAdHocDataView } from '../../hooks/use_adhoc_data_view'; interface InventorySearchBarContextType { searchBarContentSubject$: Subject<{ @@ -24,7 +25,7 @@ const InventorySearchBarContext = createContext({ }); export function InventorySearchBarContextProvider({ children }: { children: ReactChild }) { - const { dataView } = useAdHocInventoryDataView(); + const { dataView } = useAdHocDataView(ENTITIES_LATEST_ALIAS); return ( { +export const useDiscoverRedirect = (entity: InventoryEntity) => { const { services: { share, application, entityManager }, } = useKibana(); + const { entityDefinitions } = useEntityDefinition(entity.entityDefinitionId); + + const title = useMemo( + () => + entityDefinitions && entityDefinitions?.length > 0 + ? entityDefinitions[0]?.indexPatterns?.join(',') + : '', + [entityDefinitions] + ); + + const { dataView } = useAdHocDataView(title); const { - dataView, searchState: { query, filters, panelFilters }, } = useUnifiedSearchContext(); const discoverLocator = share.url.locators.get('DISCOVER_APP_LOCATOR'); - const getDiscoverEntitiesRedirectUrl = useCallback( - (entity?: InventoryEntity) => { - const entityKqlFilter = entity - ? entityManager.entityClient.asKqlFilter({ - entity: { - identity_fields: entity.entityIdentityFields, - }, - ...entity, - }) - : ''; + const getDiscoverEntitiesRedirectUrl = useCallback(() => { + const entityKqlFilter = entity + ? entityManager.entityClient.asKqlFilter({ + entity: { + identity_fields: entity.entityIdentityFields, + }, + ...entity, + }) + : ''; - const kueryWithEntityDefinitionFilters = [ - query.query, - entityKqlFilter, - `${ENTITY_DEFINITION_ID} : builtin*`, - ] - .filter(Boolean) - .join(' AND '); + const kueryWithEntityDefinitionFilters = [query.query, entityKqlFilter] + .filter(Boolean) + .join(' AND '); - return application.capabilities.discover?.show - ? discoverLocator?.getRedirectUrl({ - indexPatternId: dataView?.id ?? '', - columns: ACTIVE_COLUMNS, - query: { query: kueryWithEntityDefinitionFilters, language: 'kuery' }, - filters: [...filters, ...panelFilters], - }) - : undefined; - }, - [ - application.capabilities.discover?.show, - dataView?.id, - discoverLocator, - entityManager.entityClient, - filters, - panelFilters, - query.query, - ] - ); - - const getDiscoverRedirectUrl = useCallback( - (entity?: InventoryEntity) => getDiscoverEntitiesRedirectUrl(entity), - [getDiscoverEntitiesRedirectUrl] - ); + return application.capabilities.discover?.show + ? discoverLocator?.getRedirectUrl({ + indexPatternId: dataView?.id ?? '', + query: { query: kueryWithEntityDefinitionFilters, language: 'kuery' }, + filters: [...filters, ...panelFilters], + }) + : undefined; + }, [ + application.capabilities.discover?.show, + dataView?.id, + discoverLocator, + entity, + entityManager.entityClient, + filters, + panelFilters, + query.query, + ]); - return { getDiscoverRedirectUrl }; + return { getDiscoverEntitiesRedirectUrl }; }; diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_entity_definition.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_entity_definition.ts new file mode 100644 index 0000000000000..659ee353f18f1 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_entity_definition.ts @@ -0,0 +1,28 @@ +/* + * 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 { useAbortableAsync } from '@kbn/observability-utils-browser/hooks/use_abortable_async'; +import { useKibana } from './use_kibana'; + +export const useEntityDefinition = (id: string) => { + const { + services: { entityManager }, + } = useKibana(); + + const { value, loading, refresh } = useAbortableAsync( + ({ signal }) => { + return entityManager.entityClient.getEntityDefinition(id); + }, + [entityManager.entityClient, id] + ); + + return { + entityDefinitions: value?.definitions, + isEnablementLoading: loading, + refresh, + }; +}; diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts index 94df3a035f3bb..3c520867540c9 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts @@ -13,13 +13,14 @@ import useEffectOnce from 'react-use/lib/useEffectOnce'; import deepEqual from 'fast-deep-equal'; import { i18n } from '@kbn/i18n'; import { useKibanaQuerySettings } from '@kbn/observability-shared-plugin/public'; -import { useAdHocInventoryDataView } from './use_adhoc_inventory_data_view'; +import { ENTITIES_LATEST_ALIAS } from '../../common/entities'; +import { useAdHocDataView } from './use_adhoc_data_view'; import { useUnifiedSearchUrl } from './use_unified_search_url'; import { useKibana } from './use_kibana'; function useUnifiedSearch() { const [isControlPanelsInitiated, setIsControlPanelsInitiated] = useState(false); - const { dataView } = useAdHocInventoryDataView(); + const { dataView } = useAdHocDataView(ENTITIES_LATEST_ALIAS); const [refreshSubject$] = useState>(new Subject()); const { searchState, setSearchState } = useUnifiedSearchUrl(); const kibanaQuerySettings = useKibanaQuerySettings(); From 624cfefb07f95085ebde2d92e04b4a23eef7b504 Mon Sep 17 00:00:00 2001 From: Jenny Date: Mon, 25 Nov 2024 11:59:47 +0100 Subject: [PATCH 02/11] Change button text to Explore in Discover --- .../inventory/public/components/entity_actions/index.tsx | 4 ++-- x-pack/plugins/translations/translations/fr-FR.json | 1 - x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx index dab11e8d7ca9f..e550cbf093d75 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx @@ -41,8 +41,8 @@ export const EntityActions = ({ entity, setShowActions }: Props) => { icon="discoverApp" href={discoverUrl} > - {i18n.translate('xpack.inventory.entityActions.discoverLink', { - defaultMessage: 'Open in discover', + {i18n.translate('xpack.inventory.entityActions.exploreInDiscoverLink', { + defaultMessage: 'Explore in Discover', })} , ]; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index f64646a2bf3d2..7d9948b8a390b 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -26188,7 +26188,6 @@ "xpack.inventory.entitiesGrid.euiDataGrid.lastSeenTooltip": "Horodatage des dernières données reçues pour l'entité (entity.lastSeenTimestamp)", "xpack.inventory.entitiesGrid.euiDataGrid.typeLabel": "Type", "xpack.inventory.entitiesGrid.euiDataGrid.typeTooltip": "Type d'entité (entity.type)", - "xpack.inventory.entityActions.discoverLink": "Ouvrir dans Discover", "xpack.inventory.featureRegistry.inventoryFeatureName": "Inventory", "xpack.inventory.home.serviceAlertsTable.tooltip.activeAlertsExplanation": "Alertes actives", "xpack.inventory.inventoryLinkTitle": "Inventory", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index cb39940e6ffb6..e09542ba2e465 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -26160,7 +26160,6 @@ "xpack.inventory.entitiesGrid.euiDataGrid.lastSeenTooltip": "エンティティで最後に受信したデータのタイムスタンプ(entity.lastSeenTimestamp)", "xpack.inventory.entitiesGrid.euiDataGrid.typeLabel": "型", "xpack.inventory.entitiesGrid.euiDataGrid.typeTooltip": "エンティティのタイプ(entity.type)", - "xpack.inventory.entityActions.discoverLink": "Discoverで開く", "xpack.inventory.featureRegistry.inventoryFeatureName": "インベントリ", "xpack.inventory.home.serviceAlertsTable.tooltip.activeAlertsExplanation": "アクティブアラート", "xpack.inventory.inventoryLinkTitle": "インベントリ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 150003b98dd48..2c81dbbe0835e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -25690,7 +25690,6 @@ "xpack.inventory.entitiesGrid.euiDataGrid.lastSeenTooltip": "上次接收的实体数据的时间戳 (entity.lastSeenTimestamp)", "xpack.inventory.entitiesGrid.euiDataGrid.typeLabel": "类型", "xpack.inventory.entitiesGrid.euiDataGrid.typeTooltip": "实体的类型 (entity.type)", - "xpack.inventory.entityActions.discoverLink": "在 Discover 中打开", "xpack.inventory.featureRegistry.inventoryFeatureName": "库存", "xpack.inventory.home.serviceAlertsTable.tooltip.activeAlertsExplanation": "活动告警", "xpack.inventory.inventoryLinkTitle": "库存", From 5183e149e39556b9798019684ea7c41f3248a9b9 Mon Sep 17 00:00:00 2001 From: Jenny Date: Mon, 25 Nov 2024 16:11:45 +0100 Subject: [PATCH 03/11] Type issue fix --- .../entity_manager/public/lib/entity_client.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/entity_manager/public/lib/entity_client.ts b/x-pack/plugins/entity_manager/public/lib/entity_client.ts index c5c4dd6200419..f3ee4181f6087 100644 --- a/x-pack/plugins/entity_manager/public/lib/entity_client.ts +++ b/x-pack/plugins/entity_manager/public/lib/entity_client.ts @@ -92,12 +92,15 @@ export class EntityClient { id: string ): Promise<{ definitions: EntityDefinition[] | EntityDefinitionWithState[] }> { try { - return await this.repositoryClient('GET /internal/entities/definition/{id}', { - params: { - path: { id }, - query: { page: 1, perPage: 1 }, - }, - }); + return await this.repositoryClient( + 'GET /internal/entities/definition/{id}' as unknown as keyof EntityManagerRouteRepository, + { + params: { + path: { id }, + query: { page: 1, perPage: 1 }, + }, + } + ); } catch (err) { if (isHttpFetchError(err) && err.body?.statusCode === 403) { throw new EntityManagerUnauthorizedError(err.body.message); From 8283ac47e9d3ce3d6821e710b96a03e18dfa690c Mon Sep 17 00:00:00 2001 From: Jenny Date: Mon, 25 Nov 2024 16:14:44 +0100 Subject: [PATCH 04/11] Remove filters and kql when navigating to Discover --- .../inventory/e2e/cypress/e2e/home.cy.ts | 5 +---- .../public/hooks/use_discover_redirect.ts | 15 +-------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts index fdb68826e9dc8..d9c959d880f28 100644 --- a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts +++ b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts @@ -223,10 +223,7 @@ describe('Home page', () => { cy.getByTestSubj('inventoryGroupTitle_entity.type_container').click(); cy.getByTestSubj('inventoryEntityActionsButton-foo').click(); cy.getByTestSubj('inventoryEntityActionOpenInDiscover').click(); - cy.url().should( - 'include', - "query:'container.id:%20%22foo%22%20AND%20entity.definition_id%20:%20builtin*" - ); + cy.url().should('include', "query:'container.id:%20%22foo%22%20"); }); }); }); diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts index ab98a2ba9f5df..49530de9d2146 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts @@ -7,7 +7,6 @@ import { useCallback, useMemo } from 'react'; import type { InventoryEntity } from '../../common/entities'; import { useKibana } from './use_kibana'; -import { useUnifiedSearchContext } from './use_unified_search_context'; import { useEntityDefinition } from './use_entity_definition'; import { useAdHocDataView } from './use_adhoc_data_view'; @@ -27,10 +26,6 @@ export const useDiscoverRedirect = (entity: InventoryEntity) => { const { dataView } = useAdHocDataView(title); - const { - searchState: { query, filters, panelFilters }, - } = useUnifiedSearchContext(); - const discoverLocator = share.url.locators.get('DISCOVER_APP_LOCATOR'); const getDiscoverEntitiesRedirectUrl = useCallback(() => { @@ -43,15 +38,10 @@ export const useDiscoverRedirect = (entity: InventoryEntity) => { }) : ''; - const kueryWithEntityDefinitionFilters = [query.query, entityKqlFilter] - .filter(Boolean) - .join(' AND '); - return application.capabilities.discover?.show ? discoverLocator?.getRedirectUrl({ indexPatternId: dataView?.id ?? '', - query: { query: kueryWithEntityDefinitionFilters, language: 'kuery' }, - filters: [...filters, ...panelFilters], + query: { query: entityKqlFilter, language: 'kuery' }, }) : undefined; }, [ @@ -60,9 +50,6 @@ export const useDiscoverRedirect = (entity: InventoryEntity) => { discoverLocator, entity, entityManager.entityClient, - filters, - panelFilters, - query.query, ]); return { getDiscoverEntitiesRedirectUrl }; From bca2b19d844206330ff44981da8e93db6039a36d Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:01:35 +0000 Subject: [PATCH 05/11] [CI] Auto-commit changed files from 'node scripts/yarn_deduplicate' --- x-pack/plugins/observability_solution/inventory/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/inventory/tsconfig.json b/x-pack/plugins/observability_solution/inventory/tsconfig.json index 5094201cc2975..b276c3e7e28b1 100644 --- a/x-pack/plugins/observability_solution/inventory/tsconfig.json +++ b/x-pack/plugins/observability_solution/inventory/tsconfig.json @@ -59,6 +59,7 @@ "@kbn/react-hooks", "@kbn/observability-utils-common", "@kbn/observability-utils-browser", - "@kbn/observability-utils-server" + "@kbn/observability-utils-server", + "@kbn/kibana-utils-plugin" ] } From d95696eda5fa2a6eb4375625df9eabe42f31a1d8 Mon Sep 17 00:00:00 2001 From: Jenny Date: Mon, 25 Nov 2024 18:05:06 +0100 Subject: [PATCH 06/11] Handle loading and small data fetching fixes --- .../components/entity_actions/index.tsx | 36 ++++++++++--------- .../public/hooks/use_discover_redirect.ts | 12 ++++--- ...tion.ts => use_fetch_entity_definition.ts} | 7 ++-- 3 files changed, 30 insertions(+), 25 deletions(-) rename x-pack/plugins/observability_solution/inventory/public/hooks/{use_entity_definition.ts => use_fetch_entity_definition.ts} (80%) diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx index e550cbf093d75..eb1ff7b4d2101 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx @@ -24,28 +24,31 @@ export const EntityActions = ({ entity, setShowActions }: Props) => { ? `inventoryEntityActionsButton-${entity.identifyingValue}` : 'inventoryEntityActionsButton'; - const { getDiscoverEntitiesRedirectUrl } = useDiscoverRedirect(entity); - + const { getDiscoverEntitiesRedirectUrl, isEntityDefinitionLoading } = useDiscoverRedirect(entity); const discoverUrl = getDiscoverEntitiesRedirectUrl(); - if (!discoverUrl) { + const actions = []; + + if (!discoverUrl && !isEntityDefinitionLoading) { setShowActions(false); return null; } - const actions = [ - - {i18n.translate('xpack.inventory.entityActions.exploreInDiscoverLink', { - defaultMessage: 'Explore in Discover', - })} - , - ]; + if (!isEntityDefinitionLoading) { + actions.push( + + {i18n.translate('xpack.inventory.entityActions.exploreInDiscoverLink', { + defaultMessage: 'Explore in Discover', + })} + + ); + } return ( { iconType="boxesHorizontal" color="text" onClick={togglePopover} + isLoading={isEntityDefinitionLoading} /> } closePopover={closePopover} diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts index 49530de9d2146..406dd44b505f4 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts @@ -7,21 +7,23 @@ import { useCallback, useMemo } from 'react'; import type { InventoryEntity } from '../../common/entities'; import { useKibana } from './use_kibana'; -import { useEntityDefinition } from './use_entity_definition'; +import { useFetchEntityDefinition } from './use_fetch_entity_definition'; import { useAdHocDataView } from './use_adhoc_data_view'; export const useDiscoverRedirect = (entity: InventoryEntity) => { const { services: { share, application, entityManager }, } = useKibana(); - const { entityDefinitions } = useEntityDefinition(entity.entityDefinitionId); + const { entityDefinitions, isEntityDefinitionLoading } = useFetchEntityDefinition( + entity.entityDefinitionId + ); const title = useMemo( () => - entityDefinitions && entityDefinitions?.length > 0 + !isEntityDefinitionLoading && entityDefinitions && entityDefinitions?.length > 0 ? entityDefinitions[0]?.indexPatterns?.join(',') : '', - [entityDefinitions] + [entityDefinitions, isEntityDefinitionLoading] ); const { dataView } = useAdHocDataView(title); @@ -52,5 +54,5 @@ export const useDiscoverRedirect = (entity: InventoryEntity) => { entityManager.entityClient, ]); - return { getDiscoverEntitiesRedirectUrl }; + return { getDiscoverEntitiesRedirectUrl, isEntityDefinitionLoading }; }; diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_entity_definition.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_fetch_entity_definition.ts similarity index 80% rename from x-pack/plugins/observability_solution/inventory/public/hooks/use_entity_definition.ts rename to x-pack/plugins/observability_solution/inventory/public/hooks/use_fetch_entity_definition.ts index 659ee353f18f1..9f6a0232891b2 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_entity_definition.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_fetch_entity_definition.ts @@ -8,12 +8,12 @@ import { useAbortableAsync } from '@kbn/observability-utils-browser/hooks/use_abortable_async'; import { useKibana } from './use_kibana'; -export const useEntityDefinition = (id: string) => { +export const useFetchEntityDefinition = (id: string) => { const { services: { entityManager }, } = useKibana(); - const { value, loading, refresh } = useAbortableAsync( + const { value, loading } = useAbortableAsync( ({ signal }) => { return entityManager.entityClient.getEntityDefinition(id); }, @@ -22,7 +22,6 @@ export const useEntityDefinition = (id: string) => { return { entityDefinitions: value?.definitions, - isEnablementLoading: loading, - refresh, + isEntityDefinitionLoading: loading, }; }; From 42387fa1ae85cc22153d7f284520c641bf791496 Mon Sep 17 00:00:00 2001 From: Jenny Date: Mon, 25 Nov 2024 18:23:19 +0100 Subject: [PATCH 07/11] Fix actions type --- .../inventory/public/components/entity_actions/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx index eb1ff7b4d2101..e6a37b6746bbb 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx @@ -27,7 +27,7 @@ export const EntityActions = ({ entity, setShowActions }: Props) => { const { getDiscoverEntitiesRedirectUrl, isEntityDefinitionLoading } = useDiscoverRedirect(entity); const discoverUrl = getDiscoverEntitiesRedirectUrl(); - const actions = []; + const actions: React.ReactElement[] = []; if (!discoverUrl && !isEntityDefinitionLoading) { setShowActions(false); From 8e24cdd0f58dfd2ae81976a83c131b29ed8636eb Mon Sep 17 00:00:00 2001 From: Jenny Date: Tue, 26 Nov 2024 12:16:44 +0100 Subject: [PATCH 08/11] Fix test --- .../inventory/e2e/cypress/e2e/home.cy.ts | 7 +++++-- .../inventory/public/components/entity_actions/index.tsx | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts index d9c959d880f28..61a55bab3258f 100644 --- a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts +++ b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts @@ -217,13 +217,16 @@ describe('Home page', () => { cy.intercept('GET', '/internal/entities/managed/enablement', { fixture: 'eem_enabled.json', }).as('getEEMStatus'); + cy.intercept('GET', '/internal/inventory/entities?**').as('getEntities'); cy.visitKibana('/app/inventory'); cy.wait('@getEEMStatus'); cy.contains('container'); cy.getByTestSubj('inventoryGroupTitle_entity.type_container').click(); + cy.wait('@getEntities'); + // cy.getByTestSubj('inventoryEntityActionsButton').click(); cy.getByTestSubj('inventoryEntityActionsButton-foo').click(); - cy.getByTestSubj('inventoryEntityActionOpenInDiscover').click(); - cy.url().should('include', "query:'container.id:%20%22foo%22%20"); + cy.getByTestSubj('inventoryEntityActionExploreInDiscover').click(); + cy.url().should('include', "query:'container.id:%20%22foo%22"); }); }); }); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx index e6a37b6746bbb..691ba4388ac63 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entity_actions/index.tsx @@ -20,8 +20,8 @@ interface Props { export const EntityActions = ({ entity, setShowActions }: Props) => { const [isPopoverOpen, { toggle: togglePopover, off: closePopover }] = useBoolean(false); - const actionButtonTestSubject = entity.identifyingValue - ? `inventoryEntityActionsButton-${entity.identifyingValue}` + const actionButtonTestSubject = entity.entityDisplayName + ? `inventoryEntityActionsButton-${entity.entityDisplayName}` : 'inventoryEntityActionsButton'; const { getDiscoverEntitiesRedirectUrl, isEntityDefinitionLoading } = useDiscoverRedirect(entity); @@ -37,8 +37,8 @@ export const EntityActions = ({ entity, setShowActions }: Props) => { if (!isEntityDefinitionLoading) { actions.push( Date: Wed, 27 Nov 2024 03:42:52 +0100 Subject: [PATCH 09/11] Fix a bug with the formatRequest helper --- .../src/format_request.test.ts | 38 +++++++++++++++++++ .../src/format_request.ts | 20 +++++++++- .../public/lib/entity_client.ts | 15 +++----- 3 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 packages/kbn-server-route-repository-utils/src/format_request.test.ts diff --git a/packages/kbn-server-route-repository-utils/src/format_request.test.ts b/packages/kbn-server-route-repository-utils/src/format_request.test.ts new file mode 100644 index 0000000000000..093ea5ab7a20f --- /dev/null +++ b/packages/kbn-server-route-repository-utils/src/format_request.test.ts @@ -0,0 +1,38 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { formatRequest } from './format_request'; + +describe('formatRequest', () => { + it('should return the correct path if the optional or required param is provided', () => { + const pathParams = { param: 'testParam' }; + const resultOptionalEnd = formatRequest('GET /api/endpoint/{param?}', pathParams); + expect(resultOptionalEnd.pathname).toBe('/api/endpoint/testParam'); + const resultRequiredEnd = formatRequest('GET /api/endpoint/{param}', pathParams); + expect(resultRequiredEnd.pathname).toBe('/api/endpoint/testParam'); + }); + it('should return the correct path if the only the required param is provided', () => { + const resultEnd = formatRequest('GET /api/endpoint/{id}/some/{opt?}', { id: 123 }); + expect(resultEnd.pathname).toBe('/api/endpoint/123/some'); + }); + it('should return the correct path if the only an optional param is provided', () => { + const resultOptEnd = formatRequest('GET /api/endpoint/{id?}', { id: 123 }); + expect(resultOptEnd.pathname).toBe('/api/endpoint/123'); + }); + it('should return the correct path if the optional param is not provided', () => { + const pathParams = {}; + const resultEnd = formatRequest('GET /api/endpoint/{pathParamReq?}', pathParams); + expect(resultEnd.pathname).toBe('/api/endpoint'); + }); + it('should return the correct path if the optional param is not provided and the required is', () => { + const pathParams = { req: 'required' }; + const resultEnd = formatRequest('GET /api/endpoint/{req}/{op?}', pathParams); + expect(resultEnd.pathname).toBe('/api/endpoint/required'); + }); +}); diff --git a/packages/kbn-server-route-repository-utils/src/format_request.ts b/packages/kbn-server-route-repository-utils/src/format_request.ts index 7348003e3bd42..dba60492f762e 100644 --- a/packages/kbn-server-route-repository-utils/src/format_request.ts +++ b/packages/kbn-server-route-repository-utils/src/format_request.ts @@ -11,10 +11,26 @@ import { parseEndpoint } from './parse_endpoint'; export function formatRequest(endpoint: string, pathParams: Record = {}) { const { method, pathname: rawPathname, version } = parseEndpoint(endpoint); + const optionalReg = /(\/\{\w+\?\})/g; // /{param?} + const optionalMidReg = /(\/\{\w+\?\}\/)/g; // /{param?}/ + const requiredReg = /(\{\w+\})/g; // {param} + + if ((rawPathname.match(optionalMidReg) ?? []).length > 0) { + throw new Error('An optional parameter is allowed only at the end of the path'); + } + const paramsReg = /(\/{)((.+?))(\})/g; + if (Object.keys(pathParams)?.length === 0) { + const pathname = rawPathname.replace(paramsReg, ''); + return { method, pathname, version }; + } - // replace template variables with path params const pathname = Object.keys(pathParams).reduce((acc, paramName) => { - return acc.replace(`{${paramName}}`, pathParams[paramName]); + return acc + .replace(requiredReg, pathParams[paramName]) + .replace( + optionalReg, + rawPathname?.includes(`/{${paramName}?}`) ? `/${pathParams[paramName]}` : '' + ); }, rawPathname); return { method, pathname, version }; diff --git a/x-pack/plugins/entity_manager/public/lib/entity_client.ts b/x-pack/plugins/entity_manager/public/lib/entity_client.ts index f3ee4181f6087..43530b27df7f7 100644 --- a/x-pack/plugins/entity_manager/public/lib/entity_client.ts +++ b/x-pack/plugins/entity_manager/public/lib/entity_client.ts @@ -92,15 +92,12 @@ export class EntityClient { id: string ): Promise<{ definitions: EntityDefinition[] | EntityDefinitionWithState[] }> { try { - return await this.repositoryClient( - 'GET /internal/entities/definition/{id}' as unknown as keyof EntityManagerRouteRepository, - { - params: { - path: { id }, - query: { page: 1, perPage: 1 }, - }, - } - ); + return await this.repositoryClient('GET /internal/entities/definition/{id?}', { + params: { + path: { id }, + query: { page: 1, perPage: 1 }, + }, + }); } catch (err) { if (isHttpFetchError(err) && err.body?.statusCode === 403) { throw new EntityManagerUnauthorizedError(err.body.message); From 5078c5445fa5d6bc2b8664183f505d005fc76e80 Mon Sep 17 00:00:00 2001 From: Jenny Date: Wed, 27 Nov 2024 10:21:07 +0100 Subject: [PATCH 10/11] Simplify fix --- .../src/format_request.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/kbn-server-route-repository-utils/src/format_request.ts b/packages/kbn-server-route-repository-utils/src/format_request.ts index dba60492f762e..6d0b8be889659 100644 --- a/packages/kbn-server-route-repository-utils/src/format_request.ts +++ b/packages/kbn-server-route-repository-utils/src/format_request.ts @@ -13,20 +13,19 @@ export function formatRequest(endpoint: string, pathParams: Record const { method, pathname: rawPathname, version } = parseEndpoint(endpoint); const optionalReg = /(\/\{\w+\?\})/g; // /{param?} const optionalMidReg = /(\/\{\w+\?\}\/)/g; // /{param?}/ - const requiredReg = /(\{\w+\})/g; // {param} if ((rawPathname.match(optionalMidReg) ?? []).length > 0) { throw new Error('An optional parameter is allowed only at the end of the path'); } - const paramsReg = /(\/{)((.+?))(\})/g; + const optionalOrRequiredParamsReg = /(\/{)((.+?))(\})/g; if (Object.keys(pathParams)?.length === 0) { - const pathname = rawPathname.replace(paramsReg, ''); + const pathname = rawPathname.replace(optionalOrRequiredParamsReg, ''); return { method, pathname, version }; } const pathname = Object.keys(pathParams).reduce((acc, paramName) => { return acc - .replace(requiredReg, pathParams[paramName]) + .replace(`{${paramName}}`, pathParams[paramName]) .replace( optionalReg, rawPathname?.includes(`/{${paramName}?}`) ? `/${pathParams[paramName]}` : '' From f413337154803b1ebd947da154bcebaf03b64e57 Mon Sep 17 00:00:00 2001 From: Jenny Date: Wed, 27 Nov 2024 18:50:01 +0100 Subject: [PATCH 11/11] Simplify the replacement --- .../src/format_request.test.ts | 9 --------- .../src/format_request.ts | 13 +++++-------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/packages/kbn-server-route-repository-utils/src/format_request.test.ts b/packages/kbn-server-route-repository-utils/src/format_request.test.ts index 093ea5ab7a20f..e2ddcebebe68f 100644 --- a/packages/kbn-server-route-repository-utils/src/format_request.test.ts +++ b/packages/kbn-server-route-repository-utils/src/format_request.test.ts @@ -17,10 +17,6 @@ describe('formatRequest', () => { const resultRequiredEnd = formatRequest('GET /api/endpoint/{param}', pathParams); expect(resultRequiredEnd.pathname).toBe('/api/endpoint/testParam'); }); - it('should return the correct path if the only the required param is provided', () => { - const resultEnd = formatRequest('GET /api/endpoint/{id}/some/{opt?}', { id: 123 }); - expect(resultEnd.pathname).toBe('/api/endpoint/123/some'); - }); it('should return the correct path if the only an optional param is provided', () => { const resultOptEnd = formatRequest('GET /api/endpoint/{id?}', { id: 123 }); expect(resultOptEnd.pathname).toBe('/api/endpoint/123'); @@ -30,9 +26,4 @@ describe('formatRequest', () => { const resultEnd = formatRequest('GET /api/endpoint/{pathParamReq?}', pathParams); expect(resultEnd.pathname).toBe('/api/endpoint'); }); - it('should return the correct path if the optional param is not provided and the required is', () => { - const pathParams = { req: 'required' }; - const resultEnd = formatRequest('GET /api/endpoint/{req}/{op?}', pathParams); - expect(resultEnd.pathname).toBe('/api/endpoint/required'); - }); }); diff --git a/packages/kbn-server-route-repository-utils/src/format_request.ts b/packages/kbn-server-route-repository-utils/src/format_request.ts index 6d0b8be889659..291ba67cf70fd 100644 --- a/packages/kbn-server-route-repository-utils/src/format_request.ts +++ b/packages/kbn-server-route-repository-utils/src/format_request.ts @@ -12,11 +12,7 @@ import { parseEndpoint } from './parse_endpoint'; export function formatRequest(endpoint: string, pathParams: Record = {}) { const { method, pathname: rawPathname, version } = parseEndpoint(endpoint); const optionalReg = /(\/\{\w+\?\})/g; // /{param?} - const optionalMidReg = /(\/\{\w+\?\}\/)/g; // /{param?}/ - if ((rawPathname.match(optionalMidReg) ?? []).length > 0) { - throw new Error('An optional parameter is allowed only at the end of the path'); - } const optionalOrRequiredParamsReg = /(\/{)((.+?))(\})/g; if (Object.keys(pathParams)?.length === 0) { const pathname = rawPathname.replace(optionalOrRequiredParamsReg, ''); @@ -26,11 +22,12 @@ export function formatRequest(endpoint: string, pathParams: Record const pathname = Object.keys(pathParams).reduce((acc, paramName) => { return acc .replace(`{${paramName}}`, pathParams[paramName]) - .replace( - optionalReg, - rawPathname?.includes(`/{${paramName}?}`) ? `/${pathParams[paramName]}` : '' - ); + .replace(`{${paramName}?}`, pathParams[paramName]); }, rawPathname); + if ((pathname.match(optionalReg) ?? [])?.length > 0) { + throw new Error(`Missing parameters: ${pathname.match(optionalReg)}`); + } + return { method, pathname, version }; }