mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Infrastructure UI] Improve Hosts View page performance (#160406)
## Summary This PR focuses on enhancing the performance of the Hosts View page to improve the user experience. The main performance detractors were identified as multiple unnecessary calls to the data view's toSpec() function during chart rendering and excessive re-rendering of charts whenever there were changes on the page. To address these issues, the following optimizations were implemented: ### Caching the toSpec() Result Previously, the toSpec() function was called multiple times for each chart, resulting in heavy computation. To optimize this, caching mechanism was introduced that stores the result of the toSpec() function. This prevents unnecessary and repetitive computations, significantly improving the chart attributes generation performance. _Before_ <img width="900" alt="image" src="c9ed1a0e
-c99b-4a09-a091-562739f69abb"> _After_ <img width="900" alt="image" src="7d390f22
-2696-48d5-bb41-df849abd6f1b"> To optimize the chart attributes generation, the `toSpec()` result is now cached. Preventing this heavy operation from performing unnecessarily ### Reducing Lens Re-renders In the previous implementation, metric charts and KPIs were re-rendered excessively whenever there were changes on the page, impacting performance. By implementing memoization techniques, unnecessary re-renders were solved, resulting in smoother and faster rendering of metric charts and KPIs. **_Metric charts_** _Before_ <img width="900" alt="image" src="fdb3ac05
-1c50-4b2d-bde6-68a9d1601444"> _After_ <img width="900" alt="image" src="c2c5ade9
-96df-4c41-be2a-d5323e8fc44d"> **_KPIs_** _Before_ <img width="900" alt="image" src="75abe5ae
-7c2e-4cee-b816-07f31e542d7f"> _After_ <img width="900" alt="image" src="bdf32738
-b11d-4da8-b0d7-6792de9ed0a7"> ### Overall performance By implementing the above optimizations, the overall performance of the Hosts View page has been greatly improved. The changes have significantly reduced delays and improved the responsiveness of buttons, checkboxes, and tab interactions. _Before_ <img width="900" alt="image" src="c390cdc8
-4421-46ee-9ea0-dc28ef1efd97"> _After_ <img width="900" alt="image" src="771ee718
-fd58-49e0-a377-ce1e187cec3a"> ### How to test - Start a local Kibana instance - Navigate to `Infrastructure > Hosts` - Click through buttons, checkboxes tabs and check if there are delays for the click actions to be performed --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
efac02dc32
commit
079763bfff
22 changed files with 500 additions and 389 deletions
|
@ -19,4 +19,4 @@ export type {
|
|||
|
||||
export { hostLensFormulas, visualizationTypes } from './constants';
|
||||
|
||||
export { buildLensAttributes } from './lens/build_lens_attributes';
|
||||
export { LensAttributesBuilder } from './lens/lens_attributes_builder';
|
||||
|
|
|
@ -1,29 +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 type { LensAttributes, TVisualization, VisualizationAttributes } from '../types';
|
||||
|
||||
export const buildLensAttributes = <T extends VisualizationAttributes<TVisualization>>(
|
||||
visualization: T
|
||||
): LensAttributes => {
|
||||
return {
|
||||
title: visualization.getTitle(),
|
||||
visualizationType: visualization.getVisualizationType(),
|
||||
references: visualization.getReferences(),
|
||||
state: {
|
||||
datasourceStates: {
|
||||
formBased: {
|
||||
layers: visualization.getLayers(),
|
||||
},
|
||||
},
|
||||
internalReferences: visualization.getReferences(),
|
||||
filters: visualization.getFilters(),
|
||||
query: { language: 'kuery', query: '' },
|
||||
visualization: visualization.getVisualizationState(),
|
||||
adHocDataViews: visualization.getAdhocDataView(),
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { DataViewSpec, DataView } from '@kbn/data-plugin/common';
|
||||
|
||||
export const DEFAULT_AD_HOC_DATA_VIEW_ID = 'infra_lens_ad_hoc_default';
|
||||
|
||||
export class DataViewCache {
|
||||
private static instance: DataViewCache;
|
||||
private cache = new Map<string, DataViewSpec>();
|
||||
private capacity: number;
|
||||
|
||||
private constructor(capacity: number) {
|
||||
this.capacity = capacity;
|
||||
this.cache = new Map<string, DataViewSpec>();
|
||||
}
|
||||
|
||||
public static getInstance(capacity: number = 10): DataViewCache {
|
||||
if (!DataViewCache.instance) {
|
||||
DataViewCache.instance = new DataViewCache(capacity);
|
||||
}
|
||||
return DataViewCache.instance;
|
||||
}
|
||||
|
||||
public getSpec(dataView: DataView): DataViewSpec {
|
||||
const key = dataView.id ?? DEFAULT_AD_HOC_DATA_VIEW_ID;
|
||||
const spec = this.cache.get(key);
|
||||
|
||||
if (!spec) {
|
||||
const result = dataView.toSpec();
|
||||
this.setSpec(key, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
private setSpec(key: string, value: DataViewSpec): void {
|
||||
if (this.cache.size >= this.capacity) {
|
||||
const lruKey = this.cache.keys().next().value;
|
||||
this.cache.delete(lruKey);
|
||||
}
|
||||
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
}
|
|
@ -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 type {
|
||||
LensAttributes,
|
||||
TVisualization,
|
||||
VisualizationAttributes,
|
||||
VisualizationAttributesBuilder,
|
||||
} from '../types';
|
||||
import { DataViewCache } from './data_view_cache';
|
||||
import { getAdhocDataView } from './utils';
|
||||
|
||||
export class LensAttributesBuilder<T extends VisualizationAttributes<TVisualization>>
|
||||
implements VisualizationAttributesBuilder
|
||||
{
|
||||
private dataViewCache: DataViewCache;
|
||||
constructor(private visualization: T) {
|
||||
this.dataViewCache = DataViewCache.getInstance();
|
||||
}
|
||||
|
||||
build(): LensAttributes {
|
||||
return {
|
||||
title: this.visualization.getTitle(),
|
||||
visualizationType: this.visualization.getVisualizationType(),
|
||||
references: this.visualization.getReferences(),
|
||||
state: {
|
||||
datasourceStates: {
|
||||
formBased: {
|
||||
layers: this.visualization.getLayers(),
|
||||
},
|
||||
},
|
||||
internalReferences: this.visualization.getReferences(),
|
||||
filters: this.visualization.getFilters(),
|
||||
query: { language: 'kuery', query: '' },
|
||||
visualization: this.visualization.getVisualizationState(),
|
||||
adHocDataViews: getAdhocDataView(
|
||||
this.dataViewCache.getSpec(this.visualization.getDataView())
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
|
@ -89,10 +89,10 @@ export const getDefaultReferences = (
|
|||
];
|
||||
};
|
||||
|
||||
export const getAdhocDataView = (dataView: DataView): Record<string, DataViewSpec> => {
|
||||
export const getAdhocDataView = (dataViewSpec: DataViewSpec): Record<string, DataViewSpec> => {
|
||||
return {
|
||||
[dataView.id ?? DEFAULT_AD_HOC_DATA_VIEW_ID]: {
|
||||
...dataView.toSpec(),
|
||||
[dataViewSpec.id ?? DEFAULT_AD_HOC_DATA_VIEW_ID]: {
|
||||
...dataViewSpec,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,11 +12,10 @@ import type {
|
|||
XYState,
|
||||
} from '@kbn/lens-plugin/public';
|
||||
import type { SavedObjectReference } from '@kbn/core-saved-objects-common';
|
||||
import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import {
|
||||
DEFAULT_LAYER_ID,
|
||||
getAdhocDataView,
|
||||
getBreakdownColumn,
|
||||
getDefaultReferences,
|
||||
getHistogramColumn,
|
||||
|
@ -101,8 +100,8 @@ export class LineChart implements VisualizationAttributes<XYState> {
|
|||
];
|
||||
}
|
||||
|
||||
getAdhocDataView(): Record<string, DataViewSpec> {
|
||||
return getAdhocDataView(this.dataView);
|
||||
getDataView(): DataView {
|
||||
return this.dataView;
|
||||
}
|
||||
|
||||
getTitle(): string {
|
||||
|
|
|
@ -12,14 +12,9 @@ import {
|
|||
PersistedIndexPatternLayer,
|
||||
} from '@kbn/lens-plugin/public';
|
||||
import type { SavedObjectReference } from '@kbn/core-saved-objects-common';
|
||||
import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import {
|
||||
DEFAULT_LAYER_ID,
|
||||
getAdhocDataView,
|
||||
getDefaultReferences,
|
||||
getHistogramColumn,
|
||||
} from '../utils';
|
||||
import { DEFAULT_LAYER_ID, getDefaultReferences, getHistogramColumn } from '../utils';
|
||||
|
||||
import type {
|
||||
VisualizationAttributes,
|
||||
|
@ -147,8 +142,8 @@ export class MetricChart implements VisualizationAttributes<MetricVisualizationS
|
|||
];
|
||||
}
|
||||
|
||||
getAdhocDataView(): Record<string, DataViewSpec> {
|
||||
return getAdhocDataView(this.dataView);
|
||||
getDataView(): DataView {
|
||||
return this.dataView;
|
||||
}
|
||||
|
||||
getTitle(): string {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { SavedObjectReference } from '@kbn/core-saved-objects-common';
|
||||
import type { DataViewSpec } from '@kbn/data-views-plugin/common';
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { DataViewBase, Filter } from '@kbn/es-query';
|
||||
import {
|
||||
FormBasedPersistedState,
|
||||
|
@ -53,7 +53,11 @@ export interface VisualizationAttributes<T extends TVisualization> {
|
|||
getVisualizationState(): T;
|
||||
getReferences(): SavedObjectReference[];
|
||||
getFilters(): Filter[];
|
||||
getAdhocDataView(): Record<string, DataViewSpec>;
|
||||
getDataView(): DataView;
|
||||
}
|
||||
|
||||
export interface VisualizationAttributesBuilder {
|
||||
build(): LensAttributes;
|
||||
}
|
||||
|
||||
export type Formula = Parameters<FormulaPublicApi['insertOrReplaceFormulaColumn']>[1];
|
||||
|
|
|
@ -209,6 +209,6 @@ describe('useHostTable hook', () => {
|
|||
],
|
||||
});
|
||||
|
||||
expect(extraActions.openInLens).not.toBeNull();
|
||||
expect(extraActions).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,29 +5,30 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { Filter, Query, TimeRange } from '@kbn/es-query';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
|
||||
import type { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { InfraClientSetupDeps } from '../types';
|
||||
import {
|
||||
buildLensAttributes,
|
||||
HostsLensFormulas,
|
||||
HostsLensMetricChartFormulas,
|
||||
HostsLensLineChartFormulas,
|
||||
LineChartOptions,
|
||||
MetricChartOptions,
|
||||
type HostsLensFormulas,
|
||||
type HostsLensMetricChartFormulas,
|
||||
type HostsLensLineChartFormulas,
|
||||
type LineChartOptions,
|
||||
type MetricChartOptions,
|
||||
LensAttributesBuilder,
|
||||
LensAttributes,
|
||||
hostLensFormulas,
|
||||
visualizationTypes,
|
||||
} from '../common/visualizations';
|
||||
import { useLazyRef } from './use_lazy_ref';
|
||||
|
||||
type Options = LineChartOptions | MetricChartOptions;
|
||||
interface UseLensAttributesBaseParams<T extends HostsLensFormulas, O extends Options> {
|
||||
dataView: DataView | undefined;
|
||||
dataView?: DataView;
|
||||
type: T;
|
||||
options?: O;
|
||||
}
|
||||
|
@ -58,92 +59,99 @@ export const useLensAttributes = ({
|
|||
const { navigateToPrefilledEditor } = lens;
|
||||
const { value, error } = useAsync(lens.stateHelperApi, [lens]);
|
||||
const { formula: formulaAPI } = value ?? {};
|
||||
const lensChartConfig = hostLensFormulas[type];
|
||||
|
||||
const attributes = useMemo(() => {
|
||||
const lensChartConfig = hostLensFormulas[type];
|
||||
const Chart = visualizationTypes[visualizationType];
|
||||
|
||||
const attributes = useLazyRef(() => {
|
||||
if (!dataView || !formulaAPI) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const VisualizationType = visualizationTypes[visualizationType];
|
||||
|
||||
const visualizationAttributes = buildLensAttributes(
|
||||
new VisualizationType(lensChartConfig, dataView, formulaAPI, options)
|
||||
const builder = new LensAttributesBuilder(
|
||||
new Chart(lensChartConfig, dataView, formulaAPI, options)
|
||||
);
|
||||
|
||||
return visualizationAttributes;
|
||||
}, [dataView, formulaAPI, options, visualizationType, lensChartConfig]);
|
||||
return builder.build();
|
||||
});
|
||||
|
||||
const injectFilters = ({
|
||||
filters,
|
||||
query = { language: 'kuery', query: '' },
|
||||
}: {
|
||||
filters: Filter[];
|
||||
query?: Query;
|
||||
}): LensAttributes | null => {
|
||||
if (!attributes) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...attributes,
|
||||
state: {
|
||||
...attributes.state,
|
||||
query,
|
||||
filters: [...attributes.state.filters, ...filters],
|
||||
},
|
||||
};
|
||||
};
|
||||
const injectFilters = useCallback(
|
||||
({
|
||||
filters,
|
||||
query = { language: 'kuery', query: '' },
|
||||
}: {
|
||||
filters: Filter[];
|
||||
query?: Query;
|
||||
}): LensAttributes | null => {
|
||||
if (!attributes.current) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...attributes.current,
|
||||
state: {
|
||||
...attributes.current.state,
|
||||
query,
|
||||
filters: [...attributes.current.state.filters, ...filters],
|
||||
},
|
||||
};
|
||||
},
|
||||
[attributes]
|
||||
);
|
||||
|
||||
const getExtraActions = ({
|
||||
timeRange,
|
||||
filters,
|
||||
query,
|
||||
}: {
|
||||
timeRange: TimeRange;
|
||||
filters: Filter[];
|
||||
query?: Query;
|
||||
}) => {
|
||||
return {
|
||||
openInLens: {
|
||||
id: 'openInLens',
|
||||
|
||||
getDisplayName(_context: ActionExecutionContext): string {
|
||||
return i18n.translate(
|
||||
'xpack.infra.hostsViewPage.tabs.metricsCharts.actions.openInLines',
|
||||
const openInLensAction = useCallback(
|
||||
({ timeRange, filters, query }: { timeRange: TimeRange; filters: Filter[]; query?: Query }) =>
|
||||
() => {
|
||||
const injectedAttributes = injectFilters({ filters, query });
|
||||
if (injectedAttributes) {
|
||||
navigateToPrefilledEditor(
|
||||
{
|
||||
defaultMessage: 'Open in Lens',
|
||||
id: '',
|
||||
timeRange,
|
||||
attributes: injectedAttributes,
|
||||
},
|
||||
{
|
||||
openInNewTab: true,
|
||||
}
|
||||
);
|
||||
},
|
||||
getIconType(_context: ActionExecutionContext): string | undefined {
|
||||
return 'visArea';
|
||||
},
|
||||
type: 'actionButton',
|
||||
async isCompatible(_context: ActionExecutionContext): Promise<boolean> {
|
||||
return true;
|
||||
},
|
||||
async execute(_context: ActionExecutionContext): Promise<void> {
|
||||
const injectedAttributes = injectFilters({ filters, query });
|
||||
if (injectedAttributes) {
|
||||
navigateToPrefilledEditor(
|
||||
{
|
||||
id: '',
|
||||
timeRange,
|
||||
attributes: injectedAttributes,
|
||||
},
|
||||
{
|
||||
openInNewTab: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
order: 100,
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
[injectFilters, navigateToPrefilledEditor]
|
||||
);
|
||||
|
||||
const getExtraActions = useCallback(
|
||||
({ timeRange, filters, query }: { timeRange: TimeRange; filters: Filter[]; query?: Query }) => {
|
||||
const openInLens = getOpenInLensAction(openInLensAction({ timeRange, filters, query }));
|
||||
return [openInLens];
|
||||
},
|
||||
[openInLensAction]
|
||||
);
|
||||
|
||||
const {
|
||||
formula: { formula },
|
||||
} = lensChartConfig;
|
||||
|
||||
return { formula, attributes, getExtraActions, error };
|
||||
return { formula, attributes: attributes.current, getExtraActions, error };
|
||||
};
|
||||
|
||||
const getOpenInLensAction = (onExecute: () => void): Action => {
|
||||
return {
|
||||
id: 'openInLens',
|
||||
|
||||
getDisplayName(_context: ActionExecutionContext): string {
|
||||
return i18n.translate('xpack.infra.hostsViewPage.tabs.metricsCharts.actions.openInLines', {
|
||||
defaultMessage: 'Open in Lens',
|
||||
});
|
||||
},
|
||||
getIconType(_context: ActionExecutionContext): string | undefined {
|
||||
return 'visArea';
|
||||
},
|
||||
type: 'actionButton',
|
||||
async isCompatible(_context: ActionExecutionContext): Promise<boolean> {
|
||||
return true;
|
||||
},
|
||||
async execute(_context: ActionExecutionContext): Promise<void> {
|
||||
onExecute();
|
||||
},
|
||||
order: 100,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -33,12 +33,17 @@ export const ChartLoader = ({
|
|||
position="absolute"
|
||||
css={css`
|
||||
top: ${loadedOnce && hasTitle ? euiTheme.size.l : 0};
|
||||
z-index: ${Number(euiTheme.levels.header) - 1};
|
||||
`}
|
||||
style={{ zIndex: Number(euiTheme.levels.header) - 1 }}
|
||||
/>
|
||||
)}
|
||||
{loading && !loadedOnce ? (
|
||||
<EuiFlexGroup style={style} justifyContent="center" alignItems="center">
|
||||
<EuiFlexGroup
|
||||
style={{ ...style, marginTop: hasTitle ? euiTheme.size.l : 0 }}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingChart mono size="l" />
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import React, { useEffect, useState, useRef, useCallback, CSSProperties } from 'react';
|
||||
|
||||
import { Action } from '@kbn/ui-actions-plugin/public';
|
||||
import { ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
|
@ -23,99 +23,103 @@ export interface LensWrapperProps {
|
|||
filters: Filter[];
|
||||
extraActions: Action[];
|
||||
lastReloadRequestTime?: number;
|
||||
style?: React.CSSProperties;
|
||||
style?: CSSProperties;
|
||||
loading?: boolean;
|
||||
hasTitle?: boolean;
|
||||
onBrushEnd?: (data: BrushTriggerEvent['data']) => void;
|
||||
onLoad?: () => void;
|
||||
}
|
||||
|
||||
export const LensWrapper = ({
|
||||
attributes,
|
||||
dateRange,
|
||||
filters,
|
||||
id,
|
||||
query,
|
||||
extraActions,
|
||||
style,
|
||||
onBrushEnd,
|
||||
lastReloadRequestTime,
|
||||
loading = false,
|
||||
hasTitle = 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,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
export const LensWrapper = React.memo(
|
||||
({
|
||||
attributes,
|
||||
dateRange,
|
||||
filters,
|
||||
intersection?.intersectionRatio,
|
||||
lastReloadRequestTime,
|
||||
id,
|
||||
query,
|
||||
]);
|
||||
extraActions,
|
||||
style,
|
||||
onBrushEnd,
|
||||
lastReloadRequestTime,
|
||||
loading = false,
|
||||
hasTitle = false,
|
||||
}: LensWrapperProps) => {
|
||||
const intersectionRef = useRef(null);
|
||||
const [loadedOnce, setLoadedOnce] = useState(false);
|
||||
|
||||
const isReady = state.attributes && intersectedOnce;
|
||||
const [state, setState] = useState({
|
||||
attributes,
|
||||
lastReloadRequestTime,
|
||||
query,
|
||||
filters,
|
||||
dateRange,
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={intersectionRef}>
|
||||
<ChartLoader
|
||||
loading={loading || !isReady}
|
||||
loadedOnce={loadedOnce}
|
||||
style={style}
|
||||
hasTitle={hasTitle}
|
||||
>
|
||||
{state.attributes && (
|
||||
<EmbeddableComponent
|
||||
id={id}
|
||||
style={style}
|
||||
attributes={state.attributes}
|
||||
viewMode={ViewMode.VIEW}
|
||||
timeRange={state.dateRange}
|
||||
query={state.query}
|
||||
filters={state.filters}
|
||||
extraActions={extraActions}
|
||||
lastReloadRequestTime={state.lastReloadRequestTime}
|
||||
executionContext={{
|
||||
type: 'infrastructure_observability_hosts_view',
|
||||
name: id,
|
||||
}}
|
||||
onBrushEnd={onBrushEnd}
|
||||
onLoad={() => {
|
||||
if (!loadedOnce) {
|
||||
setLoadedOnce(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ChartLoader>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
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}>
|
||||
<ChartLoader
|
||||
loading={loading || !isReady}
|
||||
loadedOnce={loadedOnce}
|
||||
style={style}
|
||||
hasTitle={hasTitle}
|
||||
>
|
||||
{state.attributes && (
|
||||
<EmbeddableComponent
|
||||
id={id}
|
||||
style={style}
|
||||
attributes={state.attributes}
|
||||
viewMode={ViewMode.VIEW}
|
||||
timeRange={state.dateRange}
|
||||
query={state.query}
|
||||
filters={state.filters}
|
||||
extraActions={extraActions}
|
||||
lastReloadRequestTime={state.lastReloadRequestTime}
|
||||
executionContext={{
|
||||
type: 'infrastructure_observability_hosts_view',
|
||||
name: id,
|
||||
}}
|
||||
onBrushEnd={onBrushEnd}
|
||||
onLoad={onLoad}
|
||||
/>
|
||||
)}
|
||||
</ChartLoader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
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';
|
||||
|
@ -15,59 +15,50 @@ export interface Props extends Pick<MetricWTrend, 'title' | 'color' | 'extra' |
|
|||
loading: boolean;
|
||||
value: number;
|
||||
toolTip: React.ReactNode;
|
||||
style?: CSSProperties;
|
||||
['data-test-subj']?: string;
|
||||
}
|
||||
|
||||
const MIN_HEIGHT = 150;
|
||||
export const MetricChartWrapper = React.memo(
|
||||
({ color, extra, id, loading, value, subtitle, title, toolTip, style, ...props }: Props) => {
|
||||
const loadedOnce = useRef(false);
|
||||
|
||||
export const MetricChartWrapper = ({
|
||||
color,
|
||||
extra,
|
||||
id,
|
||||
loading,
|
||||
value,
|
||||
subtitle,
|
||||
title,
|
||||
toolTip,
|
||||
...props
|
||||
}: Props) => {
|
||||
const loadedOnce = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!loadedOnce.current && !loading) {
|
||||
loadedOnce.current = true;
|
||||
}
|
||||
return () => {
|
||||
loadedOnce.current = false;
|
||||
};
|
||||
}, [loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadedOnce.current && !loading) {
|
||||
loadedOnce.current = true;
|
||||
}
|
||||
return () => {
|
||||
loadedOnce.current = false;
|
||||
const metricsData: MetricWNumber = {
|
||||
title,
|
||||
subtitle,
|
||||
color,
|
||||
extra,
|
||||
value,
|
||||
valueFormatter: (d: number) => d.toString(),
|
||||
};
|
||||
}, [loading]);
|
||||
|
||||
const metricsData: MetricWNumber = {
|
||||
title,
|
||||
subtitle,
|
||||
color,
|
||||
extra,
|
||||
value,
|
||||
valueFormatter: (d: number) => d.toString(),
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPanel hasShadow={false} paddingSize="none" {...props}>
|
||||
<ChartLoader loading={loading} loadedOnce={loadedOnce.current} style={{ height: MIN_HEIGHT }}>
|
||||
<EuiToolTip
|
||||
className="eui-fullWidth"
|
||||
delay="regular"
|
||||
content={toolTip}
|
||||
anchorClassName="eui-fullWidth"
|
||||
>
|
||||
<KPIChartStyled size={{ height: MIN_HEIGHT }}>
|
||||
<Metric id={id} data={[[metricsData]]} />
|
||||
</KPIChartStyled>
|
||||
</EuiToolTip>
|
||||
</ChartLoader>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<EuiPanel hasShadow={false} paddingSize="none" {...props}>
|
||||
<ChartLoader loading={loading} loadedOnce={loadedOnce.current} style={style}>
|
||||
<EuiToolTip
|
||||
className="eui-fullWidth"
|
||||
delay="regular"
|
||||
content={toolTip}
|
||||
anchorClassName="eui-fullWidth"
|
||||
>
|
||||
<KPIChartStyled size={style}>
|
||||
<Metric id={id} data={[[metricsData]]} />
|
||||
</KPIChartStyled>
|
||||
</EuiToolTip>
|
||||
</ChartLoader>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const KPIChartStyled = styled(Chart)`
|
||||
.echMetric {
|
||||
|
|
|
@ -13,6 +13,7 @@ import { TOOLTIP } from '../../translations';
|
|||
|
||||
import { type Props, MetricChartWrapper } from '../chart/metric_chart_wrapper';
|
||||
import { TooltipContent } from '../metric_explanation/tooltip_content';
|
||||
import { KPIChartProps } from './tile';
|
||||
|
||||
const HOSTS_CHART: Omit<Props, 'loading' | 'value' | 'toolTip'> = {
|
||||
id: `metric-hostCount`,
|
||||
|
@ -23,7 +24,7 @@ const HOSTS_CHART: Omit<Props, 'loading' | 'value' | 'toolTip'> = {
|
|||
['data-test-subj']: 'hostsViewKPI-hostsCount',
|
||||
};
|
||||
|
||||
export const HostsTile = () => {
|
||||
export const HostsTile = ({ style }: Pick<KPIChartProps, 'style'>) => {
|
||||
const { data: hostCountData, isRequestRunning: hostCountLoading } = useHostCountContext();
|
||||
const { searchCriteria } = useUnifiedSearchContext();
|
||||
|
||||
|
@ -41,6 +42,7 @@ export const HostsTile = () => {
|
|||
return (
|
||||
<MetricChartWrapper
|
||||
{...HOSTS_CHART}
|
||||
style={style}
|
||||
value={hostCountData?.count.value ?? 0}
|
||||
subtitle={getSubtitle()}
|
||||
toolTip={
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import React, { CSSProperties } from 'react';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -14,8 +14,13 @@ 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';
|
||||
|
||||
const KPI_CHARTS: Array<Omit<KPIChartProps, 'loading' | 'subtitle'>> = [
|
||||
const lensStyle: CSSProperties = {
|
||||
height: KPI_CHART_MIN_HEIGHT,
|
||||
};
|
||||
|
||||
const KPI_CHARTS: Array<Omit<KPIChartProps, 'loading' | 'subtitle' | 'style'>> = [
|
||||
{
|
||||
type: 'cpuUsage',
|
||||
trendLine: true,
|
||||
|
@ -68,11 +73,11 @@ export const KPIGrid = () => {
|
|||
data-test-subj="hostsViewKPIGrid"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<HostsTile />
|
||||
<HostsTile style={lensStyle} />
|
||||
</EuiFlexItem>
|
||||
{KPI_CHARTS.map(({ ...chartProp }) => (
|
||||
<EuiFlexItem key={chartProp.type}>
|
||||
<Tile {...chartProp} />
|
||||
<Tile {...chartProp} style={lensStyle} />
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { CSSProperties, useMemo, useCallback } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { BrushTriggerEvent } from '@kbn/charts-plugin/public';
|
||||
|
@ -18,6 +18,7 @@ import {
|
|||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { Action } from '@kbn/ui-actions-plugin/public';
|
||||
import { useLensAttributes } from '../../../../../hooks/use_lens_attributes';
|
||||
import { useMetricsDataViewContext } from '../../hooks/use_data_view';
|
||||
import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
|
||||
|
@ -28,6 +29,7 @@ import { buildCombinedHostsFilter } from '../../utils';
|
|||
import { useHostCountContext } from '../../hooks/use_host_count';
|
||||
import { useAfterLoadedState } from '../../hooks/use_after_loaded_state';
|
||||
import { TooltipContent } from '../metric_explanation/tooltip_content';
|
||||
import { KPI_CHART_MIN_HEIGHT } from '../../constants';
|
||||
|
||||
export interface KPIChartProps {
|
||||
title: string;
|
||||
|
@ -37,15 +39,15 @@ export interface KPIChartProps {
|
|||
type: HostsLensMetricChartFormulas;
|
||||
decimals?: number;
|
||||
toolTip: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const MIN_HEIGHT = 150;
|
||||
|
||||
export const Tile = ({
|
||||
title,
|
||||
type,
|
||||
backgroundColor,
|
||||
toolTip,
|
||||
style,
|
||||
decimals = 1,
|
||||
trendLine = false,
|
||||
}: KPIChartProps) => {
|
||||
|
@ -91,23 +93,24 @@ export const Tile = ({
|
|||
];
|
||||
}, [hostNodes, dataView]);
|
||||
|
||||
const extraActionOptions = getExtraActions({
|
||||
timeRange: searchCriteria.dateRange,
|
||||
filters,
|
||||
});
|
||||
|
||||
const handleBrushEnd = ({ range }: BrushTriggerEvent['data']) => {
|
||||
const [min, max] = range;
|
||||
onSubmit({
|
||||
dateRange: {
|
||||
from: new Date(min).toISOString(),
|
||||
to: new Date(max).toISOString(),
|
||||
mode: 'absolute',
|
||||
},
|
||||
});
|
||||
};
|
||||
const handleBrushEnd = useCallback(
|
||||
({ range }: BrushTriggerEvent['data']) => {
|
||||
const [min, max] = range;
|
||||
onSubmit({
|
||||
dateRange: {
|
||||
from: new Date(min).toISOString(),
|
||||
to: new Date(max).toISOString(),
|
||||
mode: 'absolute',
|
||||
},
|
||||
});
|
||||
},
|
||||
[onSubmit]
|
||||
);
|
||||
|
||||
const loading = hostsLoading || !attributes || hostCountLoading;
|
||||
|
||||
// prevents requestTs and serchCriteria states from reloading the chart
|
||||
// we want it to reload only once the table has finished loading
|
||||
const { afterLoadedState } = useAfterLoadedState(loading, {
|
||||
attributes,
|
||||
lastReloadRequestTime: requestTs,
|
||||
|
@ -115,16 +118,24 @@ export const Tile = ({
|
|||
filters,
|
||||
});
|
||||
|
||||
const extraActions: Action[] = useMemo(
|
||||
() =>
|
||||
getExtraActions({
|
||||
timeRange: afterLoadedState.dateRange,
|
||||
filters,
|
||||
}),
|
||||
[afterLoadedState.dateRange, filters, getExtraActions]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPanelStyled
|
||||
hasShadow={false}
|
||||
paddingSize={error ? 'm' : 'none'}
|
||||
style={{ minHeight: MIN_HEIGHT }}
|
||||
data-test-subj={`hostsViewKPI-${type}`}
|
||||
>
|
||||
{error ? (
|
||||
<EuiFlexGroup
|
||||
style={{ height: MIN_HEIGHT, alignContent: 'center' }}
|
||||
style={{ minHeight: '100%', alignContent: 'center' }}
|
||||
gutterSize="xs"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
|
@ -148,17 +159,19 @@ export const Tile = ({
|
|||
content={<TooltipContent formula={formula} description={toolTip} />}
|
||||
anchorClassName="eui-fullWidth"
|
||||
>
|
||||
<LensWrapper
|
||||
id={`hostsViewKPIGrid${type}Tile`}
|
||||
attributes={afterLoadedState.attributes}
|
||||
style={{ height: MIN_HEIGHT }}
|
||||
extraActions={[extraActionOptions.openInLens]}
|
||||
lastReloadRequestTime={afterLoadedState.lastReloadRequestTime}
|
||||
dateRange={afterLoadedState.dateRange}
|
||||
filters={afterLoadedState.filters}
|
||||
onBrushEnd={handleBrushEnd}
|
||||
loading={loading}
|
||||
/>
|
||||
<div>
|
||||
<LensWrapper
|
||||
id={`hostsViewKPIGrid${type}Tile`}
|
||||
attributes={afterLoadedState.attributes}
|
||||
style={style}
|
||||
extraActions={extraActions}
|
||||
lastReloadRequestTime={afterLoadedState.lastReloadRequestTime}
|
||||
dateRange={afterLoadedState.dateRange}
|
||||
filters={afterLoadedState.filters}
|
||||
onBrushEnd={handleBrushEnd}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
</EuiPanelStyled>
|
||||
|
@ -166,6 +179,7 @@ export const Tile = ({
|
|||
};
|
||||
|
||||
const EuiPanelStyled = styled(EuiPanel)`
|
||||
min-height: ${KPI_CHART_MIN_HEIGHT};
|
||||
.echMetric {
|
||||
border-radius: ${({ theme }) => theme.eui.euiBorderRadius};
|
||||
pointer-events: none;
|
||||
|
|
|
@ -17,55 +17,52 @@ interface Props extends Pick<HTMLAttributes<HTMLDivElement>, 'style'> {
|
|||
showDocumentationLink?: boolean;
|
||||
}
|
||||
|
||||
export const TooltipContent = ({
|
||||
description,
|
||||
formula,
|
||||
showDocumentationLink = false,
|
||||
style,
|
||||
}: Props) => {
|
||||
return (
|
||||
<EuiText size="xs" style={style}>
|
||||
<p>{description}</p>
|
||||
{formula && (
|
||||
<p>
|
||||
<strong>
|
||||
export const TooltipContent = React.memo(
|
||||
({ description, formula, showDocumentationLink = false, style }: Props) => {
|
||||
return (
|
||||
<EuiText size="xs" style={style}>
|
||||
<p>{description}</p>
|
||||
{formula && (
|
||||
<p>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.hostsViewPage.table.tooltip.formula"
|
||||
defaultMessage="Formula Calculation:"
|
||||
/>
|
||||
</strong>
|
||||
<br />
|
||||
<code
|
||||
css={css`
|
||||
word-break: break-word;
|
||||
`}
|
||||
>
|
||||
{formula}
|
||||
</code>
|
||||
</p>
|
||||
)}
|
||||
{showDocumentationLink && (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.hostsViewPage.table.tooltip.formula"
|
||||
defaultMessage="Formula Calculation:"
|
||||
id="xpack.infra.hostsViewPage.table.tooltip.documentationLabel"
|
||||
defaultMessage="See {documentation} for more information"
|
||||
values={{
|
||||
documentation: (
|
||||
<EuiLink
|
||||
data-test-subj="hostsViewTooltipDocumentationLink"
|
||||
href={HOST_METRICS_DOC_HREF}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.hostsViewPage.table.tooltip.documentationLink"
|
||||
defaultMessage="documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</strong>
|
||||
<br />
|
||||
<code
|
||||
css={css`
|
||||
word-break: break-word;
|
||||
`}
|
||||
>
|
||||
{formula}
|
||||
</code>
|
||||
</p>
|
||||
)}
|
||||
{showDocumentationLink && (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.hostsViewPage.table.tooltip.documentationLabel"
|
||||
defaultMessage="See {documentation} for more information"
|
||||
values={{
|
||||
documentation: (
|
||||
<EuiLink
|
||||
data-test-subj="hostsViewTooltipDocumentationLink"
|
||||
href={HOST_METRICS_DOC_HREF}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.hostsViewPage.table.tooltip.documentationLink"
|
||||
defaultMessage="documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</EuiText>
|
||||
);
|
||||
};
|
||||
</p>
|
||||
)}
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import {
|
||||
EuiButtonGroup,
|
||||
EuiButtonGroupOptionProps,
|
||||
|
@ -27,10 +27,13 @@ interface Props {
|
|||
|
||||
export const LimitOptions = ({ limit, onChange }: Props) => {
|
||||
const [idSelected, setIdSelected] = useState(limit as number);
|
||||
const onSelected = (_id: string, value: number) => {
|
||||
setIdSelected(value);
|
||||
onChange(value);
|
||||
};
|
||||
const onSelected = useCallback(
|
||||
(_id: string, value: number) => {
|
||||
setIdSelected(value);
|
||||
onChange(value);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
direction="row"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { CSSProperties, useCallback, useMemo } from 'react';
|
||||
import { Action } from '@kbn/ui-actions-plugin/public';
|
||||
import { BrushTriggerEvent } from '@kbn/charts-plugin/public';
|
||||
import {
|
||||
|
@ -26,6 +26,7 @@ import { buildCombinedHostsFilter } from '../../../utils';
|
|||
import { useHostsTableContext } from '../../../hooks/use_hosts_table';
|
||||
import { LensWrapper } from '../../chart/lens_wrapper';
|
||||
import { useAfterLoadedState } from '../../../hooks/use_after_loaded_state';
|
||||
import { METRIC_CHART_MIN_HEIGHT } from '../../../constants';
|
||||
|
||||
export interface MetricChartProps {
|
||||
title: string;
|
||||
|
@ -34,7 +35,9 @@ export interface MetricChartProps {
|
|||
render?: boolean;
|
||||
}
|
||||
|
||||
const MIN_HEIGHT = 300;
|
||||
const lensStyle: CSSProperties = {
|
||||
height: METRIC_CHART_MIN_HEIGHT,
|
||||
};
|
||||
|
||||
export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
@ -43,7 +46,7 @@ export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) =>
|
|||
const { requestTs, loading } = useHostsViewContext();
|
||||
const { currentPage } = useHostsTableContext();
|
||||
|
||||
// prevents updates on requestTs and serchCriteria states from relaoding the chart
|
||||
// prevents requestTs and serchCriteria states from reloading the chart
|
||||
// we want it to reload only once the table has finished loading
|
||||
const { afterLoadedState } = useAfterLoadedState(loading, {
|
||||
lastReloadRequestTime: requestTs,
|
||||
|
@ -70,23 +73,28 @@ export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) =>
|
|||
];
|
||||
}, [currentPage, dataView]);
|
||||
|
||||
const extraActionOptions = getExtraActions({
|
||||
timeRange: afterLoadedState.dateRange,
|
||||
filters,
|
||||
});
|
||||
const extraActions: Action[] = useMemo(
|
||||
() =>
|
||||
getExtraActions({
|
||||
timeRange: afterLoadedState.dateRange,
|
||||
filters,
|
||||
}),
|
||||
[afterLoadedState.dateRange, filters, getExtraActions]
|
||||
);
|
||||
|
||||
const extraActions: Action[] = [extraActionOptions.openInLens];
|
||||
|
||||
const handleBrushEnd = ({ range }: BrushTriggerEvent['data']) => {
|
||||
const [min, max] = range;
|
||||
onSubmit({
|
||||
dateRange: {
|
||||
from: new Date(min).toISOString(),
|
||||
to: new Date(max).toISOString(),
|
||||
mode: 'absolute',
|
||||
},
|
||||
});
|
||||
};
|
||||
const handleBrushEnd = useCallback(
|
||||
({ range }: BrushTriggerEvent['data']) => {
|
||||
const [min, max] = range;
|
||||
onSubmit({
|
||||
dateRange: {
|
||||
from: new Date(min).toISOString(),
|
||||
to: new Date(max).toISOString(),
|
||||
mode: 'absolute',
|
||||
},
|
||||
});
|
||||
},
|
||||
[onSubmit]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPanel
|
||||
|
@ -95,7 +103,7 @@ export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) =>
|
|||
hasBorder
|
||||
paddingSize={error ? 'm' : 'none'}
|
||||
css={css`
|
||||
min-height: calc(${MIN_HEIGHT}px + ${euiTheme.size.l});
|
||||
min-height: calc(${METRIC_CHART_MIN_HEIGHT}px + ${euiTheme.size.l});
|
||||
position: relative;
|
||||
`}
|
||||
data-test-subj={`hostsView-metricChart-${type}`}
|
||||
|
@ -124,7 +132,7 @@ export const MetricChart = ({ title, type, breakdownSize }: MetricChartProps) =>
|
|||
<LensWrapper
|
||||
id={`hostsViewsmetricsChart-${type}`}
|
||||
attributes={attributes}
|
||||
style={{ height: MIN_HEIGHT }}
|
||||
style={lensStyle}
|
||||
extraActions={extraActions}
|
||||
lastReloadRequestTime={afterLoadedState.lastReloadRequestTime}
|
||||
dateRange={afterLoadedState.dateRange}
|
||||
|
|
|
@ -18,6 +18,9 @@ export const DEFAULT_PAGE_SIZE = 10;
|
|||
export const LOCAL_STORAGE_HOST_LIMIT_KEY = 'hostsView:hostLimitSelection';
|
||||
export const LOCAL_STORAGE_PAGE_SIZE_KEY = 'hostsView:pageSizeSelection';
|
||||
|
||||
export const KPI_CHART_MIN_HEIGHT = 150;
|
||||
export const METRIC_CHART_MIN_HEIGHT = 300;
|
||||
|
||||
export const ALL_ALERTS: AlertStatusFilter = {
|
||||
status: ALERT_STATUS_ALL,
|
||||
label: i18n.translate('xpack.infra.hostsViewPage.tabs.alerts.alertStatusFilter.showAll', {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
import _ from 'lodash';
|
||||
import { pick } from 'lodash';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { constant, identity } from 'fp-ts/lib/function';
|
||||
|
@ -90,19 +90,26 @@ const getVisibleControlPanelsConfig = (dataView: DataView | undefined) => {
|
|||
};
|
||||
|
||||
const addDataViewIdToControlPanels = (controlPanels: ControlPanels, dataViewId: string = '') => {
|
||||
return _.mapValues(controlPanels, (controlPanelConfig) => {
|
||||
const controlsClone = _.cloneDeep(controlPanelConfig);
|
||||
controlsClone.explicitInput.dataViewId = dataViewId;
|
||||
return controlsClone;
|
||||
});
|
||||
return Object.entries(controlPanels).reduce((acc, [key, controlPanelConfig]) => {
|
||||
return {
|
||||
...acc,
|
||||
[key]: {
|
||||
...controlPanelConfig,
|
||||
explicitInput: { ...controlPanelConfig.explicitInput, dataViewId },
|
||||
},
|
||||
};
|
||||
}, {});
|
||||
};
|
||||
|
||||
const cleanControlPanels = (controlPanels: ControlPanels) => {
|
||||
return _.mapValues(controlPanels, (controlPanelConfig) => {
|
||||
const controlsClone = _.cloneDeep(controlPanelConfig);
|
||||
delete controlsClone.explicitInput.dataViewId;
|
||||
return controlsClone;
|
||||
});
|
||||
return Object.entries(controlPanels).reduce((acc, [key, controlPanelConfig]) => {
|
||||
const { explicitInput } = controlPanelConfig;
|
||||
const { dataViewId, ...rest } = explicitInput;
|
||||
return {
|
||||
...acc,
|
||||
[key]: { ...controlPanelConfig, explicitInput: rest },
|
||||
};
|
||||
}, {});
|
||||
};
|
||||
|
||||
const mergeDefaultPanelsWithUrlConfig = (dataView: DataView, urlPanels: ControlPanels = {}) => {
|
||||
|
@ -111,7 +118,7 @@ const mergeDefaultPanelsWithUrlConfig = (dataView: DataView, urlPanels: ControlP
|
|||
|
||||
// Get list of panel which can be overridden to avoid merging additional config from url
|
||||
const existingKeys = Object.keys(visiblePanels);
|
||||
const controlPanelsToOverride = _.pick(urlPanels, existingKeys);
|
||||
const controlPanelsToOverride = pick(urlPanels, existingKeys);
|
||||
|
||||
// Merge default and existing configs and add dataView.id to each of them
|
||||
return addDataViewIdToControlPanels(
|
||||
|
|
|
@ -29,11 +29,11 @@ export const useDataView = ({ metricAlias }: { metricAlias: string }) => {
|
|||
});
|
||||
}, [metricAlias]);
|
||||
|
||||
const { value, loading, error, retry } = state;
|
||||
const { value: dataView, loading, error, retry } = state;
|
||||
|
||||
return {
|
||||
metricAlias,
|
||||
dataView: value,
|
||||
dataView,
|
||||
loading,
|
||||
loadDataView: retry,
|
||||
error,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue