diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/container/index.ts b/x-pack/legacy/plugins/infra/common/inventory_models/container/index.ts index 254ed7b759300..54fe938528d19 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/container/index.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/container/index.ts @@ -4,13 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { layout } from './layout'; import { metrics } from './metrics'; import { InventoryModel } from '../types'; export const container: InventoryModel = { id: 'container', requiredModules: ['docker'], - layout, metrics, + requiredMetrics: [ + 'containerOverview', + 'containerCpuUsage', + 'containerMemory', + 'containerNetworkTraffic', + 'containerDiskIOBytes', + 'containerDiskIOOps', + ], }; diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/container/layout.ts b/x-pack/legacy/plugins/infra/common/inventory_models/container/layout.tsx similarity index 64% rename from x-pack/legacy/plugins/infra/common/inventory_models/container/layout.ts rename to x-pack/legacy/plugins/infra/common/inventory_models/container/layout.tsx index 821e180cda016..00da70f1d96a5 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/container/layout.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/container/layout.tsx @@ -3,30 +3,32 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import React from 'react'; import { i18n } from '@kbn/i18n'; -import { InventoryDetailLayoutCreator } from '../types'; -import { nginxLayoutCreator } from '../shared/layouts/nginx'; +import { LayoutPropsWithTheme } from '../../../public/pages/metrics/types'; +import { Section } from '../../../public/pages/metrics/components/section'; +import { SubSection } from '../../../public/pages/metrics/components/sub_section'; +import { GaugesSectionVis } from '../../../public/pages/metrics/components/gauges_section_vis'; +import { ChartSectionVis } from '../../../public/pages/metrics/components/chart_section_vis'; +import { withTheme } from '../../../../../common/eui_styled_components'; -export const layout: InventoryDetailLayoutCreator = theme => [ - { - id: 'containerOverview', - label: i18n.translate('xpack.infra.metricDetailPage.containerMetricsLayout.layoutLabel', { - defaultMessage: 'Container', - }), - sections: [ - { - id: 'containerOverview', - label: i18n.translate( - 'xpack.infra.metricDetailPage.containerMetricsLayout.overviewSection.sectionLabel', - { - defaultMessage: 'Overview', - } - ), - requires: ['docker.cpu', 'docker.memory', 'docker.network'], - type: 'gauges', - visConfig: { - seriesOverrides: { +export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( + +
+ + [ formatter: 'bits', formatterTemplate: '{{value}}/s', }, - }, - }, - }, - { - id: 'containerCpuUsage', - label: i18n.translate( + }} + /> + + + + + + + + + [ } ), }, - }, - }, - }, - { - id: 'containerDiskIOOps', - label: i18n.translate( + }} + /> + + + [ } ), }, - }, - }, - }, - { - id: 'containerDiskIOBytes', - label: i18n.translate( + }} + /> + + + [ } ), }, - }, - }, - }, - ], - }, - ...nginxLayoutCreator(theme), -]; + }} + /> + +
+
+)); diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/container/metrics/index.ts b/x-pack/legacy/plugins/infra/common/inventory_models/container/metrics/index.ts index 9cbbb2dca7ffa..9e0153c5d6ea6 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/container/metrics/index.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/container/metrics/index.ts @@ -29,4 +29,5 @@ export const metrics: InventoryMetrics = { containerMemory, }, snapshot: { cpu, memory, rx, tx }, + defaultSnapshot: 'cpu', }; diff --git a/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/container_toolbar_items.tsx b/x-pack/legacy/plugins/infra/common/inventory_models/container/toolbar_items.tsx similarity index 76% rename from x-pack/legacy/plugins/infra/public/components/inventory/toolbars/container_toolbar_items.tsx rename to x-pack/legacy/plugins/infra/common/inventory_models/container/toolbar_items.tsx index 2c24c7aa1a830..ddb3c0491f164 100644 --- a/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/container_toolbar_items.tsx +++ b/x-pack/legacy/plugins/infra/common/inventory_models/container/toolbar_items.tsx @@ -6,11 +6,14 @@ import React, { useMemo } from 'react'; import { EuiFlexItem } from '@elastic/eui'; -import { ToolbarProps } from './toolbar'; -import { WaffleMetricControls } from '../../waffle/waffle_metric_controls'; -import { WaffleGroupByControls } from '../../waffle/waffle_group_by_controls'; -import { InfraSnapshotMetricType } from '../../../graphql/types'; -import { toMetricOpt, toGroupByOpt } from './toolbar_wrapper'; +import { ToolbarProps } from '../../../public/components/inventory/toolbars/toolbar'; +import { WaffleMetricControls } from '../../../public/components/waffle/waffle_metric_controls'; +import { WaffleGroupByControls } from '../../../public/components/waffle/waffle_group_by_controls'; +import { InfraSnapshotMetricType } from '../../../public/graphql/types'; +import { + toMetricOpt, + toGroupByOpt, +} from '../../../public/components/inventory/toolbars/toolbar_wrapper'; export const ContainerToolbarItems = (props: ToolbarProps) => { const options = useMemo( diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/host/index.ts b/x-pack/legacy/plugins/infra/common/inventory_models/host/index.ts index e29f5878bfcb3..08056e650a32e 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/host/index.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/host/index.ts @@ -4,13 +4,29 @@ * you may not use this file except in compliance with the Elastic License. */ -import { layout } from './layout'; import { metrics } from './metrics'; import { InventoryModel } from '../types'; +import { + aws as awsRequiredMetrics, + nginx as nginxRequireMetrics, +} from '../shared/metrics/required_metrics'; export const host: InventoryModel = { id: 'host', requiredModules: ['system'], - layout, metrics, + requiredMetrics: [ + 'hostSystemOverview', + 'hostCpuUsage', + 'hostLoad', + 'hostMemoryUsage', + 'hostNetworkTraffic', + 'hostK8sOverview', + 'hostK8sCpuCap', + 'hostK8sMemoryCap', + 'hostK8sDiskCap', + 'hostK8sPodCap', + ...awsRequiredMetrics, + ...nginxRequireMetrics, + ], }; diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/host/layout.ts b/x-pack/legacy/plugins/infra/common/inventory_models/host/layout.tsx similarity index 68% rename from x-pack/legacy/plugins/infra/common/inventory_models/host/layout.ts rename to x-pack/legacy/plugins/infra/common/inventory_models/host/layout.tsx index 2ac12387e26a4..fee79d8364c42 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/host/layout.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/host/layout.tsx @@ -3,33 +3,34 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import React from 'react'; import { i18n } from '@kbn/i18n'; -import { InventoryDetailLayoutCreator } from '../types'; - -import { nginxLayoutCreator } from '../shared/layouts/nginx'; -import { awsLayoutCreator } from '../shared/layouts/aws'; +import { LayoutPropsWithTheme } from '../../../public/pages/metrics/types'; +import { Section } from '../../../public/pages/metrics/components/section'; +import { SubSection } from '../../../public/pages/metrics/components/sub_section'; +import { GaugesSectionVis } from '../../../public/pages/metrics/components/gauges_section_vis'; +import { ChartSectionVis } from '../../../public/pages/metrics/components/chart_section_vis'; +import { withTheme } from '../../../../../common/eui_styled_components'; +import * as Aws from '../shared/layouts/aws'; +import * as Ngnix from '../shared/layouts/nginx'; -export const layout: InventoryDetailLayoutCreator = theme => [ - { - id: 'hostOverview', - label: i18n.translate('xpack.infra.metricDetailPage.hostMetricsLayout.layoutLabel', { - defaultMessage: 'Host', - }), - sections: [ - { - id: 'hostSystemOverview', - linkToId: 'hostOverview', - label: i18n.translate( - 'xpack.infra.metricDetailPage.hostMetricsLayout.overviewSection.sectionLabel', - { - defaultMessage: 'Overview', - } - ), - requires: ['system.cpu', 'system.load', 'system.memory', 'system.network'], - type: 'gauges', - visConfig: { - seriesOverrides: { +export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( + +
+ + [ formatter: 'bits', formatterTemplate: '{{value}}/s', }, - }, - }, - }, - { - id: 'hostCpuUsage', - label: i18n.translate( + }} + /> + + + [ softirq: { color: theme.eui.euiColorVis6 }, iowait: { color: theme.eui.euiColorVis7 }, nice: { color: theme.eui.euiColorVis5 }, - }, - }, - }, - { - id: 'hostLoad', - label: i18n.translate( + }} + /> + + + [ } ), }, - }, - }, - }, - { - id: 'hostMemoryUsage', - label: i18n.translate( + }} + /> + + + + + + [ } ), }, - }, - }, - }, - ], - }, - { - id: 'k8sOverview', - label: 'Kubernetes', - sections: [ - { - id: 'hostK8sOverview', - linkToId: 'k8sOverview', - label: i18n.translate( - 'xpack.infra.metricDetailPage.kubernetesMetricsLayout.overviewSection.sectionLabel', - { - defaultMessage: 'Overview', - } - ), - requires: ['kubernetes.node'], - type: 'gauges', - visConfig: { - seriesOverrides: { + }} + /> + +
+
+ + [ formatter: 'percent', gaugeMax: 1, }, - }, - }, - }, - { - id: 'hostK8sCpuCap', - label: i18n.translate( + }} + /> + + + + + + + + + + + + + +
+ + +
+)); diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/host/metrics/index.ts b/x-pack/legacy/plugins/infra/common/inventory_models/host/metrics/index.ts index d1fd3a307ae36..f4c0150309dd8 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/host/metrics/index.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/host/metrics/index.ts @@ -51,4 +51,5 @@ export const metrics: InventoryMetrics = { hostDockerTop5ByCpu, }, snapshot: { count, cpu, load, logRate, memory, rx, tx }, + defaultSnapshot: 'cpu', }; diff --git a/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/host_toolbar_items.tsx b/x-pack/legacy/plugins/infra/common/inventory_models/host/toolbar_items.tsx similarity index 77% rename from x-pack/legacy/plugins/infra/public/components/inventory/toolbars/host_toolbar_items.tsx rename to x-pack/legacy/plugins/infra/common/inventory_models/host/toolbar_items.tsx index 81dadcc7f6c8b..8e1bb0dfb4816 100644 --- a/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/host_toolbar_items.tsx +++ b/x-pack/legacy/plugins/infra/common/inventory_models/host/toolbar_items.tsx @@ -6,11 +6,14 @@ import React, { useMemo } from 'react'; import { EuiFlexItem } from '@elastic/eui'; -import { ToolbarProps } from './toolbar'; -import { WaffleMetricControls } from '../../waffle/waffle_metric_controls'; -import { WaffleGroupByControls } from '../../waffle/waffle_group_by_controls'; -import { InfraSnapshotMetricType } from '../../../graphql/types'; -import { toGroupByOpt, toMetricOpt } from './toolbar_wrapper'; +import { ToolbarProps } from '../../../public/components/inventory/toolbars/toolbar'; +import { WaffleMetricControls } from '../../../public/components/waffle/waffle_metric_controls'; +import { WaffleGroupByControls } from '../../../public/components/waffle/waffle_group_by_controls'; +import { InfraSnapshotMetricType } from '../../../public/graphql/types'; +import { + toGroupByOpt, + toMetricOpt, +} from '../../../public/components/inventory/toolbars/toolbar_wrapper'; export const HostToolbarItems = (props: ToolbarProps) => { const metricOptions = useMemo( diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/index.ts b/x-pack/legacy/plugins/infra/common/inventory_models/index.ts index ec518cb9cb950..79aad7b2ccf6f 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/index.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/index.ts @@ -11,7 +11,7 @@ import { container } from './container'; import { InventoryItemType } from './types'; export { metrics } from './metrics'; -export const inventoryModels = [host, pod, container]; +const inventoryModels = [host, pod, container]; export const findInventoryModel = (type: InventoryItemType) => { const model = inventoryModels.find(m => m.id === type); diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts b/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts new file mode 100644 index 0000000000000..9fce720f5b14b --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/inventory_models/layouts.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** + * WHY ARE THE LAYOUTS A SEPERATE FILE? + * + * Files with React can not be included on the server without + * crashing due to the requirement of the `window` object. + */ + +import { idx } from '@kbn/elastic-idx'; +import { i18n } from '@kbn/i18n'; + +import { ReactNode, FunctionComponent } from 'react'; +import { Layout as HostLayout } from './host/layout'; +import { Layout as PodLayout } from './pod/layout'; +import { Layout as ContainerLayout } from './container/layout'; +import { InventoryItemType } from './types'; +import { LayoutProps } from '../../public/pages/metrics/types'; + +interface Layouts { + [type: string]: ReactNode; +} + +const layouts: Layouts = { + host: HostLayout, + pod: PodLayout, + container: ContainerLayout, +}; + +export const findLayout = (type: InventoryItemType) => { + const Layout = idx(layouts, _ => _[type]); + if (!Layout) { + throw new Error( + i18n.translate('xpack.infra.inventoryModels.findLayout.error', { + defaultMessage: "The layout you've attempted to find does not exist", + }) + ); + } + return Layout as FunctionComponent; +}; diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/pod/index.ts b/x-pack/legacy/plugins/infra/common/inventory_models/pod/index.ts index 1cf839b787657..66ace03abac00 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/pod/index.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/pod/index.ts @@ -4,12 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { layout } from './layout'; import { metrics } from './metrics'; import { InventoryModel } from '../types'; +import { nginx as nginxRequiredMetrics } from '../shared/metrics/required_metrics'; + export const pod: InventoryModel = { id: 'pod', requiredModules: ['kubernetes'], - layout, metrics, + requiredMetrics: [ + 'podOverview', + 'podCpuUsage', + 'podMemoryUsage', + 'podNetworkTraffic', + ...nginxRequiredMetrics, + ], }; diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/pod/layout.ts b/x-pack/legacy/plugins/infra/common/inventory_models/pod/layout.tsx similarity index 59% rename from x-pack/legacy/plugins/infra/common/inventory_models/pod/layout.ts rename to x-pack/legacy/plugins/infra/common/inventory_models/pod/layout.tsx index ffb78894d63f6..401e25c4defb8 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/pod/layout.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/pod/layout.tsx @@ -3,30 +3,33 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import React from 'react'; import { i18n } from '@kbn/i18n'; -import { InventoryDetailLayoutCreator } from '../types'; -import { nginxLayoutCreator } from '../shared/layouts/nginx'; +import { LayoutPropsWithTheme } from '../../../public/pages/metrics/types'; +import { Section } from '../../../public/pages/metrics/components/section'; +import { SubSection } from '../../../public/pages/metrics/components/sub_section'; +import { GaugesSectionVis } from '../../../public/pages/metrics/components/gauges_section_vis'; +import { ChartSectionVis } from '../../../public/pages/metrics/components/chart_section_vis'; +import { withTheme } from '../../../../../common/eui_styled_components'; +import * as Nginx from '../shared/layouts/nginx'; -export const layout: InventoryDetailLayoutCreator = theme => [ - { - id: 'podOverview', - label: i18n.translate('xpack.infra.metricDetailPage.podMetricsLayout.layoutLabel', { - defaultMessage: 'Pod', - }), - sections: [ - { - id: 'podOverview', - label: i18n.translate( - 'xpack.infra.metricDetailPage.podMetricsLayout.overviewSection.sectionLabel', - { - defaultMessage: 'Overview', - } - ), - requires: ['kubernetes.pod'], - type: 'gauges', - visConfig: { - seriesOverrides: { +export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( + +
+ + [ formatter: 'bits', formatterTemplate: '{{value}}/s', }, - }, - }, - }, - { - id: 'podCpuUsage', - label: i18n.translate( + }} + /> + + + + + + + + + [ } ), }, - }, - }, - }, - ], - }, - ...nginxLayoutCreator(theme), -]; + }} + /> + +
+ +
+)); diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/pod/metrics/index.ts b/x-pack/legacy/plugins/infra/common/inventory_models/pod/metrics/index.ts index 03aa6d4e039b1..2aa7ac6b496af 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/pod/metrics/index.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/pod/metrics/index.ts @@ -25,4 +25,5 @@ export const metrics: InventoryMetrics = { podMemoryUsage, }, snapshot: { cpu, memory, rx, tx }, + defaultSnapshot: 'cpu', }; diff --git a/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/pod_toolbar_items.tsx b/x-pack/legacy/plugins/infra/common/inventory_models/pod/toolbar_items.tsx similarity index 75% rename from x-pack/legacy/plugins/infra/public/components/inventory/toolbars/pod_toolbar_items.tsx rename to x-pack/legacy/plugins/infra/common/inventory_models/pod/toolbar_items.tsx index b3eec84dcfcd6..cc0676fc60ae4 100644 --- a/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/pod_toolbar_items.tsx +++ b/x-pack/legacy/plugins/infra/common/inventory_models/pod/toolbar_items.tsx @@ -6,11 +6,14 @@ import React, { useMemo } from 'react'; import { EuiFlexItem } from '@elastic/eui'; -import { ToolbarProps } from './toolbar'; -import { WaffleMetricControls } from '../../waffle/waffle_metric_controls'; -import { WaffleGroupByControls } from '../../waffle/waffle_group_by_controls'; -import { InfraSnapshotMetricType } from '../../../graphql/types'; -import { toGroupByOpt, toMetricOpt } from './toolbar_wrapper'; +import { ToolbarProps } from '../../../public/components/inventory/toolbars/toolbar'; +import { WaffleMetricControls } from '../../../public/components/waffle/waffle_metric_controls'; +import { WaffleGroupByControls } from '../../../public/components/waffle/waffle_group_by_controls'; +import { InfraSnapshotMetricType } from '../../../public/graphql/types'; +import { + toGroupByOpt, + toMetricOpt, +} from '../../../public/components/inventory/toolbars/toolbar_wrapper'; export const PodToolbarItems = (props: ToolbarProps) => { const options = useMemo( diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/shared/layouts/aws.ts b/x-pack/legacy/plugins/infra/common/inventory_models/shared/layouts/aws.tsx similarity index 69% rename from x-pack/legacy/plugins/infra/common/inventory_models/shared/layouts/aws.ts rename to x-pack/legacy/plugins/infra/common/inventory_models/shared/layouts/aws.tsx index 7c7c009df6f27..2cabbe4c33ff3 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/shared/layouts/aws.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/shared/layouts/aws.tsx @@ -3,28 +3,30 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import React from 'react'; import { i18n } from '@kbn/i18n'; -import { InventoryDetailLayoutCreator } from '../../types'; +import { LayoutPropsWithTheme } from '../../../../public/pages/metrics/types'; +import { Section } from '../../../../public/pages/metrics/components/section'; +import { SubSection } from '../../../../public/pages/metrics/components/sub_section'; +import { GaugesSectionVis } from '../../../../public/pages/metrics/components/gauges_section_vis'; +import { ChartSectionVis } from '../../../../public/pages/metrics/components/chart_section_vis'; +import { withTheme } from '../../../../../../common/eui_styled_components'; -export const awsLayoutCreator: InventoryDetailLayoutCreator = theme => [ - { - id: 'awsOverview', - label: 'AWS', - sections: [ - { - id: 'awsOverview', - linkToId: 'awsOverview', - label: i18n.translate( - 'xpack.infra.metricDetailPage.awsMetricsLayout.overviewSection.sectionLabel', - { - defaultMessage: 'Overview', - } - ), - requires: ['aws.ec2'], - type: 'gauges', - visConfig: { - seriesOverrides: { +export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( + +
+ + [ color: theme.eui.euiColorFullShade, formatter: 'number', }, - }, - }, - }, - { - id: 'awsCpuUtilization', - label: i18n.translate( + }} + /> + + + [ } ), }, - }, - }, - }, - { - id: 'awsNetworkBytes', - label: i18n.translate( + }} + /> + + + [ } ), }, - }, - }, - }, - { - id: 'awsNetworkPackets', - label: i18n.translate( + }} + /> + + + [ } ), }, - }, - }, - }, - { - id: 'awsDiskioOps', - label: i18n.translate( + }} + /> + + + [ } ), }, - }, - }, - }, - { - id: 'awsDiskioBytes', - label: i18n.translate( + }} + /> + + + [ } ), }, - }, - }, - }, - ], - }, -]; + }} + /> + +
+
+)); diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/shared/layouts/nginx.ts b/x-pack/legacy/plugins/infra/common/inventory_models/shared/layouts/nginx.ts deleted file mode 100644 index 2d895c3b83a0b..0000000000000 --- a/x-pack/legacy/plugins/infra/common/inventory_models/shared/layouts/nginx.ts +++ /dev/null @@ -1,102 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { InventoryDetailLayoutCreator } from '../../types'; - -export const nginxLayoutCreator: InventoryDetailLayoutCreator = theme => [ - { - id: 'nginxOverview', - label: 'Nginx', - sections: [ - { - id: 'nginxHits', - label: i18n.translate( - 'xpack.infra.metricDetailPage.nginxMetricsLayout.hitsSection.sectionLabel', - { - defaultMessage: 'Hits', - } - ), - requires: ['nginx.access'], - type: 'chart', - visConfig: { - formatter: 'abbreviatedNumber', - stacked: true, - seriesOverrides: { - '200s': { color: theme.eui.euiColorVis1, type: 'bar' }, - '300s': { color: theme.eui.euiColorVis5, type: 'bar' }, - '400s': { color: theme.eui.euiColorVis2, type: 'bar' }, - '500s': { color: theme.eui.euiColorVis9, type: 'bar' }, - }, - }, - }, - { - id: 'nginxRequestRate', - label: i18n.translate( - 'xpack.infra.metricDetailPage.nginxMetricsLayout.requestRateSection.sectionLabel', - { - defaultMessage: 'Request Rate', - } - ), - requires: ['nginx.stubstatus'], - type: 'chart', - visConfig: { - formatter: 'abbreviatedNumber', - formatterTemplate: '{{value}}/s', - seriesOverrides: { - rate: { color: theme.eui.euiColorVis1, type: 'area' }, - }, - }, - }, - { - id: 'nginxActiveConnections', - label: i18n.translate( - 'xpack.infra.metricDetailPage.nginxMetricsLayout.activeConnectionsSection.sectionLabel', - { - defaultMessage: 'Active Connections', - } - ), - requires: ['nginx.stubstatus'], - type: 'chart', - visConfig: { - formatter: 'abbreviatedNumber', - seriesOverrides: { - connections: { - color: theme.eui.euiColorVis1, - type: 'bar', - }, - }, - }, - }, - { - id: 'nginxRequestsPerConnection', - label: i18n.translate( - 'xpack.infra.metricDetailPage.nginxMetricsLayout.requestsPerConnectionsSection.sectionLabel', - { - defaultMessage: 'Requests per Connections', - } - ), - requires: ['nginx.stubstatus'], - type: 'chart', - visConfig: { - formatter: 'abbreviatedNumber', - seriesOverrides: { - reqPerConns: { - color: theme.eui.euiColorVis1, - type: 'bar', - name: i18n.translate( - 'xpack.infra.metricDetailPage.nginxMetricsLayout.requestsPerConnectionsSection.reqsPerConnSeriesLabel', - { - defaultMessage: 'reqs per conn', - } - ), - }, - }, - }, - }, - ], - }, -]; diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/shared/layouts/nginx.tsx b/x-pack/legacy/plugins/infra/common/inventory_models/shared/layouts/nginx.tsx new file mode 100644 index 0000000000000..9d31ffa775d21 --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/inventory_models/shared/layouts/nginx.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { LayoutPropsWithTheme } from '../../../../public/pages/metrics/types'; +import { Section } from '../../../../public/pages/metrics/components/section'; +import { SubSection } from '../../../../public/pages/metrics/components/sub_section'; +import { ChartSectionVis } from '../../../../public/pages/metrics/components/chart_section_vis'; +import { withTheme } from '../../../../../../common/eui_styled_components'; + +export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( + +
+ + + + + + + + + + + + +
+
+)); diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/shared/metrics/index.ts b/x-pack/legacy/plugins/infra/common/inventory_models/shared/metrics/index.ts index a17ca0e2b09e5..6416aa08e8585 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/shared/metrics/index.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/shared/metrics/index.ts @@ -16,6 +16,7 @@ import { awsNetworkBytes } from './tsvb/aws_network_bytes'; import { awsNetworkPackets } from './tsvb/aws_network_packets'; import { awsOverview } from './tsvb/aws_overview'; import { InventoryMetrics } from '../../types'; +import { count } from './snapshot/count'; export const metrics: InventoryMetrics = { tsvb: { @@ -30,4 +31,8 @@ export const metrics: InventoryMetrics = { awsNetworkPackets, awsOverview, }, + snapshot: { + count, + }, + defaultSnapshot: 'count', }; diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/shared/metrics/required_metrics.ts b/x-pack/legacy/plugins/infra/common/inventory_models/shared/metrics/required_metrics.ts new file mode 100644 index 0000000000000..0b2623c448646 --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/inventory_models/shared/metrics/required_metrics.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { InventoryMetric } from '../../types'; + +export const nginx: InventoryMetric[] = [ + 'nginxHits', + 'nginxRequestRate', + 'nginxActiveConnections', + 'nginxRequestsPerConnection', +]; + +export const aws: InventoryMetric[] = [ + 'awsOverview', + 'awsCpuUtilization', + 'awsNetworkBytes', + 'awsNetworkPackets', + 'awsDiskioOps', + 'awsDiskioBytes', +]; diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts b/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts new file mode 100644 index 0000000000000..661b9c7a8841e --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/inventory_models/toolbars.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { idx } from '@kbn/elastic-idx/target'; +import { ReactNode, FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { InventoryItemType } from './types'; +import { HostToolbarItems } from './host/toolbar_items'; +import { ContainerToolbarItems } from './container/toolbar_items'; +import { PodToolbarItems } from './pod/toolbar_items'; +import { ToolbarProps } from '../../public/components/inventory/toolbars/toolbar'; + +interface Toolbars { + [type: string]: ReactNode; +} + +const toolbars: Toolbars = { + host: HostToolbarItems, + container: ContainerToolbarItems, + pod: PodToolbarItems, +}; + +export const findToolbar = (type: InventoryItemType) => { + const Toolbar = idx(toolbars, _ => _[type]); + if (!Toolbar) { + throw new Error( + i18n.translate('xpack.infra.inventoryModels.findToolbar.error', { + defaultMessage: "The toolbar you've attempted to find does not exist.", + }) + ); + } + return Toolbar as FunctionComponent; +}; diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/types.ts b/x-pack/legacy/plugins/infra/common/inventory_models/types.ts index 9c9f11048ee63..93eaf214ad23e 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/types.ts +++ b/x-pack/legacy/plugins/infra/common/inventory_models/types.ts @@ -5,7 +5,6 @@ */ import * as rt from 'io-ts'; -import { EuiTheme } from '../../../../common/eui_styled_components'; export const ItemTypeRT = rt.keyof({ host: null, @@ -32,62 +31,50 @@ export const InventoryFormatterTypeRT = rt.keyof({ number: null, percent: null, }); +export type InventoryFormatterType = rt.TypeOf; export type InventoryItemType = rt.TypeOf; -export const InventoryMetricRT = rt.string; -export type InventoryMetric = rt.TypeOf; - -export const SeriesOverridesRT = rt.intersection([ - rt.type({ - color: rt.string, - }), - rt.partial({ - type: InventoryVisTypeRT, - name: rt.string, - formatter: InventoryFormatterTypeRT, - formatterTemplate: rt.string, - gaugeMax: rt.number, - }), -]); - -export const VisConfigRT = rt.partial({ - stacked: rt.boolean, - type: InventoryVisTypeRT, - formatter: InventoryFormatterTypeRT, - formatterTemplate: rt.string, - seriesOverrides: rt.record(rt.string, rt.union([rt.undefined, SeriesOverridesRT])), +export const InventoryMetricRT = rt.keyof({ + hostSystemOverview: null, + hostCpuUsage: null, + hostFilesystem: null, + hostK8sOverview: null, + hostK8sCpuCap: null, + hostK8sDiskCap: null, + hostK8sMemoryCap: null, + hostK8sPodCap: null, + hostLoad: null, + hostMemoryUsage: null, + hostNetworkTraffic: null, + hostDockerOverview: null, + hostDockerInfo: null, + hostDockerTop5ByCpu: null, + hostDockerTop5ByMemory: null, + podOverview: null, + podCpuUsage: null, + podMemoryUsage: null, + podLogUsage: null, + podNetworkTraffic: null, + containerOverview: null, + containerCpuKernel: null, + containerCpuUsage: null, + containerDiskIOOps: null, + containerDiskIOBytes: null, + containerMemory: null, + containerNetworkTraffic: null, + nginxHits: null, + nginxRequestRate: null, + nginxActiveConnections: null, + nginxRequestsPerConnection: null, + awsOverview: null, + awsCpuUtilization: null, + awsNetworkBytes: null, + awsNetworkPackets: null, + awsDiskioBytes: null, + awsDiskioOps: null, + custom: null, }); - -export const InventorySectionTypeRT = rt.keyof({ - chart: null, - gauges: null, -}); - -export type InventorySectionType = rt.TypeOf; - -export const SectionRT = rt.intersection([ - rt.type({ - id: InventoryMetricRT, - label: rt.string, - requires: rt.array(rt.string), - visConfig: VisConfigRT, - type: InventorySectionTypeRT, - }), - rt.partial({ - linkToId: rt.string, - }), -]); - -export const InventoryDetailLayoutRT = rt.type({ - id: rt.string, - label: rt.string, - sections: rt.array(SectionRT), -}); - -export type InventoryDetailSection = rt.TypeOf; -export type InventoryDetailLayout = rt.TypeOf; - -export type InventoryDetailLayoutCreator = (theme: EuiTheme) => InventoryDetailLayout[]; +export type InventoryMetric = rt.TypeOf; export const TSVBMetricTypeRT = rt.keyof({ avg: null, @@ -272,14 +259,27 @@ export const SnapshotModelRT = rt.record( ); export type SnapshotModel = rt.TypeOf; +export const SnapshotMetricTypeRT = rt.keyof({ + count: null, + cpu: null, + load: null, + memory: null, + tx: null, + rx: null, + logRate: null, +}); + +export type SnapshotMetricType = rt.TypeOf; + export interface InventoryMetrics { tsvb: { [name: string]: TSVBMetricModelCreator }; - snapshot?: { [name: string]: SnapshotModel }; + snapshot: { [name: string]: SnapshotModel }; + defaultSnapshot: SnapshotMetricType; } export interface InventoryModel { id: string; requiredModules: string[]; - layout: InventoryDetailLayoutCreator; metrics: InventoryMetrics; + requiredMetrics: InventoryMetric[]; } diff --git a/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/toolbar.tsx b/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/toolbar.tsx index f68944b012cca..167a328135bf7 100644 --- a/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/toolbar.tsx +++ b/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/toolbar.tsx @@ -4,18 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FunctionComponent } from 'react'; import { StaticIndexPattern } from 'ui/index_patterns'; import { Action } from 'typescript-fsa'; import { EuiFlexItem } from '@elastic/eui'; +import { findToolbar } from '../../../../common/inventory_models/toolbars'; import { InfraNodeType, InfraSnapshotMetricInput, InfraSnapshotGroupbyInput, } from '../../../graphql/types'; -import { HostToolbarItems } from './host_toolbar_items'; -import { PodToolbarItems } from './pod_toolbar_items'; -import { ContainerToolbarItems } from './container_toolbar_items'; import { ToolbarWrapper } from './toolbar_wrapper'; import { waffleOptionsSelectors } from '../../../store'; @@ -35,7 +33,7 @@ export interface ToolbarProps { nodeType: ReturnType; } -const wrapToolbarItems = (ToolbarItems: (props: ToolbarProps) => JSX.Element) => { +const wrapToolbarItems = (ToolbarItems: FunctionComponent) => { return ( {props => ( @@ -60,15 +58,7 @@ const wrapToolbarItems = (ToolbarItems: (props: ToolbarProps) => JSX.Element) => ); }; -export const Toolbar = (props: { nodeType: InfraNodeType }) => { - switch (props.nodeType) { - case InfraNodeType.host: - return wrapToolbarItems(HostToolbarItems); - case InfraNodeType.pod: - return wrapToolbarItems(PodToolbarItems); - case InfraNodeType.container: - return wrapToolbarItems(ContainerToolbarItems); - default: - return null; - } +export const Toolbar = ({ nodeType }: { nodeType: InfraNodeType }) => { + const ToolbarItems = findToolbar(nodeType); + return wrapToolbarItems(ToolbarItems); }; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/index.tsx b/x-pack/legacy/plugins/infra/public/components/metrics/index.tsx deleted file mode 100644 index a80525e3a9cac..0000000000000 --- a/x-pack/legacy/plugins/infra/public/components/metrics/index.tsx +++ /dev/null @@ -1,128 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiPageContentBody, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; - -import { InfraMetricData } from '../../graphql/types'; -import { NoData } from '../empty_states'; -import { InfraLoadingPanel } from '../loading'; -import { Section } from './section'; -import { MetricsTimeInput } from '../../containers/metrics/with_metrics_time'; -import { - InventoryDetailLayout, - InventoryDetailSection, -} from '../../../common/inventory_models/types'; - -interface Props { - metrics: InfraMetricData[]; - layouts: InventoryDetailLayout[]; - loading: boolean; - refetch: () => void; - nodeId: string; - label: string; - onChangeRangeTime?: (time: MetricsTimeInput) => void; - isLiveStreaming?: boolean; - stopLiveStreaming?: () => void; -} - -interface State { - crosshairValue: number | null; -} - -export const Metrics = class extends React.PureComponent { - public static displayName = 'Metrics'; - public readonly state = { - crosshairValue: null, - }; - - public render() { - if (this.props.loading) { - return ( - - ); - } else if (!this.props.loading && this.props.metrics && this.props.metrics.length === 0) { - return ( - - ); - } - - return {this.props.layouts.map(this.renderLayout)}; - } - - private handleRefetch = () => { - this.props.refetch(); - }; - - private renderLayout = (layout: InventoryDetailLayout) => { - return ( - - - -

- -

-
-
- {layout.sections.map(this.renderSection(layout))} -
- ); - }; - - private renderSection = (layout: InventoryDetailLayout) => (section: InventoryDetailSection) => { - let sectionProps = {}; - if (section.type === 'chart') { - const { onChangeRangeTime, isLiveStreaming, stopLiveStreaming } = this.props; - sectionProps = { - onChangeRangeTime, - isLiveStreaming, - stopLiveStreaming, - crosshairValue: this.state.crosshairValue, - onCrosshairUpdate: this.onCrosshairUpdate, - }; - } - return ( -
- ); - }; - - private onCrosshairUpdate = (crosshairValue: number) => { - this.setState({ - crosshairValue, - }); - }; -}; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/section.tsx b/x-pack/legacy/plugins/infra/public/components/metrics/section.tsx deleted file mode 100644 index ff416bf4541b2..0000000000000 --- a/x-pack/legacy/plugins/infra/public/components/metrics/section.tsx +++ /dev/null @@ -1,42 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { InfraMetricData } from '../../graphql/types'; -import { sections } from './sections'; -import { MetricsTimeInput } from '../../containers/metrics/with_metrics_time'; -import { InventoryDetailSection } from '../../../common/inventory_models/types'; - -interface Props { - section: InventoryDetailSection; - metrics: InfraMetricData[]; - onChangeRangeTime?: (time: MetricsTimeInput) => void; - crosshairValue?: number; - onCrosshairUpdate?: (crosshairValue: number) => void; - isLiveStreaming?: boolean; - stopLiveStreaming?: () => void; -} - -export class Section extends React.PureComponent { - public render() { - const metric = this.props.metrics.find(m => m.id === this.props.section.id); - if (!metric) { - return null; - } - let sectionProps = {}; - if (this.props.section.type === 'chart') { - sectionProps = { - onChangeRangeTime: this.props.onChangeRangeTime, - crosshairValue: this.props.crosshairValue, - onCrosshairUpdate: this.props.onCrosshairUpdate, - isLiveStreaming: this.props.isLiveStreaming, - stopLiveStreaming: this.props.stopLiveStreaming, - }; - } - const Component = sections[this.props.section.type]; - return ; - } -} diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/sections/gauges_section.tsx b/x-pack/legacy/plugins/infra/public/components/metrics/sections/gauges_section.tsx deleted file mode 100644 index 6b73ac62872c9..0000000000000 --- a/x-pack/legacy/plugins/infra/public/components/metrics/sections/gauges_section.tsx +++ /dev/null @@ -1,104 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - EuiFlexItem, - EuiPageContentBody, - EuiPanel, - EuiProgress, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { get, last, max } from 'lodash'; -import React, { ReactText } from 'react'; - -import euiStyled from '../../../../../../common/eui_styled_components'; -import { InfraMetricData } from '../../../graphql/types'; -import { InfraFormatterType } from '../../../lib/lib'; -import { createFormatter } from '../../../utils/formatters'; -import { InventoryDetailSection } from '../../../../common/inventory_models/types'; - -interface Props { - section: InventoryDetailSection; - metric: InfraMetricData; -} - -const getFormatter = (section: InventoryDetailSection, seriesId: string) => (val: ReactText) => { - if (val == null) { - return ''; - } - const defaultFormatter = get(section, ['visConfig', 'formatter'], InfraFormatterType.number); - const defaultFormatterTemplate = get(section, ['visConfig', 'formatterTemplate'], '{{value}}'); - const formatter = get( - section, - ['visConfig', 'seriesOverrides', seriesId, 'formatter'], - defaultFormatter - ); - const formatterTemplate = get( - section, - ['visConfig', 'seriesOverrides', seriesId, 'formatterTemplate'], - defaultFormatterTemplate - ); - return createFormatter(formatter, formatterTemplate)(val); -}; - -export class GaugesSection extends React.PureComponent { - public render() { - const { metric, section } = this.props; - return ( - - - - {metric.series.map(series => { - const lastDataPoint = last(series.data); - if (!lastDataPoint) { - return null; - } - const formatter = getFormatter(section, series.id); - const value = formatter(lastDataPoint.value || 0); - const name = get( - section, - ['visConfig', 'seriesOverrides', series.id, 'name'], - series.id - ); - const dataMax = max(series.data.map(d => d.value || 0)); - const gaugeMax = get( - section, - ['visConfig', 'seriesOverrides', series.id, 'gaugeMax'], - dataMax - ); - return ( - - - - {name} - - -

{value}

-
- -
-
- ); - })} -
- -
- ); - } -} - -const GroupBox = euiStyled.div` - display: flex; - flex-flow: row wrap; - justify-content: space-evenly; -`; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/sections/index.ts b/x-pack/legacy/plugins/infra/public/components/metrics/sections/index.ts deleted file mode 100644 index 39844868ecbea..0000000000000 --- a/x-pack/legacy/plugins/infra/public/components/metrics/sections/index.ts +++ /dev/null @@ -1,13 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { GaugesSection } from './gauges_section'; -import { ChartSection } from './chart_section'; - -export const sections = { - chart: ChartSection, - gauges: GaugesSection, -}; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/side_nav.tsx b/x-pack/legacy/plugins/infra/public/components/metrics/side_nav.tsx deleted file mode 100644 index f20adadce6042..0000000000000 --- a/x-pack/legacy/plugins/infra/public/components/metrics/side_nav.tsx +++ /dev/null @@ -1,81 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiHideFor, EuiPageSideBar, EuiShowFor, EuiSideNav } from '@elastic/eui'; - -import React from 'react'; - -import euiStyled from '../../../../../common/eui_styled_components'; -import { - InventoryDetailLayout, - InventoryDetailSection, -} from '../../../common/inventory_models/types'; - -interface Props { - layouts: InventoryDetailLayout[]; - loading: boolean; - nodeName: string; - handleClick: (section: InventoryDetailSection) => () => void; -} - -export const MetricsSideNav = class extends React.PureComponent { - public static displayName = 'MetricsSideNav'; - - public readonly state = { - isOpenOnMobile: false, - }; - - public render() { - let content; - let mobileContent; - if (!this.props.loading) { - const entries = this.props.layouts.map(item => { - return { - name: item.label, - id: item.id, - items: item.sections.map(section => ({ - id: section.id, - name: section.label, - onClick: this.props.handleClick(section), - })), - }; - }); - content = ; - mobileContent = ( - - ); - } - return ( - - - {content} - - {mobileContent} - - ); - } - - private toggleOpenOnMobile = () => { - this.setState({ - isOpenOnMobile: !this.state.isOpenOnMobile, - }); - }; -}; - -const SideNavContainer = euiStyled.div` - position: fixed; - z-index: 1; - height: 88vh; - padding-left: 16px; - margin-left: -16px; - overflow-y: auto; - overflow-x: hidden; -`; diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx index ddb89811ac023..38e87038b7c4f 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx @@ -15,6 +15,7 @@ import { InfraNodeType, InfraSnapshotGroupbyInput, } from '../../graphql/types'; +import { findInventoryModel } from '../../../common/inventory_models'; interface Props { nodeType: InfraNodeType; @@ -32,7 +33,10 @@ export const WaffleInventorySwitcher = (props: Props) => { closePopover(); props.changeNodeType(nodeType); props.changeGroupBy([]); - props.changeMetric({ type: InfraSnapshotMetricType.cpu }); + const inventoryModel = findInventoryModel(nodeType); + props.changeMetric({ + type: inventoryModel.metrics.defaultSnapshot as InfraSnapshotMetricType, + }); }, [props.changeGroupBy, props.changeNodeType, props.changeMetric] ); diff --git a/x-pack/legacy/plugins/infra/public/containers/metadata/lib/get_filtered_layouts.ts b/x-pack/legacy/plugins/infra/public/containers/metadata/lib/get_filtered_layouts.ts deleted file mode 100644 index 6c7a9d6dcad38..0000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/metadata/lib/get_filtered_layouts.ts +++ /dev/null @@ -1,38 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { InfraMetadataFeature } from '../../../../common/http_api/metadata_api'; -import { InventoryDetailLayout } from '../../../../common/inventory_models/types'; - -export const getFilteredLayouts = ( - layouts: InventoryDetailLayout[], - metadata: Array | undefined -): InventoryDetailLayout[] => { - if (!metadata) { - return layouts; - } - - const metricMetadata: Array = metadata - .filter(data => data && data.source === 'metrics') - .map(data => data && data.name); - - // After filtering out sections that can't be displayed, a layout may end up empty and can be removed. - const filteredLayouts = layouts - .map(layout => getFilteredLayout(layout, metricMetadata)) - .filter(layout => layout.sections.length > 0); - return filteredLayouts; -}; - -export const getFilteredLayout = ( - layout: InventoryDetailLayout, - metricMetadata: Array -): InventoryDetailLayout => { - // A section is only displayed if at least one of its requirements is met - // All others are filtered out. - const filteredSections = layout.sections.filter( - section => _.intersection(section.requires, metricMetadata).length > 0 - ); - return { ...layout, sections: filteredSections }; -}; diff --git a/x-pack/legacy/plugins/infra/public/containers/metadata/lib/get_filtered_metrics.ts b/x-pack/legacy/plugins/infra/public/containers/metadata/lib/get_filtered_metrics.ts new file mode 100644 index 0000000000000..b485c90700145 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/metadata/lib/get_filtered_metrics.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { InfraMetadataFeature } from '../../../../common/http_api/metadata_api'; +import { InventoryMetric } from '../../../../common/inventory_models/types'; +import { metrics } from '../../../../common/inventory_models/metrics'; + +export const getFilteredMetrics = ( + requiredMetrics: InventoryMetric[], + metadata: Array +) => { + const metricMetadata = metadata + .filter(data => data && data.source === 'metrics') + .map(data => data && data.name); + return requiredMetrics.filter(metric => { + const metricModelCreator = metrics.tsvb[metric]; + // We just need to get a dummy version of the model so we can filter + // using the `requires` attribute. + const metricModel = metricModelCreator('@timestamp', 'test', '>=1m'); + return metricMetadata.some(m => m && metricModel.requires.includes(m)); + }); +}; diff --git a/x-pack/legacy/plugins/infra/public/containers/metadata/use_metadata.ts b/x-pack/legacy/plugins/infra/public/containers/metadata/use_metadata.ts index 718178ecb4fa2..e04aafee56fd2 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metadata/use_metadata.ts +++ b/x-pack/legacy/plugins/infra/public/containers/metadata/use_metadata.ts @@ -10,15 +10,15 @@ import { identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; import { InfraNodeType } from '../../graphql/types'; import { InfraMetadata, InfraMetadataRT } from '../../../common/http_api/metadata_api'; -import { getFilteredLayouts } from './lib/get_filtered_layouts'; import { useHTTPRequest } from '../../hooks/use_http_request'; import { throwErrors, createPlainError } from '../../../common/runtime_types'; -import { InventoryDetailLayout } from '../../../common/inventory_models/types'; +import { InventoryMetric } from '../../../common/inventory_models/types'; +import { getFilteredMetrics } from './lib/get_filtered_metrics'; export function useMetadata( nodeId: string, nodeType: InfraNodeType, - layouts: InventoryDetailLayout[], + requiredMetrics: InventoryMetric[], sourceId: string ) { const decodeResponse = (response: any) => { @@ -44,7 +44,8 @@ export function useMetadata( return { name: (response && response.name) || '', - filteredLayouts: (response && getFilteredLayouts(layouts, response.features)) || [], + filteredRequiredMetrics: + (response && getFilteredMetrics(requiredMetrics, response.features)) || [], error: (error && error.message) || null, loading, metadata: response, diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx index d06abb2aa1060..c1b0814f550a3 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; -import { replaceMetricTimeInQueryString } from '../../containers/metrics/with_metrics_time'; +import { replaceMetricTimeInQueryString } from '../metrics/containers/with_metrics_time'; import { useHostIpToName } from './use_host_ip_to_name'; import { getFromFromLocation, getToFromLocation } from './query_params'; import { LoadingPage } from '../../components/loading_page'; diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx index ad51307641780..a2cebbb96a4f0 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; -import { replaceMetricTimeInQueryString } from '../../containers/metrics/with_metrics_time'; +import { replaceMetricTimeInQueryString } from '../metrics/containers/with_metrics_time'; import { InfraNodeType } from '../../graphql/types'; import { getFromFromLocation, getToFromLocation } from './query_params'; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/sections/chart_section.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx similarity index 72% rename from x-pack/legacy/plugins/infra/public/components/metrics/sections/chart_section.tsx rename to x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx index e265329e6d78c..425b5a43f793f 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics/sections/chart_section.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx @@ -6,7 +6,6 @@ import React, { useCallback } from 'react'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; -import { get } from 'lodash'; import { Axis, Chart, @@ -16,10 +15,8 @@ import { Settings, TooltipValue, } from '@elastic/charts'; -import { EuiPageContentBody, EuiTitle } from '@elastic/eui'; -import { InfraMetricData } from '../../../graphql/types'; -import { getChartTheme } from '../../metrics_explorer/helpers/get_chart_theme'; -import { InfraFormatterType } from '../../../lib/lib'; +import { EuiPageContentBody } from '@elastic/eui'; +import { getChartTheme } from '../../../components/metrics_explorer/helpers/get_chart_theme'; import { SeriesChart } from './series_chart'; import { getFormatter, @@ -31,28 +28,24 @@ import { } from './helpers'; import { ErrorMessage } from './error_message'; import { useKibanaUiSetting } from '../../../utils/use_kibana_ui_setting'; -import { MetricsTimeInput } from '../../../containers/metrics/with_metrics_time'; -import { InventoryDetailSection } from '../../../../common/inventory_models/types'; +import { VisSectionProps } from '../types'; -interface Props { - section: InventoryDetailSection; - metric: InfraMetricData; - onChangeRangeTime?: (time: MetricsTimeInput) => void; - isLiveStreaming?: boolean; - stopLiveStreaming?: () => void; -} - -export const ChartSection = ({ +export const ChartSectionVis = ({ + id, onChangeRangeTime, - section, metric, stopLiveStreaming, isLiveStreaming, -}: Props) => { - const { visConfig } = section; + formatter, + formatterTemplate, + stacked, + seriesOverrides, + type, +}: VisSectionProps) => { + if (!metric || !id) { + return null; + } const [dateFormat] = useKibanaUiSetting('dateFormat'); - const formatter = get(visConfig, 'formatter', InfraFormatterType.number); - const formatterTemplate = get(visConfig, 'formatterTemplate', '{{value}}'); const valueFormatter = useCallback(getFormatter(formatter, formatterTemplate), [ formatter, formatterTemplate, @@ -108,9 +101,6 @@ export const ChartSection = ({ return ( - -

{section.label}

-
( ))} (val: ReactText) => { + if (val == null) { + return ''; + } + const formatter = get(seriesOverrides, [seriesId, 'formatter'], defaultFormatter); + const formatterTemplate = get( + seriesOverrides, + [seriesId, 'formatterTemplate'], + defaultFormatterTemplate + ); + return createFormatter(formatter, formatterTemplate)(val); +}; + +export const GaugesSectionVis = ({ + id, + metric, + seriesOverrides, + formatter, + formatterTemplate, +}: VisSectionProps) => { + if (!metric || !id) { + return null; + } + return ( + + + + {metric.series.map(series => { + const lastDataPoint = last(series.data); + if (!lastDataPoint) { + return null; + } + const formatterFn = getFormatter( + formatter, + formatterTemplate, + seriesOverrides, + series.id + ); + const value = formatterFn(lastDataPoint.value || 0); + const name = getChartName(seriesOverrides, series.id, series.id); + const dataMax = max(series.data.map(d => d.value || 0)); + const gaugeMax = get(seriesOverrides, [series.id, 'gaugeMax'], dataMax); + return ( + + + + {name} + + +

{value}

+
+ +
+
+ ); + })} +
+ +
+ ); +}; + +const GroupBox = euiStyled.div` + display: flex; + flex-flow: row wrap; + justify-content: space-evenly; +`; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/sections/helpers/index.ts b/x-pack/legacy/plugins/infra/public/pages/metrics/components/helpers.ts similarity index 57% rename from x-pack/legacy/plugins/infra/public/components/metrics/sections/helpers/index.ts rename to x-pack/legacy/plugins/infra/public/pages/metrics/components/helpers.ts index 2e8d6b056caef..68c459983bd72 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics/sections/helpers/index.ts +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/helpers.ts @@ -7,19 +7,22 @@ import { ReactText } from 'react'; import Color from 'color'; import { get, first, last, min, max } from 'lodash'; -import { InfraFormatterType } from '../../../../lib/lib'; -import { createFormatter } from '../../../../utils/formatters'; -import { InfraDataSeries, InfraMetricData } from '../../../../graphql/types'; +import { createFormatter } from '../../../utils/formatters'; +import { InfraDataSeries, InfraMetricData } from '../../../graphql/types'; import { - InventoryDetailSection, InventoryVisTypeRT, -} from '../../../../../common/inventory_models/types'; + InventoryFormatterType, + InventoryVisType, +} from '../../../../common/inventory_models/types'; +import { SeriesOverrides } from '../types'; /** * Returns a formatter */ -export const getFormatter = (formatter: InfraFormatterType, template: string) => (val: ReactText) => - val != null ? createFormatter(formatter, template)(val) : ''; +export const getFormatter = ( + formatter: InventoryFormatterType = 'number', + template: string = '{{value}}' +) => (val: ReactText) => (val != null ? createFormatter(formatter, template)(val) : ''); /** * Does a series have more then two points? @@ -47,16 +50,25 @@ export const getMaxMinTimestamp = (metric: InfraMetricData): [number, number] => * Returns the chart name from the visConfig based on the series id, otherwise it * just returns the seriesId */ -export const getChartName = (section: InventoryDetailSection, seriesId: string, label: string) => { - return get(section, ['visConfig', 'seriesOverrides', seriesId, 'name'], label); +export const getChartName = ( + seriesOverrides: SeriesOverrides | undefined, + seriesId: string, + label: string +) => { + if (!seriesOverrides) { + return label; + } + return get(seriesOverrides, [seriesId, 'name'], label); }; /** * Returns the chart color from the visConfig based on the series id, otherwise it * just returns null if the color doesn't exists in the overrides. */ -export const getChartColor = (section: InventoryDetailSection, seriesId: string) => { - const rawColor: string | null = get(section, ['visConfig', 'seriesOverrides', seriesId, 'color']); +export const getChartColor = (seriesOverrides: SeriesOverrides | undefined, seriesId: string) => { + const rawColor: string | null = seriesOverrides + ? get(seriesOverrides, [seriesId, 'color']) + : null; if (!rawColor) { return null; } @@ -67,14 +79,20 @@ export const getChartColor = (section: InventoryDetailSection, seriesId: string) /** * Gets the chart type based on the section and seriesId */ -export const getChartType = (section: InventoryDetailSection, seriesId: string) => { - const value = get(section, ['visConfig', 'type']); - const overrideValue = get(section, ['visConfig', 'seriesOverrides', seriesId, 'type']); +export const getChartType = ( + seriesOverrides: SeriesOverrides | undefined, + type: InventoryVisType | undefined, + seriesId: string +) => { + if (!seriesOverrides || !type) { + return 'line'; + } + const overrideValue = get(seriesOverrides, [seriesId, 'type']); if (InventoryVisTypeRT.is(overrideValue)) { return overrideValue; } - if (InventoryVisTypeRT.is(value)) { - return value; + if (InventoryVisTypeRT.is(type)) { + return type; } return 'line'; }; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/invalid_node.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/invalid_node.tsx similarity index 92% rename from x-pack/legacy/plugins/infra/public/components/metrics/invalid_node.tsx rename to x-pack/legacy/plugins/infra/public/pages/metrics/components/invalid_node.tsx index 673bca91904c7..f9e56791746f5 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics/invalid_node.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/invalid_node.tsx @@ -8,12 +8,12 @@ import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/e import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import euiStyled from '../../../../../common/eui_styled_components'; -import { WithKibanaChrome } from '../../containers/with_kibana_chrome'; +import euiStyled from '../../../../../../common/eui_styled_components'; +import { WithKibanaChrome } from '../../../containers/with_kibana_chrome'; import { ViewSourceConfigurationButton, ViewSourceConfigurationButtonHrefBase, -} from '../../components/source_configuration'; +} from '../../../components/source_configuration'; interface InvalidNodeErrorProps { nodeName: string; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/node_details.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/node_details.tsx similarity index 97% rename from x-pack/legacy/plugins/infra/public/components/metrics/node_details.tsx rename to x-pack/legacy/plugins/infra/public/pages/metrics/components/node_details.tsx index 41331ed73afce..5329ea992c493 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics/node_details.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/node_details.tsx @@ -8,8 +8,8 @@ import React, { useState, useCallback, useMemo } from 'react'; import { EuiButtonIcon, EuiFlexGrid, EuiFlexItem, EuiTitle, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; -import { InfraMetadata } from '../../../common/http_api'; -import euiStyled from '../../../../../common/eui_styled_components'; +import { InfraMetadata } from '../../../../common/http_api'; +import euiStyled from '../../../../../../common/eui_styled_components'; interface Props { metadata?: InfraMetadata | null; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_body.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_body.tsx new file mode 100644 index 0000000000000..81c96c0ffe68d --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_body.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { findLayout } from '../../../../common/inventory_models/layouts'; +import { InventoryItemType } from '../../../../common/inventory_models/types'; +import { InfraMetricData } from '../../../graphql/types'; +import { MetricsTimeInput } from '../containers/with_metrics_time'; +import { InfraLoadingPanel } from '../../../components/loading'; +import { NoData } from '../../../components/empty_states'; + +interface Props { + loading: boolean; + refetch: () => void; + type: InventoryItemType; + metrics: InfraMetricData[]; + onChangeRangeTime?: (time: MetricsTimeInput) => void; + isLiveStreaming?: boolean; + stopLiveStreaming?: () => void; +} + +export const PageBody = ({ + loading, + refetch, + type, + metrics, + onChangeRangeTime, + isLiveStreaming, + stopLiveStreaming, +}: Props) => { + if (loading) { + return ( + + ); + } else if (!loading && metrics && metrics.length === 0) { + return ( + + ); + } + + const Layout = findLayout(type); + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_error.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_error.tsx new file mode 100644 index 0000000000000..69ba80b85e3b2 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_error.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GraphQLFormattedError } from 'graphql'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { ApolloError } from 'apollo-client'; +import { InvalidNodeError } from './invalid_node'; +import { InfraMetricsErrorCodes } from '../../../../common/errors'; +import { DocumentTitle } from '../../../components/document_title'; +import { ErrorPageBody } from '../../error'; + +interface Props { + name: string; + error: ApolloError; +} + +export const PageError = ({ error, name }: Props) => { + const invalidNodeError = error.graphQLErrors.some( + (err: GraphQLFormattedError) => err.code === InfraMetricsErrorCodes.invalid_node + ); + + return ( + <> + + i18n.translate('xpack.infra.metricDetailPage.documentTitleError', { + defaultMessage: '{previousTitle} | Uh oh', + values: { + previousTitle, + }, + }) + } + /> + {invalidNodeError ? ( + + ) : ( + + )} + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/section.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/section.tsx new file mode 100644 index 0000000000000..32d2e2eff8ab9 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/section.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { + useContext, + Children, + isValidElement, + cloneElement, + FunctionComponent, + useMemo, +} from 'react'; +import { EuiTitle } from '@elastic/eui'; +import { SideNavContext, SubNavItem } from '../lib/side_nav_context'; +import { LayoutProps } from '../types'; + +type SectionProps = LayoutProps & { + navLabel: string; + sectionLabel: string; +}; + +export const Section: FunctionComponent = ({ + children, + metrics, + navLabel, + sectionLabel, + onChangeRangeTime, + isLiveStreaming, + stopLiveStreaming, +}) => { + const { addNavItem } = useContext(SideNavContext); + const subNavItems: SubNavItem[] = []; + + const childrenWithProps = useMemo( + () => + Children.map(children, child => { + if (isValidElement(child)) { + const metric = (metrics && metrics.find(m => m.id === child.props.id)) || null; + if (metric) { + subNavItems.push({ + id: child.props.id, + name: child.props.label, + onClick: () => { + const el = document.getElementById(child.props.id); + if (el) { + el.scrollIntoView(); + } + }, + }); + } + return cloneElement(child, { + metrics, + onChangeRangeTime, + isLiveStreaming, + stopLiveStreaming, + }); + } + return null; + }), + [children, metrics, onChangeRangeTime, isLiveStreaming, stopLiveStreaming] + ); + + if (metrics && subNavItems.length) { + addNavItem({ id: navLabel, name: navLabel, items: subNavItems }); + return ( +
+ +

{sectionLabel}

+
+ {childrenWithProps} +
+ ); + } + + return null; +}; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/sections/series_chart.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/series_chart.tsx similarity index 100% rename from x-pack/legacy/plugins/infra/public/components/metrics/sections/series_chart.tsx rename to x-pack/legacy/plugins/infra/public/pages/metrics/components/series_chart.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/side_nav.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/side_nav.tsx new file mode 100644 index 0000000000000..8e922818222b4 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/side_nav.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiHideFor, EuiPageSideBar, EuiShowFor, EuiSideNav } from '@elastic/eui'; +import React, { useState, useCallback } from 'react'; +import euiStyled from '../../../../../../common/eui_styled_components'; +import { NavItem } from '../lib/side_nav_context'; + +interface Props { + loading: boolean; + name: string; + items: NavItem[]; +} + +export const MetricsSideNav = ({ loading, name, items }: Props) => { + const [isOpenOnMobile, setMobileState] = useState(false); + + const toggle = useCallback(() => { + setMobileState(!isOpenOnMobile); + }, [isOpenOnMobile]); + + const content = loading ? null : ; + const mobileContent = loading ? null : ( + + ); + return ( + + + {content} + + {mobileContent} + + ); +}; + +const SideNavContainer = euiStyled.div` + position: fixed; + z-index: 1; + height: 88vh; + padding-left: 16px; + margin-left: -16px; + overflow-y: auto; + overflow-x: hidden; +`; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/sub_section.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/sub_section.tsx new file mode 100644 index 0000000000000..f3db3b1670199 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/sub_section.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { isValidElement, cloneElement, FunctionComponent, Children, useMemo } from 'react'; +import { EuiTitle } from '@elastic/eui'; +import { InventoryMetric } from '../../../../common/inventory_models/types'; +import { LayoutProps } from '../types'; + +type SubSectionProps = LayoutProps & { + id: InventoryMetric; + label?: string; +}; + +export const SubSection: FunctionComponent = ({ + id, + label, + children, + metrics, + onChangeRangeTime, + isLiveStreaming, + stopLiveStreaming, +}) => { + if (!children || !metrics) { + return null; + } + const metric = metrics.find(m => m.id === id); + if (!metric) { + return null; + } + const childrenWithProps = useMemo( + () => + Children.map(children, child => { + if (isValidElement(child)) { + return cloneElement(child, { + metric, + id, + onChangeRangeTime, + isLiveStreaming, + stopLiveStreaming, + }); + } + return null; + }), + [children, metric, id, onChangeRangeTime, isLiveStreaming, stopLiveStreaming] + ); + return ( +
+ {label ? ( + +

{label}

+
+ ) : null} + {childrenWithProps} +
+ ); +}; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/time_controls.test.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.test.tsx similarity index 95% rename from x-pack/legacy/plugins/infra/public/components/metrics/time_controls.test.tsx rename to x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.test.tsx index 61872f52615a0..624a2bb4a6f0f 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics/time_controls.test.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { MetricsTimeControls } from './time_controls'; import { mount } from 'enzyme'; -import { MetricsTimeInput } from '../../containers/metrics/with_metrics_time'; +import { MetricsTimeInput } from '../containers/with_metrics_time'; describe('MetricsTimeControls', () => { it('should set a valid from and to value for Today', () => { diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/time_controls.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.tsx similarity index 92% rename from x-pack/legacy/plugins/infra/public/components/metrics/time_controls.tsx rename to x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.tsx index 7d236cf0a3ea7..d181aa37f59aa 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics/time_controls.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/time_controls.tsx @@ -6,8 +6,8 @@ import { EuiSuperDatePicker, OnRefreshChangeProps, OnTimeChangeProps } from '@elastic/eui'; import React from 'react'; -import euiStyled from '../../../../../common/eui_styled_components'; -import { MetricsTimeInput } from '../../containers/metrics/with_metrics_time'; +import euiStyled from '../../../../../../common/eui_styled_components'; +import { MetricsTimeInput } from '../containers/with_metrics_time'; interface MetricsTimeControlsProps { currentTimeRange: MetricsTimeInput; diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics/metrics.gql_query.ts b/x-pack/legacy/plugins/infra/public/pages/metrics/containers/metrics.gql_query.ts similarity index 100% rename from x-pack/legacy/plugins/infra/public/containers/metrics/metrics.gql_query.ts rename to x-pack/legacy/plugins/infra/public/pages/metrics/containers/metrics.gql_query.ts diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics/metrics_time.test.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/containers/metrics_time.test.tsx similarity index 100% rename from x-pack/legacy/plugins/infra/public/containers/metrics/metrics_time.test.tsx rename to x-pack/legacy/plugins/infra/public/pages/metrics/containers/metrics_time.test.tsx diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics/with_metrics.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics.tsx similarity index 80% rename from x-pack/legacy/plugins/infra/public/containers/metrics/with_metrics.tsx rename to x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics.tsx index bb346553a4bf9..6f7e411628d27 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metrics/with_metrics.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics.tsx @@ -13,9 +13,9 @@ import { InfraNodeType, MetricsQuery, InfraTimerangeInput, -} from '../../graphql/types'; +} from '../../../graphql/types'; import { metricsQuery } from './metrics.gql_query'; -import { InventoryDetailLayout, InventoryMetric } from '../../../common/inventory_models/types'; +import { InventoryMetric, InventoryMetricRT } from '../../../../common/inventory_models/types'; interface WithMetricsArgs { metrics: InfraMetricData[]; @@ -26,7 +26,7 @@ interface WithMetricsArgs { interface WithMetricsProps { children: (args: WithMetricsArgs) => React.ReactNode; - layouts: InventoryDetailLayout[]; + requiredMetrics: InventoryMetric[]; nodeType: InfraNodeType; nodeId: string; cloudId: string; @@ -34,24 +34,20 @@ interface WithMetricsProps { timerange: InfraTimerangeInput; } -const isInfraMetrics = (subject: string[]): subject is InfraMetric[] => { - return subject.every(s => s in InfraMetric); +const isInfraMetrics = (subject: any[]): subject is InfraMetric[] => { + return subject.every(s => InventoryMetricRT.is(s)); }; export const WithMetrics = ({ children, - layouts, + requiredMetrics, sourceId, timerange, nodeType, nodeId, cloudId, }: WithMetricsProps) => { - const metrics = layouts.reduce((acc, item) => { - return acc.concat(item.sections.map(s => s.id)); - }, [] as InventoryMetric[]); - - if (!isInfraMetrics(metrics)) { + if (!isInfraMetrics(requiredMetrics)) { throw new Error( i18n.translate('xpack.infra.invalidInventoryMetricsError', { defaultMessage: 'One of the InfraMetric is invalid', @@ -66,7 +62,7 @@ export const WithMetrics = ({ notifyOnNetworkStatusChange variables={{ sourceId, - metrics, + metrics: requiredMetrics, nodeType, nodeId, cloudId, diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics/with_metrics_time.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics_time.tsx similarity index 98% rename from x-pack/legacy/plugins/infra/public/containers/metrics/with_metrics_time.tsx rename to x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics_time.tsx index 1afc3a04acd65..6a89e75679468 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metrics/with_metrics_time.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics_time.tsx @@ -11,8 +11,8 @@ import moment from 'moment'; import dateMath from '@elastic/datemath'; import * as rt from 'io-ts'; import { isRight } from 'fp-ts/lib/Either'; -import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state'; -import { InfraTimerangeInput } from '../../graphql/types'; +import { replaceStateKeyInQueryString, UrlStateContainer } from '../../../utils/url_state'; +import { InfraTimerangeInput } from '../../../graphql/types'; export interface MetricsTimeInput { from: string; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx index 1996d51b4f26b..643d943273a81 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx @@ -14,34 +14,28 @@ import { EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { GraphQLFormattedError } from 'graphql'; -import React, { useCallback, useContext } from 'react'; +import React, { useContext, useState } from 'react'; import { UICapabilities } from 'ui/capabilities'; import { injectUICapabilities } from 'ui/capabilities/react'; import euiStyled, { EuiTheme, withTheme } from '../../../../../common/eui_styled_components'; -import { InfraMetricsErrorCodes } from '../../../common/errors'; import { AutoSizer } from '../../components/auto_sizer'; import { DocumentTitle } from '../../components/document_title'; import { Header } from '../../components/header'; -import { Metrics } from '../../components/metrics'; -import { InvalidNodeError } from '../../components/metrics/invalid_node'; -import { MetricsSideNav } from '../../components/metrics/side_nav'; -import { MetricsTimeControls } from '../../components/metrics/time_controls'; +import { MetricsSideNav } from './components/side_nav'; +import { MetricsTimeControls } from './components/time_controls'; import { ColumnarPage, PageContent } from '../../components/page'; -import { WithMetrics } from '../../containers/metrics/with_metrics'; -import { - WithMetricsTime, - WithMetricsTimeUrlState, -} from '../../containers/metrics/with_metrics_time'; +import { WithMetrics } from './containers/with_metrics'; +import { WithMetricsTime, WithMetricsTimeUrlState } from './containers/with_metrics_time'; import { InfraNodeType } from '../../graphql/types'; -import { ErrorPageBody } from '../error'; import { withMetricPageProviders } from './page_providers'; import { useMetadata } from '../../containers/metadata/use_metadata'; import { Source } from '../../containers/source'; import { InfraLoadingPanel } from '../../components/loading'; -import { NodeDetails } from '../../components/metrics/node_details'; +import { NodeDetails } from './components/node_details'; import { findInventoryModel } from '../../../common/inventory_models'; -import { InventoryDetailSection } from '../../../common/inventory_models/types'; +import { PageError } from './components/page_error'; +import { NavItem, SideNavContext } from './lib/side_nav_context'; +import { PageBody } from './components/page_body'; const DetailPageContent = euiStyled(PageContent)` overflow: auto; @@ -69,15 +63,26 @@ export const MetricDetail = withMetricPageProviders( const nodeId = match.params.node; const nodeType = match.params.type as InfraNodeType; const inventoryModel = findInventoryModel(nodeType); - const layoutCreator = inventoryModel.layout; const { sourceId } = useContext(Source.Context); - const layouts = layoutCreator(theme); - const { name, filteredLayouts, loading: metadataLoading, cloudId, metadata } = useMetadata( - nodeId, - nodeType, - layouts, - sourceId + const { + name, + filteredRequiredMetrics, + loading: metadataLoading, + cloudId, + metadata, + } = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId); + + const [sideNav, setSideNav] = useState([]); + + const addNavItem = React.useCallback( + (item: NavItem) => { + if (!sideNav.some(n => n.id === item.id)) { + setSideNav([item, ...sideNav]); + } + }, + [sideNav] ); + const breadcrumbs = [ { href: '#/', @@ -88,18 +93,7 @@ export const MetricDetail = withMetricPageProviders( { text: name }, ]; - const handleClick = useCallback( - (section: InventoryDetailSection) => () => { - const id = section.linkToId || section.id; - const el = document.getElementById(id); - if (el) { - el.scrollIntoView(); - } - }, - [] - ); - - if (metadataLoading && !filteredLayouts.length) { + if (metadataLoading && !filteredRequiredMetrics.length) { return ( {({ metrics, error, loading, refetch }) => { if (error) { - const invalidNodeError = error.graphQLErrors.some( - (err: GraphQLFormattedError) => - err.code === InfraMetricsErrorCodes.invalid_node - ); - - return ( - <> - - i18n.translate('xpack.infra.metricDetailPage.documentTitleError', { - defaultMessage: '{previousTitle} | Uh oh', - values: { - previousTitle, - }, - }) - } - /> - {invalidNodeError ? ( - - ) : ( - - )} - - ); + return ; } return ( - + {({ measureRef, bounds: { width = 0 } }) => { const w = width ? `${width}px` : `100%`; @@ -209,19 +175,19 @@ export const MetricDetail = withMetricPageProviders( - 0 && isAutoReloading ? false : loading - } - refetch={refetch} - onChangeRangeTime={setTimeRange} - isLiveStreaming={isAutoReloading} - stopLiveStreaming={() => setAutoReload(false)} - /> + + 0 && isAutoReloading ? false : loading + } + refetch={refetch} + type={nodeType} + metrics={metrics} + onChangeRangeTime={setTimeRange} + isLiveStreaming={isAutoReloading} + stopLiveStreaming={() => setAutoReload(false)} + /> + diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/lib/side_nav_context.ts b/x-pack/legacy/plugins/infra/public/pages/metrics/lib/side_nav_context.ts new file mode 100644 index 0000000000000..3afd91ef59e93 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/lib/side_nav_context.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +export interface SubNavItem { + id: string; + name: string; + onClick: () => void; +} + +export interface NavItem { + id: string | number; + name: string; + items: SubNavItem[]; +} + +export const SideNavContext = React.createContext({ + items: [] as NavItem[], + addNavItem: (item: NavItem) => {}, +}); diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/page_providers.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/page_providers.tsx index 5e43e79ab7c89..0abbd597dd65c 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/page_providers.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/page_providers.tsx @@ -6,7 +6,7 @@ import React from 'react'; -import { MetricsTimeContainer } from '../../containers/metrics/with_metrics_time'; +import { MetricsTimeContainer } from './containers/with_metrics_time'; import { Source } from '../../containers/source'; export const withMetricPageProviders = (Component: React.ComponentType) => ( diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/types.ts b/x-pack/legacy/plugins/infra/public/pages/metrics/types.ts new file mode 100644 index 0000000000000..e752164796150 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/types.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import rt from 'io-ts'; +import { EuiTheme } from '../../../../../common/eui_styled_components'; +import { InfraMetricData } from '../../graphql/types'; +import { InventoryFormatterTypeRT } from '../../../common/inventory_models/types'; +import { MetricsTimeInput } from './containers/with_metrics_time'; + +export interface LayoutProps { + metrics?: InfraMetricData[]; + onChangeRangeTime?: (time: MetricsTimeInput) => void; + isLiveStreaming?: boolean; + stopLiveStreaming?: () => void; +} + +export type LayoutPropsWithTheme = LayoutProps & { theme: EuiTheme }; + +const ChartTypesRT = rt.keyof({ + area: null, + bar: null, + line: null, +}); + +export const SeriesOverridesObjectRT = rt.intersection([ + rt.type({ + color: rt.string, + }), + rt.partial({ + name: rt.string, + formatter: InventoryFormatterTypeRT, + formatterTemplate: rt.string, + gaugeMax: rt.number, + type: ChartTypesRT, + }), +]); + +export const SeriesOverridesRT = rt.record( + rt.string, + rt.union([rt.undefined, SeriesOverridesObjectRT]) +); + +export type SeriesOverrides = rt.TypeOf; + +export const VisSectionPropsRT = rt.partial({ + type: ChartTypesRT, + stacked: rt.boolean, + formatter: InventoryFormatterTypeRT, + formatterTemplate: rt.string, + seriesOverrides: SeriesOverridesRT, +}); + +export type VisSectionProps = rt.TypeOf & { + id?: string; + metric?: InfraMetricData; + onChangeRangeTime?: (time: MetricsTimeInput) => void; + isLiveStreaming?: boolean; + stopLiveStreaming?: () => void; +}; diff --git a/x-pack/legacy/plugins/infra/public/utils/formatters/index.ts b/x-pack/legacy/plugins/infra/public/utils/formatters/index.ts index 4006e672d8b74..efb20e71a9ce4 100644 --- a/x-pack/legacy/plugins/infra/public/utils/formatters/index.ts +++ b/x-pack/legacy/plugins/infra/public/utils/formatters/index.ts @@ -5,26 +5,25 @@ */ import Mustache from 'mustache'; -import { InfraFormatterType, InfraWaffleMapDataFormat } from '../../lib/lib'; +import { InfraWaffleMapDataFormat } from '../../lib/lib'; import { createBytesFormatter } from './bytes'; import { formatNumber } from './number'; import { formatPercent } from './percent'; +import { InventoryFormatterType } from '../../../common/inventory_models/types'; export const FORMATTERS = { - [InfraFormatterType.number]: formatNumber, + number: formatNumber, // Because the implimentation for formatting large numbers is the same as formatting // bytes we are re-using the same code, we just format the number using the abbreviated number format. - [InfraFormatterType.abbreviatedNumber]: createBytesFormatter( - InfraWaffleMapDataFormat.abbreviatedNumber - ), + abbreviatedNumber: createBytesFormatter(InfraWaffleMapDataFormat.abbreviatedNumber), // bytes in bytes formatted string out - [InfraFormatterType.bytes]: createBytesFormatter(InfraWaffleMapDataFormat.bytesDecimal), + bytes: createBytesFormatter(InfraWaffleMapDataFormat.bytesDecimal), // bytes in bits formatted string out - [InfraFormatterType.bits]: createBytesFormatter(InfraWaffleMapDataFormat.bitsDecimal), - [InfraFormatterType.percent]: formatPercent, + bits: createBytesFormatter(InfraWaffleMapDataFormat.bitsDecimal), + percent: formatPercent, }; -export const createFormatter = (format: InfraFormatterType, template: string = '{{value}}') => ( +export const createFormatter = (format: InventoryFormatterType, template: string = '{{value}}') => ( val: string | number ) => { if (val == null) { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index cbb65c61c1bc6..f0c4cb3010eab 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5977,7 +5977,6 @@ "xpack.infra.metrics.emptyViewTitle": "表示するデータがありません。", "xpack.infra.metrics.invalidNodeErrorDescription": "構成をよく確認してください", "xpack.infra.metrics.invalidNodeErrorTitle": "{nodeName} がメトリックデータを収集していないようです", - "xpack.infra.metrics.layoutLabelOverviewTitle": "{layoutLabel} 概要", "xpack.infra.metrics.loadingNodeDataText": "データを読み込み中", "xpack.infra.metrics.refetchButtonLabel": "新規データを確認", "xpack.infra.metricsExplorer.actionsLabel.aria": "{grouping} のアクション", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6bbf7394023be..913cd85579f9d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5918,7 +5918,6 @@ "xpack.infra.metrics.emptyViewTitle": "没有可显示的数据。", "xpack.infra.metrics.invalidNodeErrorDescription": "反复检查您的配置", "xpack.infra.metrics.invalidNodeErrorTitle": "似乎 {nodeName} 未在收集任何指标数据", - "xpack.infra.metrics.layoutLabelOverviewTitle": "{layoutLabel} 概览", "xpack.infra.metrics.loadingNodeDataText": "正在加载数据", "xpack.infra.metrics.refetchButtonLabel": "检查新数据", "xpack.infra.metricsExplorer.actionsLabel.aria": "适用于 {grouping} 的操作", diff --git a/x-pack/test/api_integration/apis/infra/metrics.ts b/x-pack/test/api_integration/apis/infra/metrics.ts index d40359edc5ff2..00c57bcc45e32 100644 --- a/x-pack/test/api_integration/apis/infra/metrics.ts +++ b/x-pack/test/api_integration/apis/infra/metrics.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { first, last } from 'lodash'; -import { metricsQuery } from '../../../../legacy/plugins/infra/public/containers/metrics/metrics.gql_query'; +import { metricsQuery } from '../../../../legacy/plugins/infra/public/pages/metrics/containers/metrics.gql_query'; import { MetricsQuery } from '../../../../legacy/plugins/infra/public/graphql/types'; import { FtrProviderContext } from '../../ftr_provider_context';