From 3628fda5882ca7697c2b8c4c1e8fbdc107c965fe Mon Sep 17 00:00:00 2001 From: jennypavlova Date: Fri, 23 Aug 2024 19:03:28 +0200 Subject: [PATCH] [Infra] Provide troubleshooting information on the host details page (#191104) Closes #190523 ## Summary This PR provides troubleshooting information in the services section on the host details page. ## Testing Go to the hosts details page / host flyout - using a host without services monitored with system integration ![image](https://github.com/user-attachments/assets/5226e4a1-7b19-4a22-b11d-2aae55f543e3) - using a host without services not monitored with system integration ( only detected by APM ) ![image](https://github.com/user-attachments/assets/f63513bd-d3f9-41eb-97a6-0011b2d11673) - icon next to the title (same as the table) image For hosts with services, nothing changes (the service is shown): image --- .../infra/common/http_api/metadata_api.ts | 1 + .../components/asset_details/constants.ts | 2 + .../asset_details/header/flyout_header.tsx | 35 +++++++++- .../header/page_title_with_popover.tsx | 66 +++++++++++++++++++ .../asset_details/tabs/metadata/utils.test.ts | 11 ++++ .../asset_details/tabs/overview/services.tsx | 39 +++++++++-- .../asset_details/template/flyout.tsx | 38 +++++------ .../asset_details/template/page.tsx | 12 +++- .../metrics/hosts/hooks/use_hosts_table.tsx | 3 +- .../infra/lib/host/get_filtered_hosts.ts | 14 +++- .../infra/server/routes/metadata/index.ts | 34 ++++++---- .../metadata/lib/get_metric_metadata.ts | 27 +++++++- .../observability/infra/metadata.ts | 1 + 13 files changed, 236 insertions(+), 47 deletions(-) create mode 100644 x-pack/plugins/observability_solution/infra/public/components/asset_details/header/page_title_with_popover.tsx diff --git a/x-pack/plugins/observability_solution/infra/common/http_api/metadata_api.ts b/x-pack/plugins/observability_solution/infra/common/http_api/metadata_api.ts index f2f5bc2c07dbe..ce2bfdfa25dcb 100644 --- a/x-pack/plugins/observability_solution/infra/common/http_api/metadata_api.ts +++ b/x-pack/plugins/observability_solution/infra/common/http_api/metadata_api.ts @@ -111,6 +111,7 @@ const InfraMetadataRequiredRT = rt.type({ const InfraMetadataOptionalRT = rt.partial({ info: InfraMetadataInfoResponseRT, + hasSystemIntegration: rt.boolean, }); export const InfraMetadataRT = rt.intersection([InfraMetadataRequiredRT, InfraMetadataOptionalRT]); diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/constants.ts b/x-pack/plugins/observability_solution/infra/public/components/asset_details/constants.ts index 770d70cf83cc2..3fed812c9d45a 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/constants.ts +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/constants.ts @@ -24,3 +24,5 @@ export const INTEGRATIONS = { export const DOCKER_METRIC_TYPES: DockerContainerMetrics[] = ['cpu', 'memory', 'network', 'disk']; export const KUBERNETES_METRIC_TYPES: KubernetesContainerMetrics[] = ['cpu', 'memory']; + +export const APM_HOST_TROUBLESHOOTING_LINK = 'https://ela.st/host-troubleshooting'; diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/header/flyout_header.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/header/flyout_header.tsx index 05f96a331a07c..8a410d4518352 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/header/flyout_header.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/header/flyout_header.tsx @@ -16,11 +16,27 @@ import { useEuiTheme, useEuiMinBreakpoint, type EuiPageHeaderProps, + EuiLoadingSpinner, } from '@elastic/eui'; import { css } from '@emotion/react'; -type Props = Pick; +import type { InventoryItemType } from '@kbn/metrics-data-access-plugin/common/inventory_models/types'; +import { PageTitleWithPopover } from './page_title_with_popover'; +type Props = Pick & { + hasSystemIntegration: boolean; + assetType: InventoryItemType; + metadataLoading: boolean; + loading: boolean; +}; -export const FlyoutHeader = ({ title, tabs = [], rightSideItems = [] }: Props) => { +export const FlyoutHeader = ({ + title, + tabs = [], + rightSideItems = [], + hasSystemIntegration, + assetType, + metadataLoading, + loading, +}: Props) => { const { euiTheme } = useEuiTheme(); return ( @@ -39,7 +55,20 @@ export const FlyoutHeader = ({ title, tabs = [], rightSideItems = [] }: Props) = `} > -

{title}

+ {metadataLoading || loading ? ( + + ) : ( +

+ {assetType === 'host' ? ( + + ) : ( + title + )} +

+ )}
{ + return !hasSystemMetrics ? ( + + {name} + + + +

+ + + + ), + }} + /> +

+

+ + + +

+
+
+
+
+ ) : ( + <>{name} + ); +}; diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metadata/utils.test.ts b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metadata/utils.test.ts index 04bfdf9945ff3..b586a0909ce03 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metadata/utils.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metadata/utils.test.ts @@ -13,6 +13,7 @@ describe('#getAllFields', () => { architecture: 'x86_64', containerized: false, hostname: 'host1', + hasSystemIntegration: true, ip: [ '10.10.10.10', '10.10.10.10', @@ -63,6 +64,7 @@ describe('#getAllFields', () => { const result: InfraMetadata = { id: 'host1', name: 'host1', + hasSystemIntegration: true, features: [ { name: 'system.core', @@ -77,6 +79,7 @@ describe('#getAllFields', () => { const result: InfraMetadata = { id: 'host1', name: 'host1', + hasSystemIntegration: true, features: [ { name: 'system.core', @@ -96,6 +99,7 @@ describe('#getAllFields', () => { const result: InfraMetadata = { id: 'host1', name: 'host1', + hasSystemIntegration: true, features: [ { name: 'system.core', @@ -117,6 +121,7 @@ describe('#getAllFields', () => { const result = { id: 'host1', name: 'host1', + hasSystemIntegration: true, features: [ { name: 'system.core', @@ -163,6 +168,7 @@ describe('#getAllFields', () => { const result: InfraMetadata = { id: 'host1', name: 'host1', + hasSystemIntegration: true, features: [ { name: 'system.core', @@ -231,6 +237,7 @@ describe('#getAllFields', () => { { name: 'host.architecture', value: 'x86_64' }, { name: 'host.containerized', value: 'false' }, { name: 'host.hostname', value: 'host1' }, + { name: 'host.hasSystemIntegration', value: 'true' }, { name: 'host.ip', value: [ @@ -266,6 +273,7 @@ describe('#getAllFields', () => { const result: InfraMetadata = { id: 'host1', name: 'host1', + hasSystemIntegration: true, features: [ { name: 'system.core', @@ -282,6 +290,7 @@ describe('#getAllFields', () => { { name: 'host.architecture', value: 'x86_64' }, { name: 'host.containerized', value: 'false' }, { name: 'host.hostname', value: 'host1' }, + { name: 'host.hasSystemIntegration', value: 'true' }, { name: 'host.ip', value: [ @@ -349,6 +358,7 @@ describe('#getAllFields', () => { const result: InfraMetadata = { id: 'host1', name: 'host1', + hasSystemIntegration: true, features: [ { name: 'system.core', @@ -365,6 +375,7 @@ describe('#getAllFields', () => { { name: 'host.architecture', value: 'x86_64' }, { name: 'host.containerized', value: 'false' }, { name: 'host.hostname', value: 'host1' }, + { name: 'host.hasSystemIntegration', value: 'true' }, { name: 'host.ip', value: [ diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/services.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/services.tsx index ec633420ce1a7..0e504413abbcf 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/services.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/services.tsx @@ -17,11 +17,12 @@ import { Section } from '../../components/section'; import { ServicesSectionTitle } from './section_titles'; import { HOST_NAME_FIELD } from '../../../../../common/constants'; import { LinkToApmServices } from '../../links'; -import { APM_HOST_FILTER_FIELD } from '../../constants'; +import { APM_HOST_FILTER_FIELD, APM_HOST_TROUBLESHOOTING_LINK } from '../../constants'; import { LinkToApmService } from '../../links/link_to_apm_service'; import { useKibanaEnvironmentContext } from '../../../../hooks/use_kibana'; import { useRequestObservable } from '../../hooks/use_request_observable'; import { useTabSwitcherContext } from '../../hooks/use_tab_switcher'; +import { useMetadataStateContext } from '../../hooks/use_metadata_state'; export const ServicesContent = ({ hostName, @@ -33,6 +34,7 @@ export const ServicesContent = ({ const { isServerlessEnv } = useKibanaEnvironmentContext(); const { request$ } = useRequestObservable(); const { isActiveTab } = useTabSwitcherContext(); + const { metadata, loading: metadataLoading } = useMetadataStateContext(); const linkProps = useLinkProps({ app: 'home', @@ -92,7 +94,7 @@ export const ServicesContent = ({ defaultMessage: 'An error occurred while fetching services.', })} - ) : isPending(status) ? ( + ) : isPending(status) || metadataLoading ? ( ) : hasServices ? ( ))} - ) : ( + ) : metadata?.hasSystemIntegration ? (

), }} - /> + />{' '} + + + +

+ ) : ( +

+ {' '} + + +

)} diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/template/flyout.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/template/flyout.tsx index 898ce873e49ec..223ac061ea118 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/template/flyout.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/template/flyout.tsx @@ -6,11 +6,9 @@ */ import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import React, { useCallback } from 'react'; import useEffectOnce from 'react-use/lib/useEffectOnce'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; -import { InfraLoadingPanel } from '../../loading'; import { ASSET_DETAILS_FLYOUT_COMPONENT_NAME } from '../constants'; import { Content } from '../content/content'; import { FlyoutHeader } from '../header/flyout_header'; @@ -19,6 +17,7 @@ import { useAssetDetailsUrlState } from '../hooks/use_asset_details_url_state'; import { usePageHeader } from '../hooks/use_page_header'; import { useTabSwitcherContext } from '../hooks/use_tab_switcher'; import type { ContentTemplateProps } from '../types'; +import { useMetadataStateContext } from '../hooks/use_metadata_state'; export const Flyout = ({ tabs = [], @@ -32,6 +31,7 @@ export const Flyout = ({ const { services: { telemetry }, } = useKibanaContextForPlugin(); + const { metadata, loading: metadataLoading } = useMetadataStateContext(); useEffectOnce(() => { telemetry.reportAssetDetailsFlyoutViewed({ @@ -53,24 +53,22 @@ export const Flyout = ({ data-component-name={ASSET_DETAILS_FLYOUT_COMPONENT_NAME} data-asset-type={asset.type} > - {loading ? ( - - ) : ( - <> - - - - - - - - )} + <> + + + + + + + ); }; diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/template/page.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/template/page.tsx index 346acb6d8a164..3c43984e2ecb4 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/template/page.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/template/page.tsx @@ -22,6 +22,7 @@ import { ContentTemplateProps } from '../types'; import { getIntegrationsAvailable } from '../utils'; import { InfraPageTemplate } from '../../shared/templates/infra_page_template'; import { OnboardingFlow } from '../../shared/templates/no_data_config'; +import { PageTitleWithPopover } from '../header/page_title_with_popover'; const DATA_AVAILABILITY_PER_TYPE: Partial> = { host: [SYSTEM_INTEGRATION], @@ -83,7 +84,16 @@ export const Page = ({ tabs = [], links = [] }: ContentTemplateProps) => { onboardingFlow={asset.type === 'host' ? OnboardingFlow.Hosts : OnboardingFlow.Infra} dataAvailabilityModules={DATA_AVAILABILITY_PER_TYPE[asset.type] || undefined} pageHeader={{ - pageTitle: loading ? : asset.name, + pageTitle: loading ? ( + + ) : asset.type === 'host' ? ( + + ) : ( + asset.name + ), tabs: tabEntries, rightSideItems, breadcrumbs: headerBreadcrumbs, diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx index 10f7b6028a384..4bdb72e625a89 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx @@ -22,6 +22,7 @@ import { findInventoryModel } from '@kbn/metrics-data-access-plugin/common'; import { EuiToolTip } from '@elastic/eui'; import { EuiBadge } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { APM_HOST_TROUBLESHOOTING_LINK } from '../../../../components/asset_details/constants'; import { Popover } from '../../../../components/asset_details/tabs/common/popover'; import { HOST_NAME_FIELD } from '../../../../../common/constants'; import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; @@ -318,7 +319,7 @@ export const useHostsTable = () => {

{ const soClient = (await requestContext.core).savedObjects.client; const { configuration } = await libs.sources.getSourceConfiguration(soClient, sourceId); + const infraMetricsClient = await getInfraMetricsClient({ + request, + libs, + context: requestContext, + }); const metricsMetadata = await getMetricMetadata( framework, requestContext, configuration, nodeId, nodeType, - timeRange + timeRange, + infraMetricsClient ); const metricFeatures = pickFeatureName(metricsMetadata.buckets).map(nameToFeature('metrics')); @@ -78,17 +85,22 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => { ); const id = metricsMetadata.id; const name = metricsMetadata.name || id; - return response.ok({ - body: InfraMetadataRT.encode({ - id, - name, - features: [...metricFeatures, ...cloudMetricsFeatures], - info: { - ...info, - timestamp: info['@timestamp'], - }, - }), + + const responseBody = InfraMetadataRT.encode({ + id, + name, + features: [...metricFeatures, ...cloudMetricsFeatures], + info: { + ...info, + timestamp: info['@timestamp'], + }, }); + if (nodeType === 'host') { + const hasSystemIntegration = metricsMetadata?.hasSystemIntegration; + return response.ok({ body: { ...responseBody, hasSystemIntegration } }); + } + + return response.ok({ body: responseBody }); } ); }; diff --git a/x-pack/plugins/observability_solution/infra/server/routes/metadata/lib/get_metric_metadata.ts b/x-pack/plugins/observability_solution/infra/server/routes/metadata/lib/get_metric_metadata.ts index f12cce92e60b5..ef5afc9f5b20a 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/metadata/lib/get_metric_metadata.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/metadata/lib/get_metric_metadata.ts @@ -4,10 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { get } from 'lodash'; import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; import { InventoryItemType } from '@kbn/metrics-data-access-plugin/common'; +import type { InfraMetricsClient } from '../../../lib/helpers/get_infra_metrics_client'; import type { InfraPluginRequestHandlerContext } from '../../../types'; import { InfraMetadataAggregationBucket, @@ -16,11 +16,13 @@ import { import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { InfraSourceConfiguration } from '../../../lib/sources'; import { TIMESTAMP_FIELD } from '../../../../common/constants'; +import { getHasDataFromSystemIntegration } from '../../infra/lib/host/get_filtered_hosts'; export interface InfraMetricsAdapterResponse { id: string; name?: string; buckets: InfraMetadataAggregationBucket[]; + hasSystemIntegration?: boolean; } export const getMetricMetadata = async ( @@ -29,7 +31,8 @@ export const getMetricMetadata = async ( sourceConfiguration: InfraSourceConfiguration, nodeId: string, nodeType: InventoryItemType, - timeRange: { from: number; to: number } + timeRange: { from: number; to: number }, + infraMetricsClient: InfraMetricsClient ): Promise => { const fields = findInventoryFields(nodeType); const metricQuery = { @@ -87,9 +90,27 @@ export const getMetricMetadata = async ( ? response.aggregations.metrics.buckets : []; - return { + const res = { id: nodeId, name: get(response, ['aggregations', 'nodeName', 'buckets', 0, 'key'], nodeId), buckets, }; + + if (nodeType === 'host') { + const hasSystemIntegration = await getHasDataFromSystemIntegration({ + infraMetricsClient, + from: timeRange.from, + to: timeRange.to, + query: { + match: { [fields.id]: nodeId }, + }, + }); + + return { + hasSystemIntegration, + ...res, + }; + } + + return res; }; diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/infra/metadata.ts b/x-pack/test_serverless/api_integration/test_suites/observability/infra/metadata.ts index c38b89b32017d..c50fe1bcb4928 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/infra/metadata.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/infra/metadata.ts @@ -65,6 +65,7 @@ export default function ({ getService }: FtrProviderContext) { if (metadata) { expect(metadata.features.length).to.be(4); expect(metadata.name).to.equal('serverless-host'); + expect(metadata.hasSystemIntegration).to.equal(true); expect(new Date(metadata.info?.timestamp ?? '')?.getTime()).to.be.above(timeRange.from); expect(new Date(metadata.info?.timestamp ?? '')?.getTime()).to.be.below(timeRange.to); expect(metadata.info?.agent).to.eql({