diff --git a/x-pack/plugins/infra/common/http_api/metadata_api.ts b/x-pack/plugins/infra/common/http_api/metadata_api.ts index 66d1d01f8175d..360923e5307a1 100644 --- a/x-pack/plugins/infra/common/http_api/metadata_api.ts +++ b/x-pack/plugins/infra/common/http_api/metadata_api.ts @@ -83,6 +83,14 @@ export const InfraMetadataInfoRT = rt.partial({ cloud: InfraMetadataCloudRT, host: InfraMetadataHostRT, agent: InfraMetadataAgentRT, + '@timestamp': rt.string, +}); + +export const InfraMetadataInfoResponseRT = rt.partial({ + cloud: InfraMetadataCloudRT, + host: InfraMetadataHostRT, + agent: InfraMetadataAgentRT, + timestamp: rt.string, }); const InfraMetadataRequiredRT = rt.type({ @@ -92,7 +100,7 @@ const InfraMetadataRequiredRT = rt.type({ }); const InfraMetadataOptionalRT = rt.partial({ - info: InfraMetadataInfoRT, + info: InfraMetadataInfoResponseRT, }); export const InfraMetadataRT = rt.intersection([InfraMetadataRequiredRT, InfraMetadataOptionalRT]); diff --git a/x-pack/plugins/infra/public/components/asset_details/components/metadata_explanation.tsx b/x-pack/plugins/infra/public/components/asset_details/components/metadata_explanation.tsx new file mode 100644 index 0000000000000..bdd77916db02f --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/components/metadata_explanation.tsx @@ -0,0 +1,102 @@ +/* + * 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 { EuiText, EuiLink } from '@elastic/eui'; +import { FormattedDate, FormattedMessage, FormattedTime } from '@kbn/i18n-react'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import { Popover } from '../tabs/common/popover'; +import { useMetadataStateProviderContext } from '../hooks/use_metadata_state'; + +const HOSTNAME_DOCS_LINK = + 'https://www.elastic.co/guide/en/ecs/current/ecs-host.html#field-host-name'; + +const MetadataExplanationTooltipContent = React.memo(() => { + const onClick = (e: React.MouseEvent) => { + e.stopPropagation(); + }; + + return ( + + + + + ), + hostName: ( + + + + ), + }} + /> + + ); +}); + +export const MetadataExplanationMessage = () => { + const { metadata, loading } = useMetadataStateProviderContext(); + + return loading ? ( + + ) : metadata?.info?.timestamp ? ( + + + + + ), + time: ( + + ), + }} + /> + + + + + + + + + ) : null; +}; diff --git a/x-pack/plugins/infra/public/components/asset_details/components/processes_explanation.tsx b/x-pack/plugins/infra/public/components/asset_details/components/processes_explanation.tsx new file mode 100644 index 0000000000000..5f1af3f9f8a26 --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/components/processes_explanation.tsx @@ -0,0 +1,111 @@ +/* + * 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 { EuiText, EuiLink } from '@elastic/eui'; +import { FormattedDate, FormattedMessage, FormattedTime } from '@kbn/i18n-react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { useDateRangeProviderContext } from '../hooks/use_date_range'; +import { Popover } from '../tabs/common/popover'; + +const DOCUMENTATION_LINK = + 'https://www.elastic.co/guide/en/observability/current/view-infrastructure-metrics.html'; +const SYSTEM_INTEGRATION_DOCS_LINK = 'https://docs.elastic.co/en/integrations/system'; + +const ProcessesExplanationTooltipContent = React.memo(() => { + const onClick = (e: React.MouseEvent) => { + e.stopPropagation(); + }; + + return ( + +

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

+

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

+
+ ); +}); + +export const ProcessesExplanationMessage = () => { + const { getDateRangeInTimestamp } = useDateRangeProviderContext(); + const dateFromRange = new Date(getDateRangeInTimestamp().to); + + return ( + + + + + ), + time: ( + + ), + }} + /> + + + + + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/common/popover.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/common/popover.tsx index 263c61d46230b..823f330e742a7 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/common/popover.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/common/popover.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { EuiPopover, EuiIcon, IconType } from '@elastic/eui'; +import { PanelPaddingSize } from '@elastic/eui'; +import { EuiPopover, EuiIcon, type IconType, type IconColor, type IconSize } from '@elastic/eui'; import { css } from '@emotion/react'; import React from 'react'; import { useBoolean } from '../../../../hooks/use_boolean'; @@ -13,20 +14,28 @@ import { useBoolean } from '../../../../hooks/use_boolean'; export const Popover = ({ children, icon, + iconColor, + iconSize, + panelPaddingSize, ...props }: { children: React.ReactNode; icon: IconType; + iconColor?: IconColor; + iconSize?: IconSize; + panelPaddingSize?: PanelPaddingSize; 'data-test-subj'?: string; }) => { const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false); return ( { } return ( - + <> + + +
+ ); }; diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metadata_summary/metadata_header.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metadata_summary/metadata_header.tsx index e19d261094d8f..7132587588f24 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metadata_summary/metadata_header.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metadata_summary/metadata_header.tsx @@ -56,10 +56,7 @@ export const MetadataHeader = ({ metadataValue }: MetadataSummaryProps) => { {columnTitles[metadataValue.field as MetadataFields]} - + {metadataValue.tooltipLink ? ( + <> + + + + + + + + + + + + + + + + {(isCompactView ? metadataData(metadata?.info) @@ -98,21 +130,6 @@ export const MetadataSummaryList = ({ ) )} - - - - - - + ); }; diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_grid.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_grid.tsx index 9b5953f9292dd..9b4036c1cdd13 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_grid.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_grid.tsx @@ -121,7 +121,7 @@ const MetricsSectionTitle = () => { - + diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.tsx index f0568332328dc..2156ca8c7555d 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.tsx @@ -19,6 +19,7 @@ import { EuiFlexItem, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiLoadingSpinner } from '@elastic/eui'; import { parseSearchString } from './parse_search_string'; import { ProcessesTable } from './processes_table'; import { STATE_NAMES } from './states'; @@ -31,6 +32,7 @@ import { import { getFieldByType } from '../../../../../common/inventory_models'; import { useAssetDetailsRenderPropsContext } from '../../hooks/use_asset_details_render_props'; import { useDateRangeProviderContext } from '../../hooks/use_date_range'; +import { ProcessesExplanationMessage } from '../../components/processes_explanation'; import { useAssetDetailsUrlState } from '../../hooks/use_asset_details_url_state'; const options = Object.entries(STATE_NAMES).map(([value, view]: [string, string]) => ({ @@ -130,6 +132,11 @@ export const Processes = () => { + {loading ? ( + + ) : ( + !error && (response?.processList ?? []).length > 0 && + )} { id, name, features: [...metricFeatures, ...cloudMetricsFeatures], - info, + info: { + ...info, + timestamp: info['@timestamp'], + }, }), }); } diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_node_info.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_node_info.ts index bc93d1f539e2a..5470efcb1fb47 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_node_info.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_node_info.ts @@ -59,7 +59,7 @@ export const getNodeInfo = async ( index: sourceConfiguration.metricAlias, body: { size: 1, - _source: ['host.*', 'cloud.*', 'agent.*'], + _source: ['host.*', 'cloud.*', 'agent.*', TIMESTAMP_FIELD], sort: [{ [TIMESTAMP_FIELD]: 'desc' }], query: { bool: { diff --git a/x-pack/test/api_integration/apis/metrics_ui/metadata.ts b/x-pack/test/api_integration/apis/metrics_ui/metadata.ts index 5cdca5a369edf..9a7f41e1d2b67 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metadata.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metadata.ts @@ -99,38 +99,42 @@ export default function ({ getService }: FtrProviderContext) { if (metadata) { expect(metadata.features.length).to.be(58); expect(metadata.name).to.equal('gke-observability-8--observability-8--bc1afd95-f0zc'); - expect(metadata.info).to.eql({ - cloud: { - availability_zone: 'europe-west1-c', - instance: { - name: 'gke-observability-8--observability-8--bc1afd95-f0zc', - id: '6200309808276807579', - }, - provider: 'gcp', - machine: { type: 'n1-standard-4' }, - project: { id: 'elastic-observability' }, - }, - agent: { - hostname: 'gke-observability-8--observability-8--bc1afd95-f0zc', - id: 'c91c0d2b-6483-46bb-9731-f06afd32bb59', - ephemeral_id: '7cb259b1-795c-4c76-beaf-2eb8f18f5b02', - type: 'metricbeat', - version: '8.0.0', - }, - host: { - hostname: 'gke-observability-8--observability-8--bc1afd95-f0zc', - os: { - kernel: '4.14.127+', - codename: 'Core', - name: 'CentOS Linux', - family: 'redhat', - version: '7 (Core)', - platform: 'centos', - }, - containerized: false, + expect(new Date(metadata.info?.timestamp ?? '')?.getTime()).to.be.above( + timeRange800withAws.from + ); + expect(new Date(metadata.info?.timestamp ?? '')?.getTime()).to.be.below( + timeRange800withAws.to + ); + expect(metadata.info?.cloud).to.eql({ + availability_zone: 'europe-west1-c', + instance: { name: 'gke-observability-8--observability-8--bc1afd95-f0zc', - architecture: 'x86_64', + id: '6200309808276807579', + }, + provider: 'gcp', + machine: { type: 'n1-standard-4' }, + project: { id: 'elastic-observability' }, + }); + expect(metadata.info?.agent).to.eql({ + hostname: 'gke-observability-8--observability-8--bc1afd95-f0zc', + id: 'c91c0d2b-6483-46bb-9731-f06afd32bb59', + ephemeral_id: '7cb259b1-795c-4c76-beaf-2eb8f18f5b02', + type: 'metricbeat', + version: '8.0.0', + }); + expect(metadata.info?.host).to.eql({ + hostname: 'gke-observability-8--observability-8--bc1afd95-f0zc', + os: { + kernel: '4.14.127+', + codename: 'Core', + name: 'CentOS Linux', + family: 'redhat', + version: '7 (Core)', + platform: 'centos', }, + containerized: false, + name: 'gke-observability-8--observability-8--bc1afd95-f0zc', + architecture: 'x86_64', }); } else { throw new Error('Metadata should never be empty'); @@ -148,38 +152,42 @@ export default function ({ getService }: FtrProviderContext) { expect(metadata.features.length).to.be(19); expect(metadata.features.some((f) => f.name === 'aws.ec2')).to.be(true); expect(metadata.name).to.equal('ip-172-31-47-9.us-east-2.compute.internal'); - expect(metadata.info).to.eql({ - cloud: { - availability_zone: 'us-east-2c', - image: { id: 'ami-0d8f6eb4f641ef691' }, - instance: { id: 'i-011454f72559c510b' }, - provider: 'aws', - machine: { type: 't2.micro' }, - region: 'us-east-2', - account: { id: '015351775590' }, - }, - agent: { - hostname: 'ip-172-31-47-9.us-east-2.compute.internal', - id: 'd0943b36-d0d3-426d-892b-7d79c071b44b', - ephemeral_id: '64c94244-88b8-4a37-adc0-30428fefaf53', - type: 'metricbeat', - version: '8.0.0', - }, - host: { - hostname: 'ip-172-31-47-9.us-east-2.compute.internal', - os: { - kernel: '4.14.123-111.109.amzn2.x86_64', - codename: 'Karoo', - name: 'Amazon Linux', - family: 'redhat', - version: '2', - platform: 'amzn', - }, - containerized: false, - name: 'ip-172-31-47-9.us-east-2.compute.internal', - id: 'ded64cbff86f478990a3dfbb63a8d238', - architecture: 'x86_64', + expect(new Date(metadata.info?.timestamp ?? '')?.getTime()).to.be.above( + timeRange800withAws.from + ); + expect(new Date(metadata.info?.timestamp ?? '')?.getTime()).to.be.below( + timeRange800withAws.to + ); + expect(metadata.info?.cloud).to.eql({ + availability_zone: 'us-east-2c', + image: { id: 'ami-0d8f6eb4f641ef691' }, + instance: { id: 'i-011454f72559c510b' }, + provider: 'aws', + machine: { type: 't2.micro' }, + region: 'us-east-2', + account: { id: '015351775590' }, + }); + expect(metadata.info?.agent).to.eql({ + hostname: 'ip-172-31-47-9.us-east-2.compute.internal', + id: 'd0943b36-d0d3-426d-892b-7d79c071b44b', + ephemeral_id: '64c94244-88b8-4a37-adc0-30428fefaf53', + type: 'metricbeat', + version: '8.0.0', + }); + expect(metadata.info?.host).to.eql({ + hostname: 'ip-172-31-47-9.us-east-2.compute.internal', + os: { + kernel: '4.14.123-111.109.amzn2.x86_64', + codename: 'Karoo', + name: 'Amazon Linux', + family: 'redhat', + version: '2', + platform: 'amzn', }, + containerized: false, + name: 'ip-172-31-47-9.us-east-2.compute.internal', + id: 'ded64cbff86f478990a3dfbb63a8d238', + architecture: 'x86_64', }); } else { throw new Error('Metadata should never be empty'); @@ -197,43 +205,47 @@ export default function ({ getService }: FtrProviderContext) { expect(metadata.features.length).to.be(29); // With this data set the `kubernetes.pod.name` fields have been removed. expect(metadata.name).to.equal('fluentd-gcp-v3.2.0-np7vw'); - expect(metadata.info).to.eql({ - cloud: { - instance: { - id: '6613144177892233360', - name: 'gke-observability-8--observability-8--bc1afd95-ngmh', - }, - provider: 'gcp', - availability_zone: 'europe-west1-c', - machine: { - type: 'n1-standard-4', - }, - project: { - id: 'elastic-observability', - }, + expect(new Date(metadata.info?.timestamp ?? '')?.getTime()).to.be.above( + timeRange800withAws.from + ); + expect(new Date(metadata.info?.timestamp ?? '')?.getTime()).to.be.below( + timeRange800withAws.to + ); + expect(metadata.info?.cloud).to.eql({ + instance: { + id: '6613144177892233360', + name: 'gke-observability-8--observability-8--bc1afd95-ngmh', }, - agent: { - hostname: 'gke-observability-8--observability-8--bc1afd95-ngmh', - id: '66dc19e6-da36-49d2-9471-2c9475503178', - ephemeral_id: 'a0c3a9ff-470a-41a0-bf43-d1af6b7a3b5b', - type: 'metricbeat', - version: '8.0.0', + provider: 'gcp', + availability_zone: 'europe-west1-c', + machine: { + type: 'n1-standard-4', }, - host: { - hostname: 'gke-observability-8--observability-8--bc1afd95-ngmh', - name: 'gke-observability-8--observability-8--bc1afd95-ngmh', - os: { - codename: 'Core', - family: 'redhat', - kernel: '4.14.127+', - name: 'CentOS Linux', - platform: 'centos', - version: '7 (Core)', - }, - architecture: 'x86_64', - containerized: false, + project: { + id: 'elastic-observability', }, }); + expect(metadata.info?.agent).to.eql({ + hostname: 'gke-observability-8--observability-8--bc1afd95-ngmh', + id: '66dc19e6-da36-49d2-9471-2c9475503178', + ephemeral_id: 'a0c3a9ff-470a-41a0-bf43-d1af6b7a3b5b', + type: 'metricbeat', + version: '8.0.0', + }); + expect(metadata.info?.host).to.eql({ + hostname: 'gke-observability-8--observability-8--bc1afd95-ngmh', + name: 'gke-observability-8--observability-8--bc1afd95-ngmh', + os: { + codename: 'Core', + family: 'redhat', + kernel: '4.14.127+', + name: 'CentOS Linux', + platform: 'centos', + version: '7 (Core)', + }, + architecture: 'x86_64', + containerized: false, + }); } else { throw new Error('Metadata should never be empty'); } @@ -248,45 +260,49 @@ export default function ({ getService }: FtrProviderContext) { }); if (metadata) { expect(metadata.features.length).to.be(26); + expect(new Date(metadata.info?.timestamp ?? '')?.getTime()).to.be.above( + timeRange800withAws.from + ); + expect(new Date(metadata.info?.timestamp ?? '')?.getTime()).to.be.below( + timeRange800withAws.to + ); expect(metadata.name).to.equal( 'k8s_prometheus-to-sd-exporter_fluentd-gcp-v3.2.0-w68r5_kube-system_26950cde-9aed-11e9-9a96-42010a84004d_0' ); - expect(metadata.info).to.eql({ - cloud: { - instance: { - id: '4039094952262994102', - name: 'gke-observability-8--observability-8--bc1afd95-nhhw', - }, - provider: 'gcp', - availability_zone: 'europe-west1-c', - machine: { - type: 'n1-standard-4', - }, - project: { - id: 'elastic-observability', - }, + expect(metadata.info?.cloud).to.eql({ + instance: { + id: '4039094952262994102', + name: 'gke-observability-8--observability-8--bc1afd95-nhhw', }, - agent: { - hostname: 'gke-observability-8--observability-8--bc1afd95-nhhw', - id: 'c58a514c-e971-4590-8206-385400e184dd', - ephemeral_id: 'e9d46cb0-2e89-469d-bd3b-6f32d7c96cc0', - type: 'metricbeat', - version: '8.0.0', + provider: 'gcp', + availability_zone: 'europe-west1-c', + machine: { + type: 'n1-standard-4', }, - host: { - hostname: 'gke-observability-8--observability-8--bc1afd95-nhhw', - name: 'gke-observability-8--observability-8--bc1afd95-nhhw', - os: { - codename: 'Core', - family: 'redhat', - kernel: '4.14.127+', - name: 'CentOS Linux', - platform: 'centos', - version: '7 (Core)', - }, - architecture: 'x86_64', - containerized: false, + project: { + id: 'elastic-observability', + }, + }); + expect(metadata.info?.agent).to.eql({ + hostname: 'gke-observability-8--observability-8--bc1afd95-nhhw', + id: 'c58a514c-e971-4590-8206-385400e184dd', + ephemeral_id: 'e9d46cb0-2e89-469d-bd3b-6f32d7c96cc0', + type: 'metricbeat', + version: '8.0.0', + }); + expect(metadata.info?.host).to.eql({ + hostname: 'gke-observability-8--observability-8--bc1afd95-nhhw', + name: 'gke-observability-8--observability-8--bc1afd95-nhhw', + os: { + codename: 'Core', + family: 'redhat', + kernel: '4.14.127+', + name: 'CentOS Linux', + platform: 'centos', + version: '7 (Core)', }, + architecture: 'x86_64', + containerized: false, }); } else { throw new Error('Metadata should never be empty');