[Infra] Add metrics tab to the container views (#186118)

Closes #180799 

## Summary

This PR adds the metrics tab to the container views - for now, the
charts are the same as in the overview tab:
- Docker container 4 metrics - 4 charts

![image](3e911d27-c268-4a36-8fd6-c35e989fd34b)

- Kubernetes container 2 metrics - 2 charts 
<img width="1650" alt="image"
src="d626922c-2972-4858-abe5-9e1271bf2acd">

### Testing 
- Select Docker Containers from the inventory "Show" drop-down menu
  - Click on the metrics tab - the charts should be visible
- Select a Docker container and click on "See all" for any metric inside
the Metrics section
- The metrics tab should be open and the correct metric in the left menu
should be selected
- Select a Kubernetes container and click on "See all" for any metric
inside the Metrics section
- The metrics tab should be open and the correct metric in the left menu
should be selected



684b4b60-6457-44aa-a1f2-ca0340049684

- Click on open as page link inside the flyout and the same steps should
be checked for the page view (both Docker & Kubernetes)



2f9e65be-e22d-4b38-9889-d997de20c081
This commit is contained in:
jennypavlova 2024-06-13 16:56:55 +02:00 committed by GitHub
parent 1150b086c1
commit 4093f4cfbc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 180 additions and 26 deletions

View file

@ -111,8 +111,14 @@ export const hostDetailsTabs: Tab[] = [
export const hostDetailsFlyoutTabs: Tab[] = [...hostDetailsTabs, linkToApmTab];
// The profiling tab would be added in next iteration
export const containerDetailsTabs: Tab[] = [overviewTab, metadataTab, logsTab];
export const containerDetailsFlyoutTabs: Tab[] = [overviewTab, metadataTab, logsTab, linkToApmTab];
export const containerDetailsTabs: Tab[] = [overviewTab, metadataTab, metricsTab, logsTab];
export const containerDetailsFlyoutTabs: Tab[] = [
overviewTab,
metadataTab,
metricsTab,
logsTab,
linkToApmTab,
];
export const getAssetDetailsTabs = (type: string): Tab[] => {
switch (type) {

View file

@ -9,7 +9,7 @@ import React from 'react';
import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
import { css, cx } from '@emotion/css';
import { EuiText, EuiLink } from '@elastic/eui';
import { EuiText, EuiLink, EuiButtonEmpty } from '@elastic/eui';
import { useDockerContainerPageViewMetricsCharts } from '../hooks/use_container_metrics_charts';
import { Section } from '../components/section';
import { ChartsGrid } from '../charts_grid/charts_grid';
@ -26,7 +26,7 @@ interface Props extends MetricsChartsFields {
const FRAGMENT_BASE = 'key-metrics';
export const DockerCharts = React.forwardRef<HTMLDivElement, Props>(
({ assetId, dataView, dateRange, metric }, ref) => {
({ assetId, dataView, dateRange, metric, onShowAll }, ref) => {
const { charts } = useDockerContainerPageViewMetricsCharts({
metric,
metricsDataViewId: dataView?.id,
@ -67,8 +67,25 @@ export const DockerCharts = React.forwardRef<HTMLDivElement, Props>(
/>
}
data-test-subj={`infraAssetDetailsDockerChartsSection${metric}`}
id="dockerContainerCharts"
id={metric}
ref={ref}
extraAction={
onShowAll ? (
<EuiButtonEmpty
data-test-subj="infraAssetDetailsHostChartsShowAllButton"
onClick={() => onShowAll(metric)}
size="xs"
flush="both"
iconSide="right"
iconType="sortRight"
>
<FormattedMessage
id="xpack.infra.assetDetails.charts.host.showAllButton"
defaultMessage="Show all"
/>
</EuiButtonEmpty>
) : null
}
>
<ChartsGrid columns={2}>
{charts.map((chart) => (

View file

@ -19,7 +19,7 @@ import { Chart } from './chart';
import { useIntegrationCheck } from '../hooks/use_integration_check';
import { useK8sContainerPageViewMetricsCharts } from '../hooks/use_container_metrics_charts';
import { CONTAINER_METRICS_DOC_HREF } from '../../../common/visualizations/constants';
import { ContainerMetricTypes, MetricsChartsFields } from './types';
import { KubernetesContainerMetrics, MetricsChartsFields } from './types';
const FRAGMENT_BASE = 'key-metrics';
@ -79,8 +79,8 @@ export const KubernetesNodeCharts = React.forwardRef<HTMLDivElement, MetricsChar
export const KubernetesContainerCharts = React.forwardRef<
HTMLDivElement,
MetricsChartsFields & { metric: ContainerMetricTypes }
>(({ assetId, dataView, dateRange, metric }, ref) => {
MetricsChartsFields & { metric: KubernetesContainerMetrics }
>(({ assetId, dataView, dateRange, metric, onShowAll }, ref) => {
const { charts } = useK8sContainerPageViewMetricsCharts({
metric,
metricsDataViewId: dataView?.id,
@ -121,9 +121,26 @@ export const KubernetesContainerCharts = React.forwardRef<
}
/>
}
data-test-subj="infraAssetDetailsK8ContainerChartsSection"
id="k8sContainerCharts"
data-test-subj={`infraAssetDetailsK8ContainerChartsSection${metric}`}
id={metric}
ref={ref}
extraAction={
onShowAll ? (
<EuiButtonEmpty
data-test-subj="infraAssetDetailsKubernetesChartsShowAllButton"
onClick={() => onShowAll(metric)}
size="xs"
flush="both"
iconSide="right"
iconType="sortRight"
>
<FormattedMessage
id="xpack.infra.assetDetails.charts.kubernetes.showAllButton"
defaultMessage="Show all"
/>
</EuiButtonEmpty>
) : null
}
>
<ChartsGrid columns={2}>
{charts.map((chart) => (

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import type { DockerContainerMetrics, KubernetesContainerMetrics } from './charts/types';
import { INTEGRATION_NAME, ASSET_DETAILS_ASSET_TYPE } from './types';
export const ASSET_DETAILS_FLYOUT_COMPONENT_NAME = 'infraAssetDetailsFlyout';
@ -25,3 +26,6 @@ export const INTEGRATIONS = {
[INTEGRATION_NAME.kubernetesContainer]: 'kubernetes.container',
[INTEGRATION_NAME.docker]: 'docker',
};
export const DOCKER_METRIC_TYPES: DockerContainerMetrics[] = ['cpu', 'memory', 'network', 'disk'];
export const KUBERNETES_METRIC_TYPES: KubernetesContainerMetrics[] = ['cpu', 'memory'];

View file

@ -0,0 +1,60 @@
/*
* 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, { useRef } from 'react';
import { useDatePickerContext } from '../../hooks/use_date_picker';
import { useAssetDetailsRenderPropsContext } from '../../hooks/use_asset_details_render_props';
import { useDataViewsContext } from '../../hooks/use_data_views';
import { useIntersectingState } from '../../hooks/use_intersecting_state';
import { MetricsTemplate } from './metrics_template';
import { DockerCharts, KubernetesContainerCharts } from '../../charts';
import { DOCKER_METRIC_TYPES, INTEGRATIONS, KUBERNETES_METRIC_TYPES } from '../../constants';
import { useIntegrationCheck } from '../../hooks/use_integration_check';
export const ContainerMetrics = () => {
const ref = useRef<HTMLDivElement>(null);
const { dateRange } = useDatePickerContext();
const { asset } = useAssetDetailsRenderPropsContext();
const { metrics } = useDataViewsContext();
const state = useIntersectingState(ref, { dateRange });
const isDockerContainer = useIntegrationCheck({ dependsOn: INTEGRATIONS.docker });
const isKubernetesContainer = useIntegrationCheck({
dependsOn: INTEGRATIONS.kubernetesContainer,
});
if (!isDockerContainer && !isKubernetesContainer) {
return null;
}
return (
<MetricsTemplate ref={ref}>
{isDockerContainer &&
DOCKER_METRIC_TYPES.map((metric) => (
<DockerCharts
key={metric}
assetId={asset.id}
dataView={metrics.dataView}
dateRange={state.dateRange}
metric={metric}
/>
))}
{!isDockerContainer &&
isKubernetesContainer &&
KUBERNETES_METRIC_TYPES.map((metric) => (
<KubernetesContainerCharts
key={metric}
assetId={asset.id}
dataView={metrics.dataView}
dateRange={state.dateRange}
metric={metric}
/>
))}
</MetricsTemplate>
);
};

View file

@ -6,6 +6,7 @@
*/
import React from 'react';
import { HostMetrics } from './host_metrics';
import { ContainerMetrics } from './container_metrics';
import { useAssetDetailsRenderPropsContext } from '../../hooks/use_asset_details_render_props';
export const Metrics = () => {
@ -14,6 +15,8 @@ export const Metrics = () => {
switch (asset.type) {
case 'host':
return <HostMetrics />;
case 'container':
return <ContainerMetrics />;
default:
return null;
}

View file

@ -9,9 +9,11 @@ import { EuiFlexGroup, EuiFlexGrid } from '@elastic/eui';
import type { TimeRange } from '@kbn/es-query';
import type { DataView } from '@kbn/data-views-plugin/public';
import { DockerCharts } from '../../../charts/docker_charts';
import { INTEGRATIONS } from '../../../constants';
import { DOCKER_METRIC_TYPES, INTEGRATIONS, KUBERNETES_METRIC_TYPES } from '../../../constants';
import { useIntegrationCheck } from '../../../hooks/use_integration_check';
import { KubernetesContainerCharts } from '../../../charts/kubernetes_charts';
import { useTabSwitcherContext } from '../../../hooks/use_tab_switcher';
import { ContentTabIds } from '../../../types';
interface Props {
assetId: string;
@ -20,11 +22,16 @@ interface Props {
}
export const ContainerMetrics = (props: Props) => {
const { showTab } = useTabSwitcherContext();
const isDockerContainer = useIntegrationCheck({ dependsOn: INTEGRATIONS.docker });
const isKubernetesContainer = useIntegrationCheck({
dependsOn: INTEGRATIONS.kubernetesContainer,
});
const onClick = (metric: string) => {
showTab(ContentTabIds.METRICS, { scrollTo: metric });
};
if (!isDockerContainer && !isKubernetesContainer) {
return null;
}
@ -32,20 +39,20 @@ export const ContainerMetrics = (props: Props) => {
return (
<EuiFlexGroup gutterSize="m" direction="column">
<EuiFlexGrid columns={2} gutterSize="s">
{isDockerContainer && (
<>
<DockerCharts {...props} metric="cpu" />
<DockerCharts {...props} metric="memory" />
<DockerCharts {...props} metric="network" />
<DockerCharts {...props} metric="disk" />
</>
)}
{!isDockerContainer && isKubernetesContainer && (
<>
<KubernetesContainerCharts {...props} metric="cpu" />
<KubernetesContainerCharts {...props} metric="memory" />
</>
)}
{isDockerContainer &&
DOCKER_METRIC_TYPES.map((metric) => (
<DockerCharts key={metric} {...props} metric={metric} onShowAll={onClick} />
))}
{!isDockerContainer &&
isKubernetesContainer &&
KUBERNETES_METRIC_TYPES.map((metric) => (
<KubernetesContainerCharts
key={metric}
{...props}
metric={metric}
onShowAll={onClick}
/>
))}
</EuiFlexGrid>
</EuiFlexGroup>
);

View file

@ -309,7 +309,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
{ metric: 'cpu', chartsCount: 1 },
{ metric: 'memory', chartsCount: 1 },
].forEach(({ metric, chartsCount }) => {
it.skip(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => {
it(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => {
const containers = await pageObjects.assetDetails.getOverviewTabDockerMetricCharts(
metric
);
@ -335,6 +335,16 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
});
describe('Metrics Tab', () => {
before(async () => {
await pageObjects.assetDetails.clickMetricsTab();
});
it('should show metrics content', async () => {
await pageObjects.assetDetails.metricsChartsContentExists();
});
});
describe('Logs Tab', () => {
before(async () => {
await pageObjects.assetDetails.clickLogsTab();

View file

@ -724,6 +724,28 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await searchInput.clearValue();
});
});
describe('Metrics Tab', () => {
before(async () => {
await pageObjects.assetDetails.clickMetricsTab();
});
[
{ metric: 'cpu', chartsCount: 1 },
{ metric: 'memory', chartsCount: 1 },
{ metric: 'disk', chartsCount: 1 },
{ metric: 'network', chartsCount: 1 },
].forEach(({ metric, chartsCount }) => {
it(`should render ${chartsCount} ${metric} chart(s)`, async () => {
const charts = await pageObjects.assetDetails.getMetricsTabDockerCharts(metric);
expect(charts.length).to.equal(chartsCount);
});
it(`should render a quick access for ${metric} in the side panel`, async () => {
await pageObjects.assetDetails.quickAccessItemExists(metric);
});
});
});
});
});
});

View file

@ -227,6 +227,14 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) {
return section.findAllByCssSelector('[data-test-subj*="infraAssetDetailsMetricChart"]');
},
async getMetricsTabDockerCharts(metric: string) {
const container = await testSubjects.find('infraAssetDetailsMetricsTabContent');
const section = await container.findByTestSubject(
`infraAssetDetailsDockerChartsSection${metric}`
);
return section.findAllByCssSelector('[data-test-subj*="infraAssetDetailsMetricChart"]');
},
async quickAccessItemExists(metric: string) {
return testSubjects.click(`infraMetricsQuickAccessItem${metric}`);
},