[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:
jennypavlova 2023-07-07 17:14:04 +02:00 committed by GitHub
parent 79f7bb45fd
commit 74a786084f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 758 additions and 176 deletions

View file

@ -42,3 +42,5 @@ export const visualizationTypes = {
lineChart: LineChart,
metricChart: MetricChart,
};
export const HOST_METRICS_DOC_HREF = 'https://ela.st/docs-infra-host-metrics';

View file

@ -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,
},
];

View file

@ -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;

View file

@ -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.',
}),
};

View file

@ -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 (

View file

@ -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;

View file

@ -93,6 +93,10 @@ export const DecorateWithKibanaContext: DecoratorFn = (story) => {
},
},
},
lens: {
navigateToPrefilledEditor: () => {},
stateHelperApi: () => new Promise(() => {}),
},
};
return (

View file

@ -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,
},

View file

@ -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={

View file

@ -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>
);
};

View file

@ -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}

View file

@ -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);
};

View file

@ -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 {

View file

@ -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';

View file

@ -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>
);
};

View file

@ -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>
</>
);
});

View file

@ -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" />
</>
);
};

View file

@ -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>
);
};

View file

@ -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;
}
`;

View file

@ -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,

View file

@ -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',
}
);

View file

@ -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;

View file

@ -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;

View file

@ -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,

View file

@ -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', {

View file

@ -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'> = {

View file

@ -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>

View file

@ -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 {

View file

@ -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;

View file

@ -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 = () => {

View file

@ -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';

View file

@ -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 }> = [

View file

@ -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;

View file

@ -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),

View file

@ -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

View file

@ -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';

View file

@ -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 }

View file

@ -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',

View file

@ -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();

View file

@ -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');