[Infra UI] Chart load optimization (#162328)

Closes https://github.com/elastic/kibana/issues/161445

## Summary

This PR enhances the usage of the intersection observer to control the
loading behavior of charts, improving the performance, especially
noticeable during scrolling.


Before 


dfb1d7db-4ddb-41c3-b8ae-3b5bdf3fe36e



After


f348b0b9-4e6f-4163-9eb4-99daea91bbef


Besides, the intersection observer threshold has been set to 0, allowing
charts to start loading as soon as they begin to enter the viewport.


### How to test

- Start a local Kibana instance
- Navigate to `Infrastructure > Hosts`
- Scroll down and confirm that it's not laggy (same for the flyout)

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Carlos Crespo 2023-07-31 16:19:11 +02:00 committed by GitHub
parent 0cc81d73f7
commit 036751463d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 231 additions and 268 deletions

View file

@ -1,63 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { EuiFlexGroup, EuiProgress, EuiFlexItem, EuiLoadingChart, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
export const ChartLoader = ({
children,
loading,
style,
loadedOnce = false,
hasTitle = false,
}: {
style?: React.CSSProperties;
children: React.ReactNode;
loadedOnce: boolean;
loading: boolean;
hasTitle?: boolean;
}) => {
const { euiTheme } = useEuiTheme();
return (
<LoaderContainer>
{loading && (
<EuiProgress
size="xs"
color="accent"
position="absolute"
css={css`
top: ${loadedOnce && hasTitle ? euiTheme.size.l : 0};
z-index: ${Number(euiTheme.levels.header) - 1};
`}
/>
)}
{loading && !loadedOnce ? (
<EuiFlexGroup
style={{ ...style, marginTop: hasTitle ? euiTheme.size.l : 0 }}
justifyContent="center"
alignItems="center"
responsive={false}
>
<EuiFlexItem grow={false}>
<EuiLoadingChart mono size="l" />
</EuiFlexItem>
</EuiFlexGroup>
) : (
children
)}
</LoaderContainer>
);
};
const LoaderContainer = euiStyled.div`
position: relative;
border-radius: ${({ theme }) => theme.eui.euiSizeS};
overflow: hidden;
height: 100%;
`;

View file

@ -1,144 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useEffect, useState, useRef, useCallback } from 'react';
import { Action } from '@kbn/ui-actions-plugin/public';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import type { TimeRange } from '@kbn/es-query';
import { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import { css } from '@emotion/react';
import { useKibanaContextForPlugin } from '../../../hooks/use_kibana';
import { useIntersectedOnce } from '../../../hooks/use_intersection_once';
import { ChartLoader } from './chart_loader';
import type { LensAttributes } from '../types';
export type LensWrapperProps = Pick<
TypedLensByValueInput,
| 'id'
| 'filters'
| 'query'
| 'style'
| 'onBrushEnd'
| 'hidePanelTitles'
| 'overrides'
| 'hidePanelTitles'
| 'disabledActions'
| 'disableTriggers'
> & {
attributes: LensAttributes | null;
dateRange: TimeRange;
extraActions: Action[];
lastReloadRequestTime?: number;
loading?: boolean;
hasTitle?: boolean;
};
export const LensWrapper = React.memo(
({
attributes,
dateRange,
filters,
id,
query,
extraActions,
style,
onBrushEnd,
lastReloadRequestTime,
overrides,
loading = false,
hasTitle = false,
disableTriggers = false,
}: LensWrapperProps) => {
const intersectionRef = useRef(null);
const [loadedOnce, setLoadedOnce] = useState(false);
const [state, setState] = useState({
attributes,
lastReloadRequestTime,
query,
filters,
dateRange,
});
const {
services: { lens },
} = useKibanaContextForPlugin();
const { intersectedOnce, intersection } = useIntersectedOnce(intersectionRef, {
threshold: 1,
});
const EmbeddableComponent = lens.EmbeddableComponent;
useEffect(() => {
if ((intersection?.intersectionRatio ?? 0) === 1) {
setState({
attributes,
lastReloadRequestTime,
query,
filters,
dateRange,
});
}
}, [
attributes,
dateRange,
filters,
intersection?.intersectionRatio,
lastReloadRequestTime,
query,
]);
const isReady = state.attributes && intersectedOnce;
const onLoad = useCallback(() => {
if (!loadedOnce) {
setLoadedOnce(true);
}
}, [loadedOnce]);
return (
<div
ref={intersectionRef}
css={css`
.echLegend .echLegendList {
display: flex;
}
`}
>
<ChartLoader
loading={loading || !isReady}
loadedOnce={loadedOnce}
style={style}
hasTitle={hasTitle}
>
{state.attributes && (
<EmbeddableComponent
id={id}
style={style}
hidePanelTitles={!hasTitle}
attributes={state.attributes}
viewMode={ViewMode.VIEW}
timeRange={state.dateRange}
query={state.query}
filters={state.filters}
extraActions={extraActions}
overrides={overrides}
lastReloadRequestTime={state.lastReloadRequestTime}
executionContext={{
type: 'infrastructure_observability_hosts_view',
name: id,
}}
onBrushEnd={onBrushEnd}
onLoad={onLoad}
disableTriggers={disableTriggers}
/>
)}
</ChartLoader>
</div>
);
}
);

View file

@ -45,7 +45,7 @@ export class XYDataLayer implements ChartLayer<XYDataLayerConfig> {
return this.column[0].getFormulaConfig().label;
}
getBaseColumnColumn(dataView: DataView, options?: XYLayerOptions) {
getBaseLayer(dataView: DataView, options?: XYLayerOptions) {
return {
...getHistogramColumn({
columnName: HISTOGRAM_COLUMN_NAME,
@ -75,7 +75,7 @@ export class XYDataLayer implements ChartLayer<XYDataLayerConfig> {
const baseLayer: PersistedIndexPatternLayer = {
columnOrder: [BREAKDOWN_COLUMN_NAME, HISTOGRAM_COLUMN_NAME],
columns: {
...this.getBaseColumnColumn(dataView, this.layerConfig.options),
...this.getBaseLayer(dataView, this.layerConfig.options),
},
};

View file

@ -12,12 +12,7 @@ import { KPI_CHARTS } from '../../../../../common/visualizations/lens/dashboards
export const KPIGrid = React.memo(({ nodeName, dataView, timeRange: dateRange }: TileProps) => {
return (
<>
<EuiFlexGroup
direction="row"
gutterSize="s"
style={{ flexGrow: 0 }}
data-test-subj="assetDetailsKPIGrid"
>
<EuiFlexGroup direction="row" gutterSize="s" data-test-subj="assetDetailsKPIGrid">
{KPI_CHARTS.map((chartProp, index) => (
<EuiFlexItem key={index}>
<Tile {...chartProp} nodeName={nodeName} dataView={dataView} timeRange={dateRange} />

View file

@ -12,11 +12,10 @@ import styled from 'styled-components';
import type { Action } from '@kbn/ui-actions-plugin/public';
import { TimeRange } from '@kbn/es-query';
import { FormattedMessage } from '@kbn/i18n-react';
import { LensWrapper, TooltipContent } from '../../../../lens';
import type { KPIChartProps } from '../../../../../common/visualizations/lens/dashboards/host/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';
const MIN_HEIGHT = 150;
@ -72,7 +71,6 @@ export const Tile = ({
<EuiPanelStyled
hasShadow={false}
paddingSize={error ? 'm' : 'none'}
style={{ minHeight: MIN_HEIGHT }}
data-test-subj={`assetDetailsKPI-${id}`}
>
{error ? (
@ -109,6 +107,7 @@ export const Tile = ({
dateRange={timeRange}
filters={filters}
loading={loading}
hidePanelTitles
/>
</EuiToolTip>
)}
@ -117,6 +116,7 @@ export const Tile = ({
};
const EuiPanelStyled = styled(EuiPanel)`
min-height: ${MIN_HEIGHT}px;
.echMetric {
border-radius: ${({ theme }) => theme.eui.euiBorderRadius};
pointer-events: none;

View file

@ -12,8 +12,8 @@ import { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { TimeRange } from '@kbn/es-query';
import { FormattedMessage } from '@kbn/i18n-react';
import { LensWrapper } from '../../../../lens/lens_wrapper';
import { buildCombinedHostsFilter } from '../../../../../utils/filters/build';
import { LensWrapper } from '../../../../../common/visualizations/lens/lens_wrapper';
import { useLensAttributes, type Layer } from '../../../../../hooks/use_lens_attributes';
import type { FormulaConfig, XYLayerOptions } from '../../../../../common/visualizations';
@ -109,7 +109,6 @@ export const MetricChart = ({
overrides={overrides}
loading={loading}
disableTriggers
hasTitle
/>
)}
</EuiPanel>

View file

@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n';
import type { DataView } from '@kbn/data-views-plugin/public';
import { TimeRange } from '@kbn/es-query';
import { FormattedMessage } from '@kbn/i18n-react';
import { HostMetricsDocsLink } from '../../../../../common/visualizations/metric_explanation/host_metrics_docs_link';
import { HostMetricsDocsLink } from '../../../../lens';
import { MetricChart, type MetricChartProps } from './metric_chart';
import { hostLensFormulas } from '../../../../../common/visualizations';

View file

@ -0,0 +1,39 @@
/*
* 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, EuiProgress, EuiFlexItem, EuiLoadingChart } from '@elastic/eui';
import { useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
export const ChartLoadingProgress = ({ hasTopMargin = false }: { hasTopMargin?: boolean }) => {
const { euiTheme } = useEuiTheme();
return (
<EuiProgress
size="xs"
color="accent"
position="absolute"
css={css`
top: ${hasTopMargin ? euiTheme.size.l : 0};
z-index: ${Number(euiTheme.levels.header) - 1};
`}
/>
);
};
export const ChartPlaceholder = ({ style }: { style?: React.CSSProperties }) => {
return (
<>
<ChartLoadingProgress hasTopMargin={false} />
<EuiFlexGroup style={style} justifyContent="center" alignItems="center" responsive={false}>
<EuiFlexItem grow={false}>
<EuiLoadingChart mono size="l" />
</EuiFlexItem>
</EuiFlexGroup>
</>
);
};

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
export { ChartPlaceholder } from './chart_placeholder';
export { LensWrapper } from './lens_wrapper';
export { TooltipContent } from './metric_explanation/tooltip_content';
export { HostMetricsDocsLink } from './metric_explanation/host_metrics_docs_link';

View file

@ -0,0 +1,154 @@
/*
* 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, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
import type { Action } from '@kbn/ui-actions-plugin/public';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import type { TimeRange } from '@kbn/es-query';
import { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { useKibanaContextForPlugin } from '../../hooks/use_kibana';
import { ChartLoadingProgress, ChartPlaceholder } from './chart_placeholder';
import { parseDateRange } from '../../utils/datemath';
import { LensAttributes } from '../../common/visualizations';
export type LensWrapperProps = Omit<
TypedLensByValueInput,
'timeRange' | 'attributes' | 'viewMode'
> & {
attributes: LensAttributes | null;
dateRange: TimeRange;
extraActions: Action[];
loading?: boolean;
};
export const LensWrapper = ({
attributes,
dateRange,
filters,
lastReloadRequestTime,
loading,
query,
...props
}: LensWrapperProps) => {
const [intersectionObserverEntry, setIntersectionObserverEntry] =
useState<IntersectionObserverEntry>();
const [embeddableLoaded, setEmbeddableLoaded] = useState(false);
const [state, setState] = useState({
attributes,
dateRange,
filters,
lastReloadRequestTime,
query,
});
const ref = useRef<HTMLDivElement>(null);
const observerRef = useRef(
new IntersectionObserver(([value]) => setIntersectionObserverEntry(value), {
root: ref.current,
})
);
useEffect(() => {
const { current: currentObserver } = observerRef;
currentObserver.disconnect();
const { current } = ref;
if (current) {
currentObserver.observe(current);
}
return () => currentObserver.disconnect();
}, [ref]);
useEffect(() => {
if (intersectionObserverEntry?.isIntersecting) {
setState({
attributes,
dateRange,
filters,
lastReloadRequestTime,
query,
});
}
}, [
attributes,
dateRange,
filters,
intersectionObserverEntry?.isIntersecting,
lastReloadRequestTime,
query,
]);
const onLoad = useCallback(() => {
if (!embeddableLoaded) {
setEmbeddableLoaded(true);
}
}, [embeddableLoaded]);
const parsedDateRange: TimeRange = useMemo(() => {
const { from = state.dateRange.from, to = state.dateRange.to } = parseDateRange(
state.dateRange
);
return { from, to };
}, [state.dateRange]);
const isLoading = loading || !state.attributes;
return (
<Container ref={ref}>
<>
{isLoading && !embeddableLoaded ? (
<ChartPlaceholder style={props.style} />
) : (
<>
{isLoading && <ChartLoadingProgress hasTopMargin={!props.hidePanelTitles} />}
<EmbeddableComponentMemo
{...props}
attributes={state.attributes}
filters={state.filters}
lastReloadRequestTime={state.lastReloadRequestTime}
onLoad={onLoad}
query={state.query}
timeRange={parsedDateRange}
viewMode={ViewMode.VIEW}
/>
</>
)}
</>
</Container>
);
};
const EmbeddableComponentMemo = React.memo(
({
attributes,
...props
}: Omit<TypedLensByValueInput, 'attributes'> & { attributes: LensAttributes | null }) => {
const {
services: { lens },
} = useKibanaContextForPlugin();
const EmbeddableComponent = lens.EmbeddableComponent;
if (!attributes) {
return <ChartPlaceholder style={props.style} />;
}
return <EmbeddableComponent {...props} attributes={attributes} />;
}
);
const Container = euiStyled.div`
position: relative;
border-radius: ${({ theme }) => theme.eui.euiSizeS};
overflow: hidden;
height: 100%;
.echLegend .echLegendList {
display: flex;
}
`;

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 '../../../common/visualizations/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 '../../../common/visualizations/constants';
interface Props extends Pick<HTMLAttributes<HTMLDivElement>, 'style'> {
description: string;

View file

@ -1,25 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { RefObject, useEffect, useState } from 'react';
import useIntersection from 'react-use/lib/useIntersection';
export const useIntersectedOnce = (
ref: RefObject<HTMLElement>,
options: IntersectionObserverInit
) => {
const [intersectedOnce, setIntersectedOnce] = useState(false);
const intersection = useIntersection(ref, options);
useEffect(() => {
if (!intersectedOnce && (intersection?.intersectionRatio ?? 0) > 0) {
setIntersectedOnce(true);
}
}, [intersectedOnce, intersection?.intersectionRatio]);
return { intersectedOnce, intersection };
};

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 '../../../../../common/visualizations/lens/chart_loader';
import { ChartPlaceholder } from '../../../../../components/lens';
export interface Props extends Pick<MetricWTrend, 'title' | 'color' | 'extra' | 'subtitle'> {
id: string;
@ -43,7 +43,9 @@ export const MetricChartWrapper = React.memo(
return (
<EuiPanel hasShadow={false} paddingSize="none" {...props}>
<ChartLoader loading={loading} loadedOnce={loadedOnce.current} style={style}>
{loading && !loadedOnce.current ? (
<ChartPlaceholder style={style} />
) : (
<EuiToolTip
className="eui-fullWidth"
delay="regular"
@ -54,7 +56,7 @@ export const MetricChartWrapper = React.memo(
<Metric id={id} data={[[metricsData]]} />
</KPIChartStyled>
</EuiToolTip>
</ChartLoader>
)}
</EuiPanel>
);
}

View file

@ -13,7 +13,7 @@ import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
import { TOOLTIP } from '../../../../../common/visualizations/lens/dashboards/host/translations';
import { type Props, MetricChartWrapper } from '../chart/metric_chart_wrapper';
import { TooltipContent } from '../../../../../common/visualizations/metric_explanation/tooltip_content';
import { TooltipContent } from '../../../../../components/lens';
const HOSTS_CHART: Omit<Props, 'loading' | 'value' | 'toolTip'> = {
id: `metric-hostCount`,

View file

@ -8,7 +8,7 @@ import React, { CSSProperties } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiSpacer } from '@elastic/eui';
import { HostMetricsDocsLink } from '../../../../../common/visualizations/metric_explanation/host_metrics_docs_link';
import { HostMetricsDocsLink } from '../../../../../components/lens';
import { Tile } from './tile';
import { HostCountProvider } from '../../hooks/use_host_count';
import { HostsTile } from './hosts_tile';
@ -24,12 +24,7 @@ export const KPIGrid = () => {
<HostCountProvider>
<HostMetricsDocsLink />
<EuiSpacer size="s" />
<EuiFlexGroup
direction="row"
gutterSize="s"
style={{ flexGrow: 0 }}
data-test-subj="hostsViewKPIGrid"
>
<EuiFlexGroup direction="row" gutterSize="s" data-test-subj="hostsViewKPIGrid">
<EuiFlexItem>
<HostsTile style={lensStyle} />
</EuiFlexItem>

View file

@ -12,19 +12,18 @@ import { EuiIcon, EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } fro
import styled from 'styled-components';
import { Action } from '@kbn/ui-actions-plugin/public';
import { FormattedMessage } from '@kbn/i18n-react';
import { LensWrapper, TooltipContent } from '../../../../../components/lens';
import { KPIChartProps } from '../../../../../common/visualizations/lens/dashboards/host/kpi_grid_config';
import { buildCombinedHostsFilter } from '../../../../../utils/filters/build';
import { useLensAttributes } from '../../../../../hooks/use_lens_attributes';
import { useMetricsDataViewContext } from '../../hooks/use_data_view';
import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
import { useHostsViewContext } from '../../hooks/use_hosts_view';
import { LensWrapper } from '../../../../../common/visualizations/lens/lens_wrapper';
import { useHostCountContext } from '../../hooks/use_host_count';
import { useAfterLoadedState } from '../../hooks/use_after_loaded_state';
import { TooltipContent } from '../../../../../common/visualizations/metric_explanation/tooltip_content';
import { KPI_CHART_MIN_HEIGHT } from '../../constants';
export const Tile = ({ id, title, layers, style, toolTip, ...props }: KPIChartProps) => {
export const Tile = ({ id, title, layers, style, toolTip }: KPIChartProps) => {
const { searchCriteria, onSubmit } = useUnifiedSearchContext();
const { dataView } = useMetricsDataViewContext();
const { requestTs, hostNodes, loading: hostsLoading } = useHostsViewContext();
@ -149,6 +148,7 @@ export const Tile = ({ id, title, layers, style, toolTip, ...props }: KPIChartPr
query={shouldUseSearchCriteria ? afterLoadedState.query : undefined}
onBrushEnd={handleBrushEnd}
loading={loading}
hidePanelTitles
/>
</div>
</EuiToolTip>
@ -158,7 +158,7 @@ export const Tile = ({ id, title, layers, style, toolTip, ...props }: KPIChartPr
};
const EuiPanelStyled = styled(EuiPanel)`
min-height: ${KPI_CHART_MIN_HEIGHT};
min-height: ${KPI_CHART_MIN_HEIGHT}px;
.echMetric {
border-radius: ${({ theme }) => theme.eui.euiBorderRadius};
pointer-events: none;

View file

@ -8,7 +8,7 @@ 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 { TooltipContent } from '../../../../../components/lens/metric_explanation/tooltip_content';
import { useBoolean } from '../../../../../hooks/use_boolean';
interface Props {

View file

@ -11,7 +11,7 @@ import { EuiIcon, EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } fr
import { css } from '@emotion/react';
import { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import { FormattedMessage } from '@kbn/i18n-react';
import { LensWrapper } from '../../../../../../common/visualizations/lens/lens_wrapper';
import { LensWrapper } from '../../../../../../components/lens';
import { useLensAttributes, Layer } from '../../../../../../hooks/use_lens_attributes';
import { useMetricsDataViewContext } from '../../../hooks/use_data_view';
import { useUnifiedSearchContext } from '../../../hooks/use_unified_search';
@ -130,7 +130,7 @@ export const MetricChart = ({ id, title, layers, overrides }: MetricChartProps)
</EuiFlexGroup>
) : (
<LensWrapper
id={`hostsViewsmetricsChart-${id}`}
id={`hostsViewMetricsChart-${id}`}
attributes={attributes}
style={lensStyle}
extraActions={extraActions}
@ -141,7 +141,6 @@ export const MetricChart = ({ id, title, layers, overrides }: MetricChartProps)
onBrushEnd={handleBrushEnd}
loading={loading}
overrides={overrides}
hasTitle
/>
)}
</EuiPanel>

View file

@ -10,7 +10,7 @@ import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { EuiSpacer } from '@elastic/eui';
import { hostLensFormulas, type XYLayerOptions } from '../../../../../../common/visualizations';
import { HostMetricsDocsLink } from '../../../../../../common/visualizations/metric_explanation/host_metrics_docs_link';
import { HostMetricsDocsLink } from '../../../../../../components/lens';
import { MetricChart, MetricChartProps } from './metric_chart';
const DEFAULT_BREAKDOWN_SIZE = 20;