mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[infra UI] Add overview tab with the first section to asset details view (#160924)
Closes #160376 ## Summary This PR adds the initial version of the Overview tab including KPI tiles and metadata summary. <img width="865" alt="image" src="36b62899
-64e8-4359-a7f5-e09a2a300bba"> The storybook is not showing the lens charts as it will be hard because of the dependencies (we should consider if it is worth the effort to add them there in the future) The other parts look Ok: <img width="2550" alt="image" src="348b868b
-9ed9-4b59-b02c-cb1e91eecb5a"> ## Next steps 👣 There are still some parts that can be addressed separately when other parts are ready: - The date is currently relying on the host date picker (passed as an override) which can be fixed once the date picker is added to the page ## Testing - Go to host view and open the flyout - the default tab is now overview tab - Hint: to see the chart movements better pick a wider time range (5 days for example) <img width="1468" alt="image" src="d31faf71
-5beb-48f2-b20a-7dfcacd52325"> - Show all button in the metadata section should open the metadata tab - Storybook: Use `yarn storybook infra` to run the storybook and check both `Page` and `Flyout`
This commit is contained in:
parent
79f7bb45fd
commit
74a786084f
42 changed files with 758 additions and 176 deletions
|
@ -42,3 +42,5 @@ export const visualizationTypes = {
|
|||
lineChart: LineChart,
|
||||
metricChart: MetricChart,
|
||||
};
|
||||
|
||||
export const HOST_METRICS_DOC_HREF = 'https://ela.st/docs-infra-host-metrics';
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { HostsLensMetricChartFormulas } from '../types';
|
||||
import { TOOLTIP } from './translations';
|
||||
|
||||
export interface KPIChartProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
trendLine?: boolean;
|
||||
backgroundColor: string;
|
||||
type: HostsLensMetricChartFormulas;
|
||||
decimals?: number;
|
||||
toolTip: string;
|
||||
}
|
||||
|
||||
export const KPI_CHARTS: Array<Omit<KPIChartProps, 'loading' | 'subtitle' | 'style'>> = [
|
||||
{
|
||||
type: 'cpuUsage',
|
||||
trendLine: true,
|
||||
backgroundColor: '#F1D86F',
|
||||
title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.cpuUsage.title', {
|
||||
defaultMessage: 'CPU Usage',
|
||||
}),
|
||||
toolTip: TOOLTIP.cpuUsage,
|
||||
},
|
||||
{
|
||||
type: 'normalizedLoad1m',
|
||||
trendLine: true,
|
||||
backgroundColor: '#79AAD9',
|
||||
title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.normalizedLoad1m.title', {
|
||||
defaultMessage: 'Normalized Load',
|
||||
}),
|
||||
toolTip: TOOLTIP.rx,
|
||||
},
|
||||
{
|
||||
type: 'memoryUsage',
|
||||
trendLine: true,
|
||||
backgroundColor: '#A987D1',
|
||||
title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.memoryUsage.title', {
|
||||
defaultMessage: 'Memory Usage',
|
||||
}),
|
||||
toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.memoryUsage.tooltip', {
|
||||
defaultMessage: 'Main memory usage excluding page cache.',
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'diskSpaceUsage',
|
||||
trendLine: true,
|
||||
backgroundColor: '#F5A35C',
|
||||
title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.diskSpaceUsage.title', {
|
||||
defaultMessage: 'Disk Space Usage',
|
||||
}),
|
||||
toolTip: TOOLTIP.tx,
|
||||
},
|
||||
];
|
|
@ -10,10 +10,10 @@ import { Action } from '@kbn/ui-actions-plugin/public';
|
|||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import { BrushTriggerEvent } from '@kbn/charts-plugin/public';
|
||||
import { Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana';
|
||||
import { useIntersectedOnce } from '../../../../../hooks/use_intersection_once';
|
||||
import { LensAttributes } from '../../../../../common/visualizations';
|
||||
import { useIntersectedOnce } from '../../../hooks/use_intersection_once';
|
||||
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
|
||||
import { ChartLoader } from './chart_loader';
|
||||
import type { LensAttributes } from '../types';
|
||||
|
||||
export interface LensWrapperProps {
|
||||
id: string;
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const TOOLTIP = {
|
||||
hostCount: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.hostCount', {
|
||||
defaultMessage: 'Number of hosts returned by your search criteria.',
|
||||
}),
|
||||
|
||||
cpuUsage: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.cpuUsage', {
|
||||
defaultMessage:
|
||||
'Percentage of CPU time spent in states other than Idle and IOWait, normalized by the number of CPU cores. This includes both time spent on user space and kernel space.',
|
||||
}),
|
||||
diskSpaceUsage: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.diskSpaceUsage', {
|
||||
defaultMessage: 'Percentage of disk space used.',
|
||||
}),
|
||||
diskLatency: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.diskLatency', {
|
||||
defaultMessage: 'Time spent to service disk requests.',
|
||||
}),
|
||||
memoryFree: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.memoryFree', {
|
||||
defaultMessage: 'Total available memory including page cache.',
|
||||
}),
|
||||
memoryTotal: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.memoryTotal', {
|
||||
defaultMessage: 'Total memory capacity.',
|
||||
}),
|
||||
memoryUsage: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.memoryUsage', {
|
||||
defaultMessage: 'Percentage of main memory usage excluding page cache.',
|
||||
}),
|
||||
normalizedLoad1m: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.normalizedLoad1m', {
|
||||
defaultMessage: '1 minute load average normalized by the number of CPU cores. ',
|
||||
}),
|
||||
rx: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.rx', {
|
||||
defaultMessage:
|
||||
'Number of bytes which have been received per second on the public interfaces of the hosts.',
|
||||
}),
|
||||
tx: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.tx', {
|
||||
defaultMessage:
|
||||
'Number of bytes which have been sent per second on the public interfaces of the hosts.',
|
||||
}),
|
||||
};
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { EuiLink, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { HOST_METRICS_DOC_HREF } from '../../constants';
|
||||
import { HOST_METRICS_DOC_HREF } from '../constants';
|
||||
|
||||
export const HostMetricsDocsLink = () => {
|
||||
return (
|
|
@ -9,7 +9,7 @@ import React, { HTMLAttributes } from 'react';
|
|||
import { EuiText, EuiLink } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { HOST_METRICS_DOC_HREF } from '../../constants';
|
||||
import { HOST_METRICS_DOC_HREF } from '../constants';
|
||||
|
||||
interface Props extends Pick<HTMLAttributes<HTMLDivElement>, 'style'> {
|
||||
description: string;
|
|
@ -93,6 +93,10 @@ export const DecorateWithKibanaContext: DecoratorFn = (story) => {
|
|||
},
|
||||
},
|
||||
},
|
||||
lens: {
|
||||
navigateToPrefilledEditor: () => {},
|
||||
stateHelperApi: () => new Promise(() => {}),
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -9,6 +9,8 @@ import React, { useState } from 'react';
|
|||
import { EuiButton } from '@elastic/eui';
|
||||
import type { Meta, Story } from '@storybook/react/types-6-0';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { DataViewField } from '@kbn/data-views-plugin/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { AssetDetails } from './asset_details';
|
||||
import { decorateWithGlobalStorybookThemeProviders } from '../../test_utils/use_global_storybook_theme';
|
||||
import { FlyoutTabIds, Tab, type AssetDetailsProps } from './types';
|
||||
|
@ -16,6 +18,13 @@ import { DecorateWithKibanaContext } from './__stories__/decorator';
|
|||
|
||||
const links: AssetDetailsProps['links'] = ['alertRule', 'nodeDetails', 'apmServices', 'uptime'];
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
id: FlyoutTabIds.OVERVIEW,
|
||||
name: i18n.translate('xpack.infra.nodeDetails.tabs.overview.title', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
'data-test-subj': 'hostsView-flyout-tabs-overview',
|
||||
},
|
||||
{
|
||||
id: FlyoutTabIds.METRICS,
|
||||
name: i18n.translate('xpack.infra.nodeDetails.tabs.metrics', {
|
||||
|
@ -98,6 +107,16 @@ const stories: Meta<AssetDetailsProps> = {
|
|||
memoryFree: 34359738368,
|
||||
},
|
||||
overrides: {
|
||||
overview: {
|
||||
dataView: {
|
||||
id: 'default',
|
||||
getFieldByName: () => 'hostname' as unknown as DataViewField,
|
||||
} as unknown as DataView,
|
||||
dateRange: {
|
||||
from: '168363046800',
|
||||
to: '168363046900',
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
showActionsColumn: true,
|
||||
},
|
||||
|
|
|
@ -48,6 +48,7 @@ export const AssetDetails = ({
|
|||
return (
|
||||
<TabSwitcherProvider
|
||||
initialActiveTabId={tabs.length > 0 ? activeTabId ?? tabs[0].id : undefined}
|
||||
onTabsStateChange={onTabsStateChange}
|
||||
>
|
||||
<ContentTemplate
|
||||
header={
|
||||
|
@ -59,7 +60,6 @@ export const AssetDetails = ({
|
|||
tabs={tabs}
|
||||
links={links}
|
||||
overrides={overrides}
|
||||
onTabsStateChange={onTabsStateChange}
|
||||
/>
|
||||
}
|
||||
body={
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import useToggle from 'react-use/lib/useToggle';
|
||||
|
||||
interface ExpandableContentProps {
|
||||
values: string | string[] | undefined;
|
||||
}
|
||||
export const ExpandableContent = (props: ExpandableContentProps) => {
|
||||
const { values } = props;
|
||||
const [isExpanded, toggle] = useToggle(false);
|
||||
|
||||
const list = Array.isArray(values) ? values : [values];
|
||||
const [first, ...others] = list;
|
||||
const hasOthers = others.length > 0;
|
||||
const shouldShowMore = hasOthers && !isExpanded;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="xs" responsive={false} alignItems="baseline" wrap direction="column">
|
||||
<div>
|
||||
{first}
|
||||
{shouldShowMore && (
|
||||
<>
|
||||
{' ... '}
|
||||
<EuiLink data-test-subj="infraExpandableContentCountMoreLink" onClick={toggle}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.nodeDetails.tabs.metadata.seeMore"
|
||||
defaultMessage="+{count} more"
|
||||
values={{
|
||||
count: others.length,
|
||||
}}
|
||||
/>
|
||||
</EuiLink>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isExpanded && others.map((item, index) => <EuiFlexItem key={index}>{item}</EuiFlexItem>)}
|
||||
{hasOthers && isExpanded && (
|
||||
<EuiFlexItem>
|
||||
<EuiLink data-test-subj="infraExpandableContentShowLessLink" onClick={toggle}>
|
||||
{i18n.translate('xpack.infra.nodeDetails.tabs.metadata.seeLess', {
|
||||
defaultMessage: 'Show less',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { useTabSwitcherContext } from '../hooks/use_tab_switcher';
|
||||
import { Anomalies, Metadata, Processes, Osquery, Metrics, Logs } from '../tabs';
|
||||
import { Anomalies, Metadata, Processes, Osquery, Metrics, Logs, Overview } from '../tabs';
|
||||
import { FlyoutTabIds, type TabState, type AssetDetailsProps } from '../types';
|
||||
|
||||
type Props = Pick<
|
||||
|
@ -19,8 +19,8 @@ export const Content = ({
|
|||
overrides,
|
||||
currentTimeRange,
|
||||
node,
|
||||
nodeType = 'host',
|
||||
onTabsStateChange,
|
||||
nodeType = 'host',
|
||||
}: Props) => {
|
||||
const onChange = (state: TabState) => {
|
||||
if (!onTabsStateChange) {
|
||||
|
@ -35,6 +35,15 @@ export const Content = ({
|
|||
<TabPanel activeWhen={FlyoutTabIds.ANOMALIES}>
|
||||
<Anomalies nodeName={node.name} onClose={overrides?.anomalies?.onClose} />
|
||||
</TabPanel>
|
||||
<TabPanel activeWhen={FlyoutTabIds.OVERVIEW}>
|
||||
<Overview
|
||||
currentTimeRange={currentTimeRange}
|
||||
nodeName={node.name}
|
||||
nodeType={nodeType}
|
||||
dataView={overrides?.overview?.dataView}
|
||||
dateRange={overrides?.overview?.dateRange}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel activeWhen={FlyoutTabIds.LOGS}>
|
||||
<Logs
|
||||
nodeName={node.name}
|
||||
|
|
|
@ -30,7 +30,7 @@ import { useTabSwitcherContext } from '../hooks/use_tab_switcher';
|
|||
|
||||
type Props = Pick<
|
||||
AssetDetailsProps,
|
||||
'currentTimeRange' | 'overrides' | 'node' | 'nodeType' | 'links' | 'tabs' | 'onTabsStateChange'
|
||||
'currentTimeRange' | 'overrides' | 'node' | 'nodeType' | 'links' | 'tabs'
|
||||
> & {
|
||||
compact: boolean;
|
||||
};
|
||||
|
@ -45,16 +45,11 @@ export const Header = ({
|
|||
compact,
|
||||
currentTimeRange,
|
||||
overrides,
|
||||
onTabsStateChange,
|
||||
}: Props) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { showTab, activeTabId } = useTabSwitcherContext();
|
||||
|
||||
const onTabClick = (tabId: TabIds) => {
|
||||
if (onTabsStateChange) {
|
||||
onTabsStateChange({ activeTabId: tabId });
|
||||
}
|
||||
|
||||
showTab(tabId);
|
||||
};
|
||||
|
||||
|
|
|
@ -8,9 +8,14 @@
|
|||
import { useState } from 'react';
|
||||
import createContainer from 'constate';
|
||||
import { useLazyRef } from '../../../hooks/use_lazy_ref';
|
||||
import type { TabIds } from '../types';
|
||||
import type { TabIds, TabsStateChangeFn } from '../types';
|
||||
|
||||
export function useTabSwitcher({ initialActiveTabId }: { initialActiveTabId?: TabIds }) {
|
||||
interface TabSwitcherParams {
|
||||
initialActiveTabId?: TabIds;
|
||||
onTabsStateChange?: TabsStateChangeFn;
|
||||
}
|
||||
|
||||
export function useTabSwitcher({ initialActiveTabId, onTabsStateChange }: TabSwitcherParams) {
|
||||
const [activeTabId, setActiveTabId] = useState<TabIds | undefined>(initialActiveTabId);
|
||||
|
||||
// This set keeps track of which tabs content have been rendered the first time.
|
||||
|
@ -20,6 +25,10 @@ export function useTabSwitcher({ initialActiveTabId }: { initialActiveTabId?: Ta
|
|||
const showTab = (tabId: TabIds) => {
|
||||
renderedTabsSet.current.add(tabId); // On a tab click, mark the tab content as allowed to be rendered
|
||||
setActiveTabId(tabId);
|
||||
|
||||
if (onTabsStateChange) {
|
||||
onTabsStateChange({ activeTabId: tabId });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
@ -11,3 +11,4 @@ export { Processes } from './processes/processes';
|
|||
export { Osquery } from './osquery/osquery';
|
||||
export { Metrics } from './metrics/metrics';
|
||||
export { Logs } from './logs/logs';
|
||||
export { Overview } from './overview/overview';
|
||||
|
|
|
@ -7,9 +7,6 @@
|
|||
|
||||
import {
|
||||
EuiText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
EuiIcon,
|
||||
EuiInMemoryTable,
|
||||
EuiSearchBarProps,
|
||||
|
@ -17,12 +14,11 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import useToggle from 'react-use/lib/useToggle';
|
||||
import { debounce } from 'lodash';
|
||||
import { Query } from '@elastic/eui';
|
||||
import useLocalStorage from 'react-use/lib/useLocalStorage';
|
||||
import { AddMetadataFilterButton } from './add_metadata_filter_button';
|
||||
import { ExpandableContent } from '../../components/expandable_content';
|
||||
import { type Field, getRowsWithPins } from './utils';
|
||||
import { AddMetadataPinToRow } from './add_pin_to_row';
|
||||
|
||||
|
@ -194,54 +190,3 @@ export const Table = ({ loading, rows, onSearchChange, search, showActionsColumn
|
|||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface ExpandableContentProps {
|
||||
values: string | string[] | undefined;
|
||||
}
|
||||
const ExpandableContent = (props: ExpandableContentProps) => {
|
||||
const { values } = props;
|
||||
const [isExpanded, toggle] = useToggle(false);
|
||||
|
||||
const list = Array.isArray(values) ? values : [values];
|
||||
const [first, ...others] = list;
|
||||
const hasOthers = others.length > 0;
|
||||
const shouldShowMore = hasOthers && !isExpanded;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
gutterSize={'xs'}
|
||||
responsive={false}
|
||||
alignItems={'baseline'}
|
||||
wrap={true}
|
||||
direction="column"
|
||||
>
|
||||
<div>
|
||||
{first}
|
||||
{shouldShowMore && (
|
||||
<>
|
||||
{' ... '}
|
||||
<EuiLink data-test-subj="infraExpandableContentCountMoreLink" onClick={toggle}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.nodeDetails.tabs.metadata.seeMore"
|
||||
defaultMessage="+{count} more"
|
||||
values={{
|
||||
count: others.length,
|
||||
}}
|
||||
/>
|
||||
</EuiLink>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isExpanded && others.map((item, index) => <EuiFlexItem key={index}>{item}</EuiFlexItem>)}
|
||||
{hasOthers && isExpanded && (
|
||||
<EuiFlexItem>
|
||||
<EuiLink data-test-subj="infraExpandableContentShowLessLink" onClick={toggle}>
|
||||
{i18n.translate('xpack.infra.nodeDetails.tabs.metadata.seeLess', {
|
||||
defaultMessage: 'Show less',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { Tile } from './tile';
|
||||
import { KPI_CHARTS } from '../../../../common/visualizations/lens/kpi_grid_config';
|
||||
import type { KPIProps } from './overview';
|
||||
import type { StringDateRange } from '../../types';
|
||||
|
||||
export interface KPIGridProps extends KPIProps {
|
||||
nodeName: string;
|
||||
dateRange: StringDateRange;
|
||||
}
|
||||
|
||||
export const KPIGrid = React.memo(({ nodeName, dataView, dateRange }: KPIGridProps) => {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup
|
||||
direction="row"
|
||||
gutterSize="s"
|
||||
style={{ flexGrow: 0 }}
|
||||
data-test-subj="assetDetailsKPIGrid"
|
||||
>
|
||||
{KPI_CHARTS.map(({ ...chartProp }) => (
|
||||
<EuiFlexItem key={chartProp.type}>
|
||||
<Tile {...chartProp} nodeName={nodeName} dataView={dataView} dateRange={dateRange} />
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
});
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiButtonEmpty,
|
||||
EuiDescriptionListTitle,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiDescriptionList,
|
||||
EuiDescriptionListDescription,
|
||||
EuiHorizontalRule,
|
||||
EuiLoadingSpinner,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { InfraMetadata } from '../../../../../common/http_api';
|
||||
import { NOT_AVAILABLE_LABEL } from '../../translations';
|
||||
import { useTabSwitcherContext } from '../../hooks/use_tab_switcher';
|
||||
import { FlyoutTabIds } from '../../types';
|
||||
import { ExpandableContent } from '../../components/expandable_content';
|
||||
|
||||
const columnTitles = {
|
||||
hostIp: i18n.translate('xpack.infra.assetDetailsEmbeddable.overview.metadataHostIpHeading', {
|
||||
defaultMessage: 'Host IP',
|
||||
}),
|
||||
hostOsVersion: i18n.translate(
|
||||
'xpack.infra.assetDetailsEmbeddable.overview.metadataHostOsVersionHeading',
|
||||
{
|
||||
defaultMessage: 'Host OS version',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
type MetadataFields = 'hostIp' | 'hostOsVersion';
|
||||
|
||||
const metadataData = (metadataInfo: InfraMetadata['info']) => [
|
||||
{
|
||||
field: 'hostIp',
|
||||
value: metadataInfo?.host?.ip,
|
||||
},
|
||||
{
|
||||
field: 'hostOsVersion',
|
||||
value: metadataInfo?.host?.os?.version,
|
||||
},
|
||||
];
|
||||
|
||||
interface MetadataSummaryProps {
|
||||
metadata: InfraMetadata | null;
|
||||
metadataLoading: boolean;
|
||||
}
|
||||
|
||||
export const MetadataSummary = ({ metadata, metadataLoading }: MetadataSummaryProps) => {
|
||||
const { showTab } = useTabSwitcherContext();
|
||||
|
||||
const onClick = () => {
|
||||
showTab(FlyoutTabIds.METADATA);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="m" responsive={false} wrap justifyContent="spaceBetween">
|
||||
<EuiFlexGroup alignItems="flexStart">
|
||||
{metadataData(metadata?.info).map((metadataValue) => (
|
||||
<EuiFlexItem key={metadataValue.field}>
|
||||
<EuiDescriptionList data-test-subj="infraMetadataSummaryItem" compressed>
|
||||
<EuiDescriptionListTitle
|
||||
css={css`
|
||||
white-space: nowrap;
|
||||
`}
|
||||
>
|
||||
{columnTitles[metadataValue.field as MetadataFields]}
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
{metadataLoading ? (
|
||||
<EuiLoadingSpinner />
|
||||
) : (
|
||||
<ExpandableContent values={metadataValue.value ?? NOT_AVAILABLE_LABEL} />
|
||||
)}
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiDescriptionList>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem grow={false} key="metadata-link">
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="infraMetadataSummaryShowAllMetadataButton"
|
||||
onClick={onClick}
|
||||
size="xs"
|
||||
flush="both"
|
||||
iconSide="right"
|
||||
iconType="sortRight"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.assetDetailsEmbeddable.metadataSummary.showAllMetadataButton"
|
||||
defaultMessage="Show all"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { InventoryItemType } from '../../../../../common/inventory_models/types';
|
||||
import { findInventoryModel } from '../../../../../common/inventory_models';
|
||||
import type { MetricsTimeInput } from '../../../../pages/metrics/metric_detail/hooks/use_metrics_time';
|
||||
import { useMetadata } from '../../hooks/use_metadata';
|
||||
import { useSourceContext } from '../../../../containers/metrics_source';
|
||||
import { MetadataSummary } from './metadata_summary';
|
||||
import { KPIGrid } from './kpi_grid';
|
||||
import type { StringDateRange } from '../../types';
|
||||
|
||||
export interface MetadataSearchUrlState {
|
||||
metadataSearchUrlState: string;
|
||||
setMetadataSearchUrlState: (metadataSearch: { metadataSearch?: string }) => void;
|
||||
}
|
||||
|
||||
export interface KPIProps {
|
||||
dateRange?: StringDateRange;
|
||||
dataView?: DataView;
|
||||
}
|
||||
export interface OverviewProps extends KPIProps {
|
||||
currentTimeRange: MetricsTimeInput;
|
||||
nodeName: string;
|
||||
nodeType: InventoryItemType;
|
||||
}
|
||||
|
||||
const DEFAULT_DATE_RANGE = {
|
||||
from: 'now-15m',
|
||||
to: 'now',
|
||||
mode: 'absolute' as const,
|
||||
};
|
||||
|
||||
export const Overview = ({
|
||||
nodeName,
|
||||
currentTimeRange,
|
||||
nodeType,
|
||||
dateRange,
|
||||
dataView,
|
||||
}: OverviewProps) => {
|
||||
const inventoryModel = findInventoryModel(nodeType);
|
||||
const { sourceId } = useSourceContext();
|
||||
const {
|
||||
loading: metadataLoading,
|
||||
error: fetchMetadataError,
|
||||
metadata,
|
||||
} = useMetadata(nodeName, nodeType, inventoryModel.requiredMetrics, sourceId, currentTimeRange);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<KPIGrid
|
||||
nodeName={nodeName}
|
||||
dateRange={dateRange ?? DEFAULT_DATE_RANGE}
|
||||
dataView={dataView}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{fetchMetadataError ? (
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.infra.assetDetailsEmbeddable.overview.errorTitle', {
|
||||
defaultMessage: 'Sorry, there was an error',
|
||||
})}
|
||||
color="danger"
|
||||
iconType="error"
|
||||
data-test-subj="infraMetadataErrorCallout"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.assetDetailsEmbeddable.overview.errorMessage"
|
||||
defaultMessage="There was an error loading your host metadata. Try to {reload} and open the host details again."
|
||||
values={{
|
||||
reload: (
|
||||
<EuiLink
|
||||
data-test-subj="infraMetadataReloadPageLink"
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
{i18n.translate('xpack.infra.assetDetailsEmbeddable.overview.errorAction', {
|
||||
defaultMessage: 'reload the page',
|
||||
})}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
) : (
|
||||
<MetadataSummary metadata={metadata} metadataLoading={metadataLoading} />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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, { useMemo } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiIcon,
|
||||
EuiPanel,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiI18n,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import type { Action } from '@kbn/ui-actions-plugin/public';
|
||||
import type { KPIChartProps } from '../../../../common/visualizations/lens/kpi_grid_config';
|
||||
import { useLensAttributes } from '../../../../hooks/use_lens_attributes';
|
||||
import { LensWrapper } from '../../../../common/visualizations/lens/lens_wrapper';
|
||||
import { buildCombinedHostsFilter } from '../../../../utils/filters/build';
|
||||
import { TooltipContent } from '../../../../common/visualizations/metric_explanation/tooltip_content';
|
||||
import type { KPIGridProps } from './kpi_grid';
|
||||
|
||||
const MIN_HEIGHT = 150;
|
||||
|
||||
export const Tile = ({
|
||||
title,
|
||||
type,
|
||||
backgroundColor,
|
||||
toolTip,
|
||||
decimals = 1,
|
||||
trendLine = false,
|
||||
nodeName,
|
||||
dateRange,
|
||||
dataView,
|
||||
}: KPIChartProps & KPIGridProps) => {
|
||||
const getSubtitle = () =>
|
||||
i18n.translate('xpack.infra.assetDetailsEmbeddable.overview.metricTrend.subtitle.average', {
|
||||
defaultMessage: 'Average',
|
||||
});
|
||||
|
||||
const { formula, attributes, getExtraActions, error } = useLensAttributes({
|
||||
type,
|
||||
dataView,
|
||||
options: {
|
||||
backgroundColor,
|
||||
decimals,
|
||||
subtitle: getSubtitle(),
|
||||
showTrendLine: trendLine,
|
||||
showTitle: false,
|
||||
title,
|
||||
},
|
||||
visualizationType: 'metricChart',
|
||||
});
|
||||
|
||||
const filters = useMemo(() => {
|
||||
return [
|
||||
buildCombinedHostsFilter({
|
||||
field: 'host.name',
|
||||
values: [nodeName],
|
||||
dataView,
|
||||
}),
|
||||
];
|
||||
}, [dataView, nodeName]);
|
||||
|
||||
const extraActions: Action[] = useMemo(
|
||||
() =>
|
||||
getExtraActions({
|
||||
timeRange: dateRange,
|
||||
filters,
|
||||
}),
|
||||
[filters, getExtraActions, dateRange]
|
||||
);
|
||||
|
||||
const loading = !attributes;
|
||||
|
||||
return (
|
||||
<EuiPanelStyled
|
||||
hasShadow={false}
|
||||
paddingSize={error ? 'm' : 'none'}
|
||||
style={{ minHeight: MIN_HEIGHT }}
|
||||
data-test-subj={`assetDetailsKPI-${type}`}
|
||||
>
|
||||
{error ? (
|
||||
<EuiFlexGroup
|
||||
style={{ height: MIN_HEIGHT, alignContent: 'center' }}
|
||||
gutterSize="xs"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
direction="column"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="warning" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s" textAlign="center">
|
||||
<EuiI18n
|
||||
token="'xpack.infra.assetDetailsEmbeddable.overview.errorOnLoadingLensDependencies'"
|
||||
default="There was an error trying to load Lens Plugin."
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiToolTip
|
||||
delay="regular"
|
||||
content={<TooltipContent formula={formula} description={toolTip} />}
|
||||
anchorClassName="eui-fullWidth"
|
||||
>
|
||||
<LensWrapper
|
||||
id={`assetDetailsKPIGrid${type}Tile`}
|
||||
attributes={attributes}
|
||||
style={{ height: MIN_HEIGHT }}
|
||||
extraActions={extraActions}
|
||||
dateRange={dateRange}
|
||||
filters={filters}
|
||||
loading={loading}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
</EuiPanelStyled>
|
||||
);
|
||||
};
|
||||
|
||||
const EuiPanelStyled = styled(EuiPanel)`
|
||||
.echMetric {
|
||||
border-radius: ${({ theme }) => theme.eui.euiBorderRadius};
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
|
@ -20,6 +20,7 @@ import {
|
|||
import { euiStyled } from '@kbn/kibana-react-plugin/common';
|
||||
import type { ProcessListAPIResponse } from '../../../../../common/http_api';
|
||||
import { STATE_NAMES } from './states';
|
||||
import { NOT_AVAILABLE_LABEL } from '../../translations';
|
||||
|
||||
interface Props {
|
||||
processSummary: ProcessListAPIResponse['summary'];
|
||||
|
@ -30,10 +31,6 @@ type SummaryRecord = {
|
|||
total: number;
|
||||
} & Record<keyof typeof STATE_NAMES, number>;
|
||||
|
||||
const NOT_AVAILABLE_LABEL = i18n.translate('xpack.infra.notAvailableLabel', {
|
||||
defaultMessage: 'N/A',
|
||||
});
|
||||
|
||||
const processSummaryNotAvailable = {
|
||||
total: NOT_AVAILABLE_LABEL,
|
||||
running: NOT_AVAILABLE_LABEL,
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const TOOLTIP = {
|
||||
hostCount: i18n.translate('xpack.infra.assetDetailsEmbeddable.metrics.tooltip.hostCount', {
|
||||
defaultMessage: 'Number of hosts returned by your search criteria.',
|
||||
}),
|
||||
|
||||
cpuUsage: i18n.translate('xpack.infra.assetDetailsEmbeddable.metrics.tooltip.cpuUsage', {
|
||||
defaultMessage:
|
||||
'Percentage of CPU time spent in states other than Idle and IOWait, normalized by the number of CPU cores. This includes both time spent on user space and kernel space.',
|
||||
}),
|
||||
diskSpaceUsage: i18n.translate(
|
||||
'xpack.infra.assetDetailsEmbeddable.metrics.tooltip.diskSpaceUsage',
|
||||
{
|
||||
defaultMessage: 'Percentage of disk space used.',
|
||||
}
|
||||
),
|
||||
diskLatency: i18n.translate('xpack.infra.assetDetailsEmbeddable.metrics.tooltip.diskLatency', {
|
||||
defaultMessage: 'Time spent to service disk requests.',
|
||||
}),
|
||||
memoryFree: i18n.translate('xpack.infra.assetDetailsEmbeddable.metrics.tooltip.memoryFree', {
|
||||
defaultMessage: 'Total available memory including page cache.',
|
||||
}),
|
||||
memoryTotal: i18n.translate('xpack.infra.assetDetailsEmbeddable.metrics.tooltip.memoryTotal', {
|
||||
defaultMessage: 'Total memory capacity.',
|
||||
}),
|
||||
memoryUsage: i18n.translate('xpack.infra.assetDetailsEmbeddable.metrics.tooltip.memoryUsage', {
|
||||
defaultMessage: 'Percentage of main memory usage excluding page cache.',
|
||||
}),
|
||||
normalizedLoad1m: i18n.translate(
|
||||
'xpack.infra.assetDetailsEmbeddable.metrics.tooltip.normalizedLoad1m',
|
||||
{
|
||||
defaultMessage: '1 minute load average normalized by the number of CPU cores. ',
|
||||
}
|
||||
),
|
||||
rx: i18n.translate('xpack.infra.assetDetailsEmbeddable.metrics.tooltip.rx', {
|
||||
defaultMessage:
|
||||
'Number of bytes which have been received per second on the public interfaces of the hosts.',
|
||||
}),
|
||||
tx: i18n.translate('xpack.infra.assetDetailsEmbeddable.metrics.tooltip.tx', {
|
||||
defaultMessage:
|
||||
'Number of bytes which have been sent per second on the public interfaces of the hosts.',
|
||||
}),
|
||||
};
|
||||
|
||||
export const NOT_AVAILABLE_LABEL = i18n.translate(
|
||||
'xpack.infra.assetDetailsEmbeddable.notApplicableLabel',
|
||||
{
|
||||
defaultMessage: 'N/A',
|
||||
}
|
||||
);
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LogViewReference } from '@kbn/logs-shared-plugin/common';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { LogViewReference } from '@kbn/logs-shared-plugin/common';
|
||||
import type { InventoryItemType } from '../../../common/inventory_models/types';
|
||||
import type { InfraAssetMetricType, SnapshotCustomMetricInput } from '../../../common/http_api';
|
||||
|
||||
|
@ -25,6 +26,7 @@ export type HostNodeRow = HostMetadata &
|
|||
};
|
||||
|
||||
export enum FlyoutTabIds {
|
||||
OVERVIEW = 'overview',
|
||||
METRICS = 'metrics',
|
||||
METADATA = 'metadata',
|
||||
PROCESSES = 'processes',
|
||||
|
@ -37,7 +39,17 @@ export enum FlyoutTabIds {
|
|||
|
||||
export type TabIds = `${FlyoutTabIds}`;
|
||||
|
||||
export interface StringDateRange {
|
||||
from: string;
|
||||
to: string;
|
||||
mode?: 'absolute' | 'relative' | undefined;
|
||||
}
|
||||
|
||||
export interface TabState {
|
||||
overview?: {
|
||||
dateRange: StringDateRange;
|
||||
dataView?: DataView;
|
||||
};
|
||||
metadata?: {
|
||||
query?: string;
|
||||
showActionsColumn?: boolean;
|
||||
|
|
|
@ -8,7 +8,7 @@ import React, { useEffect, useRef, CSSProperties } from 'react';
|
|||
import { Chart, Metric, type MetricWNumber, type MetricWTrend } from '@elastic/charts';
|
||||
import { EuiPanel, EuiToolTip } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { ChartLoader } from './chart_loader';
|
||||
import { ChartLoader } from '../../../../../common/visualizations/lens/chart_loader';
|
||||
|
||||
export interface Props extends Pick<MetricWTrend, 'title' | 'color' | 'extra' | 'subtitle'> {
|
||||
id: string;
|
||||
|
|
|
@ -13,6 +13,7 @@ import { HostFlyout, useHostFlyoutUrlState } from '../../hooks/use_host_flyout_u
|
|||
import { AssetDetails } from '../../../../../components/asset_details/asset_details';
|
||||
import { orderedFlyoutTabs } from './tabs';
|
||||
import { useLogViewReference } from '../../hooks/use_log_view_reference';
|
||||
import { useMetricsDataViewContext } from '../../hooks/use_data_view';
|
||||
|
||||
export interface Props {
|
||||
node: HostNodeRow;
|
||||
|
@ -23,6 +24,8 @@ const NODE_TYPE = 'host' as InventoryItemType;
|
|||
|
||||
export const FlyoutWrapper = ({ node, closeFlyout }: Props) => {
|
||||
const { getDateRangeAsTimestamp } = useUnifiedSearchContext();
|
||||
const { searchCriteria } = useUnifiedSearchContext();
|
||||
const { dataView } = useMetricsDataViewContext();
|
||||
const { logViewReference, loading } = useLogViewReference({
|
||||
id: 'hosts-flyout-logs-view',
|
||||
});
|
||||
|
@ -43,6 +46,10 @@ export const FlyoutWrapper = ({ node, closeFlyout }: Props) => {
|
|||
currentTimeRange={currentTimeRange}
|
||||
activeTabId={hostFlyoutState?.tabId}
|
||||
overrides={{
|
||||
overview: {
|
||||
dateRange: searchCriteria.dateRange,
|
||||
dataView,
|
||||
},
|
||||
metadata: {
|
||||
query: hostFlyoutState?.metadataSearch,
|
||||
showActionsColumn: true,
|
||||
|
|
|
@ -9,6 +9,13 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FlyoutTabIds, type Tab } from '../../../../../components/asset_details/types';
|
||||
|
||||
export const orderedFlyoutTabs: Tab[] = [
|
||||
{
|
||||
id: FlyoutTabIds.OVERVIEW,
|
||||
name: i18n.translate('xpack.infra.nodeDetails.tabs.overview.title', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
'data-test-subj': 'hostsView-flyout-tabs-overview',
|
||||
},
|
||||
{
|
||||
id: FlyoutTabIds.METADATA,
|
||||
name: i18n.translate('xpack.infra.nodeDetails.tabs.metadata.title', {
|
||||
|
|
|
@ -9,10 +9,10 @@ import React from 'react';
|
|||
import { hostLensFormulas } from '../../../../../common/visualizations';
|
||||
import { useHostCountContext } from '../../hooks/use_host_count';
|
||||
import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
|
||||
import { TOOLTIP } from '../../translations';
|
||||
import { TOOLTIP } from '../../../../../common/visualizations/lens/translations';
|
||||
|
||||
import { type Props, MetricChartWrapper } from '../chart/metric_chart_wrapper';
|
||||
import { TooltipContent } from '../metric_explanation/tooltip_content';
|
||||
import { TooltipContent } from '../../../../../common/visualizations/metric_explanation/tooltip_content';
|
||||
import { KPIChartProps } from './tile';
|
||||
|
||||
const HOSTS_CHART: Omit<Props, 'loading' | 'value' | 'toolTip'> = {
|
||||
|
|
|
@ -7,60 +7,18 @@
|
|||
import React, { CSSProperties } from 'react';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { KPIChartProps, Tile } from './tile';
|
||||
import { HostMetricsDocsLink } from '../../../../../common/visualizations/metric_explanation/host_metrics_docs_link';
|
||||
import { Tile } from './tile';
|
||||
import { HostCountProvider } from '../../hooks/use_host_count';
|
||||
import { TOOLTIP } from '../../translations';
|
||||
import { HostsTile } from './hosts_tile';
|
||||
import { HostMetricsDocsLink } from '../metric_explanation/host_metrics_docs_link';
|
||||
import { KPI_CHART_MIN_HEIGHT } from '../../constants';
|
||||
import { KPI_CHARTS } from '../../../../../common/visualizations/lens/kpi_grid_config';
|
||||
|
||||
const lensStyle: CSSProperties = {
|
||||
height: KPI_CHART_MIN_HEIGHT,
|
||||
};
|
||||
|
||||
const KPI_CHARTS: Array<Omit<KPIChartProps, 'loading' | 'subtitle' | 'style'>> = [
|
||||
{
|
||||
type: 'cpuUsage',
|
||||
trendLine: true,
|
||||
backgroundColor: '#F1D86F',
|
||||
title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.cpuUsage.title', {
|
||||
defaultMessage: 'CPU Usage',
|
||||
}),
|
||||
toolTip: TOOLTIP.cpuUsage,
|
||||
},
|
||||
{
|
||||
type: 'normalizedLoad1m',
|
||||
trendLine: true,
|
||||
backgroundColor: '#79AAD9',
|
||||
title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.normalizedLoad1m.title', {
|
||||
defaultMessage: 'Normalized Load',
|
||||
}),
|
||||
toolTip: TOOLTIP.rx,
|
||||
},
|
||||
{
|
||||
type: 'memoryUsage',
|
||||
trendLine: true,
|
||||
backgroundColor: '#A987D1',
|
||||
title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.memoryUsage.title', {
|
||||
defaultMessage: 'Memory Usage',
|
||||
}),
|
||||
toolTip: i18n.translate('xpack.infra.hostsViewPage.metricTrend.memoryUsage.tooltip', {
|
||||
defaultMessage: 'Main memory usage excluding page cache.',
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'diskSpaceUsage',
|
||||
trendLine: true,
|
||||
backgroundColor: '#F5A35C',
|
||||
title: i18n.translate('xpack.infra.hostsViewPage.metricTrend.diskSpaceUsage.title', {
|
||||
defaultMessage: 'Disk Space Usage',
|
||||
}),
|
||||
toolTip: TOOLTIP.tx,
|
||||
},
|
||||
];
|
||||
|
||||
export const KPIGrid = () => {
|
||||
return (
|
||||
<HostCountProvider>
|
||||
|
|
|
@ -24,11 +24,11 @@ import { useMetricsDataViewContext } from '../../hooks/use_data_view';
|
|||
import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
|
||||
import { HostsLensMetricChartFormulas } from '../../../../../common/visualizations';
|
||||
import { useHostsViewContext } from '../../hooks/use_hosts_view';
|
||||
import { LensWrapper } from '../chart/lens_wrapper';
|
||||
import { buildCombinedHostsFilter } from '../../utils';
|
||||
import { LensWrapper } from '../../../../../common/visualizations/lens/lens_wrapper';
|
||||
import { buildCombinedHostsFilter } from '../../../../../utils/filters/build';
|
||||
import { useHostCountContext } from '../../hooks/use_host_count';
|
||||
import { useAfterLoadedState } from '../../hooks/use_after_loaded_state';
|
||||
import { TooltipContent } from '../metric_explanation/tooltip_content';
|
||||
import { TooltipContent } from '../../../../../common/visualizations/metric_explanation/tooltip_content';
|
||||
import { KPI_CHART_MIN_HEIGHT } from '../../constants';
|
||||
|
||||
export interface KPIChartProps {
|
||||
|
|
|
@ -8,8 +8,8 @@ import React, { useState, useRef, useCallback, useLayoutEffect } from 'react';
|
|||
import { EuiPopover, EuiIcon, EuiFlexGroup, useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { APP_WRAPPER_CLASS } from '@kbn/core/public';
|
||||
import { TooltipContent } from '../../../../../common/visualizations/metric_explanation/tooltip_content';
|
||||
import { useBoolean } from '../../../../../hooks/use_boolean';
|
||||
import { TooltipContent } from '../metric_explanation/tooltip_content';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
|
|
|
@ -15,7 +15,7 @@ import { useUnifiedSearchContext } from '../../../hooks/use_unified_search';
|
|||
import { useLogsSearchUrlState } from '../../../hooks/use_logs_search_url_state';
|
||||
import { LogsLinkToStream } from './logs_link_to_stream';
|
||||
import { LogsSearchBar } from './logs_search_bar';
|
||||
import { buildCombinedHostsFilter } from '../../../utils';
|
||||
import { buildCombinedHostsFilter } from '../../../../../../utils/filters/build';
|
||||
import { useLogViewReference } from '../../../hooks/use_log_view_reference';
|
||||
|
||||
export const LogsTabContent = () => {
|
||||
|
|
|
@ -22,9 +22,9 @@ import { useMetricsDataViewContext } from '../../../hooks/use_data_view';
|
|||
import { useUnifiedSearchContext } from '../../../hooks/use_unified_search';
|
||||
import { HostsLensLineChartFormulas } from '../../../../../../common/visualizations';
|
||||
import { useHostsViewContext } from '../../../hooks/use_hosts_view';
|
||||
import { buildCombinedHostsFilter } from '../../../utils';
|
||||
import { buildCombinedHostsFilter } from '../../../../../../utils/filters/build';
|
||||
import { useHostsTableContext } from '../../../hooks/use_hosts_table';
|
||||
import { LensWrapper } from '../../chart/lens_wrapper';
|
||||
import { LensWrapper } from '../../../../../../common/visualizations/lens/lens_wrapper';
|
||||
import { useAfterLoadedState } from '../../../hooks/use_after_loaded_state';
|
||||
import { METRIC_CHART_MIN_HEIGHT } from '../../../constants';
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ import React from 'react';
|
|||
import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { HostMetricsDocsLink } from '../../../../../../common/visualizations/metric_explanation/host_metrics_docs_link';
|
||||
import { MetricChart, MetricChartProps } from './metric_chart';
|
||||
import { HostMetricsDocsLink } from '../../metric_explanation/host_metrics_docs_link';
|
||||
|
||||
const DEFAULT_BREAKDOWN_SIZE = 20;
|
||||
const CHARTS_IN_ORDER: Array<Pick<MetricChartProps, 'title' | 'type'> & { fullRow?: boolean }> = [
|
||||
|
|
|
@ -15,7 +15,7 @@ import { HostsState } from './use_unified_search_url_state';
|
|||
import { useHostsViewContext } from './use_hosts_view';
|
||||
import { AlertStatus } from '../types';
|
||||
import { ALERT_STATUS_QUERY } from '../constants';
|
||||
import { buildCombinedHostsFilter } from '../utils';
|
||||
import { buildCombinedHostsFilter } from '../../../../utils/filters/build';
|
||||
|
||||
export interface AlertsEsQuery {
|
||||
bool: BoolQuery;
|
||||
|
|
|
@ -16,7 +16,7 @@ import { useUrlState } from '../../../../utils/use_url_state';
|
|||
|
||||
export const DEFAULT_STATE: HostFlyout = {
|
||||
itemId: '',
|
||||
tabId: FlyoutTabIds.METADATA,
|
||||
tabId: FlyoutTabIds.OVERVIEW,
|
||||
processSearch: undefined,
|
||||
metadataSearch: undefined,
|
||||
};
|
||||
|
@ -45,6 +45,7 @@ export const useHostFlyoutUrlState = (): [HostFlyoutUrl, SetHostFlyoutState] =>
|
|||
};
|
||||
|
||||
const FlyoutTabIdRT = rt.union([
|
||||
rt.literal(FlyoutTabIds.OVERVIEW),
|
||||
rt.literal(FlyoutTabIds.METADATA),
|
||||
rt.literal(FlyoutTabIds.PROCESSES),
|
||||
rt.literal(FlyoutTabIds.LOGS),
|
||||
|
|
|
@ -30,8 +30,9 @@ import { useHostsViewContext } from './use_hosts_view';
|
|||
import { useUnifiedSearchContext } from './use_unified_search';
|
||||
import { useMetricsDataViewContext } from './use_data_view';
|
||||
import { ColumnHeader } from '../components/table/column_header';
|
||||
import { TOOLTIP, TABLE_COLUMN_LABEL } from '../translations';
|
||||
import { buildCombinedHostsFilter } from '../utils';
|
||||
import { TABLE_COLUMN_LABEL } from '../translations';
|
||||
import { TOOLTIP } from '../../../../common/visualizations/lens/translations';
|
||||
import { buildCombinedHostsFilter } from '../../../../utils/filters/build';
|
||||
|
||||
/**
|
||||
* Columns and items types
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
// import { useMemo } from 'react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { DEFAULT_LOG_VIEW, LogViewReference } from '@kbn/logs-shared-plugin/common';
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
type HostsState,
|
||||
type StringDateRangeTimestamp,
|
||||
} from './use_unified_search_url_state';
|
||||
import { retrieveFieldsFromFilter } from '../utils';
|
||||
import { retrieveFieldsFromFilter } from '../../../../utils/filters/build';
|
||||
|
||||
const buildQuerySubmittedPayload = (
|
||||
hostState: HostsState & { parsedDateRange: StringDateRangeTimestamp }
|
||||
|
|
|
@ -7,43 +7,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const TOOLTIP = {
|
||||
hostCount: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.hostCount', {
|
||||
defaultMessage: 'Number of hosts returned by your search criteria.',
|
||||
}),
|
||||
|
||||
cpuUsage: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.cpuUsage', {
|
||||
defaultMessage:
|
||||
'Percentage of CPU time spent in states other than Idle and IOWait, normalized by the number of CPU cores. This includes both time spent on user space and kernel space.',
|
||||
}),
|
||||
diskSpaceUsage: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.diskSpaceUsage', {
|
||||
defaultMessage: 'Percentage of disk space used.',
|
||||
}),
|
||||
diskLatency: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.diskLatency', {
|
||||
defaultMessage: 'Time spent to service disk requests.',
|
||||
}),
|
||||
memoryFree: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.memoryFree', {
|
||||
defaultMessage: 'Total available memory including page cache.',
|
||||
}),
|
||||
memoryTotal: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.memoryTotal', {
|
||||
defaultMessage: 'Total memory capacity.',
|
||||
}),
|
||||
memoryUsage: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.memoryUsage', {
|
||||
defaultMessage: 'Percentage of main memory usage excluding page cache.',
|
||||
}),
|
||||
normalizedLoad1m: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.normalizedLoad1m', {
|
||||
defaultMessage: '1 minute load average normalized by the number of CPU cores. ',
|
||||
}),
|
||||
rx: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.rx', {
|
||||
defaultMessage:
|
||||
'Number of bytes which have been received per second on the public interfaces of the hosts.',
|
||||
}),
|
||||
tx: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.tx', {
|
||||
defaultMessage:
|
||||
'Number of bytes which have been sent per second on the public interfaces of the hosts.',
|
||||
}),
|
||||
};
|
||||
|
||||
export const TABLE_COLUMN_LABEL = {
|
||||
title: i18n.translate('xpack.infra.hostsViewPage.table.nameColumnHeader', {
|
||||
defaultMessage: 'Name',
|
||||
|
|
|
@ -266,10 +266,41 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Overview Tab', () => {
|
||||
it('should render 4 metrics trend tiles', async () => {
|
||||
const hosts = await pageObjects.infraHostsView.getAllKPITiles();
|
||||
expect(hosts.length).to.equal(5);
|
||||
});
|
||||
|
||||
[
|
||||
{ metric: 'cpuUsage', value: '13.9%' },
|
||||
{ metric: 'normalizedLoad1m', value: '18.8%' },
|
||||
{ metric: 'memoryUsage', value: '94.9%' },
|
||||
{ metric: 'diskSpaceUsage', value: 'N/A' },
|
||||
].forEach(({ metric, value }) => {
|
||||
it(`${metric} tile should show ${value}`, async () => {
|
||||
await retry.try(async () => {
|
||||
const tileValue = await pageObjects.infraHostsView.getAssetDetailsKPITileValue(
|
||||
metric
|
||||
);
|
||||
expect(tileValue).to.eql(value);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should navigate to metadata tab', async () => {
|
||||
await pageObjects.infraHostsView.clickShowAllMetadataOverviewTab();
|
||||
await pageObjects.header.waitUntilLoadingHasFinished();
|
||||
await pageObjects.infraHostsView.metadataTableExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Metadata Tab', () => {
|
||||
it('should render metadata tab, pin/unpin row, add and remove filter', async () => {
|
||||
await pageObjects.infraHostsView.clickMetadataFlyoutTab();
|
||||
|
||||
const metadataTab = await pageObjects.infraHostsView.getMetadataTabName();
|
||||
expect(metadataTab).to.contain('Metadata');
|
||||
await pageObjects.infraHostsView.metadataTableExist();
|
||||
|
||||
// Add Pin
|
||||
await pageObjects.infraHostsView.clickAddMetadataPin();
|
||||
|
|
|
@ -40,10 +40,18 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) {
|
|||
return testSubjects.click('euiFlyoutCloseButton');
|
||||
},
|
||||
|
||||
async clickMetadataFlyoutTab() {
|
||||
return testSubjects.click('hostsView-flyout-tabs-metadata');
|
||||
},
|
||||
|
||||
async clickProcessesFlyoutTab() {
|
||||
return testSubjects.click('hostsView-flyout-tabs-processes');
|
||||
},
|
||||
|
||||
async clickShowAllMetadataOverviewTab() {
|
||||
return testSubjects.click('infraMetadataSummaryShowAllMetadataButton');
|
||||
},
|
||||
|
||||
async clickLogsFlyoutTab() {
|
||||
return testSubjects.click('hostsView-flyout-tabs-logs');
|
||||
},
|
||||
|
@ -188,10 +196,21 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) {
|
|||
},
|
||||
|
||||
// Flyout Tabs
|
||||
async getAssetDetailsKPITileValue(type: string) {
|
||||
const container = await testSubjects.find('assetDetailsKPIGrid');
|
||||
const element = await container.findByTestSubject(`assetDetailsKPI-${type}`);
|
||||
const div = await element.findByClassName('echMetricText__value');
|
||||
return div.getAttribute('title');
|
||||
},
|
||||
|
||||
getMetadataTab() {
|
||||
return testSubjects.find('hostsView-flyout-tabs-metadata');
|
||||
},
|
||||
|
||||
metadataTableExist() {
|
||||
return testSubjects.exists('infraMetadataTable');
|
||||
},
|
||||
|
||||
async getMetadataTabName() {
|
||||
const tabElement = await this.getMetadataTab();
|
||||
const tabTitle = await tabElement.findByClassName('euiTab__content');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue