From a057a88fabfc72fd898273374db61bc9655e22ce Mon Sep 17 00:00:00 2001 From: Carlos Crespo Date: Thu, 7 Sep 2023 14:50:40 +0200 Subject: [PATCH] [Infra UI] Fix page content reload when date picker has relative dates (#165462) closes https://github.com/elastic/kibana/issues/165194 ## Summary This PR fixes the problem with the Asset Details not reloading its content when date-picker had relative dates. **flyout** https://github.com/elastic/kibana/assets/2767137/3823f587-c743-41d7-aa05-75b3e072ef4c **full-page** https://github.com/elastic/kibana/assets/2767137/4a246ddd-6b7e-4650-834b-97d2db0740d5 I've also changed all the charts to receive absolute date ranges. If they used relative dates, `now` will be calculated when each chart starts loading, this could change significantly if there is a delay between loading each individual chart. ### How to test - Start a local Kibana instance - Navigate to Infrastructure > Hosts - Open the flyout, change the date picker's end-date to `now` and submit (do that one more time) - Open the full page, change the date picker's end-date to `now` and submit (do that one more time) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../asset_details/host/host_metric_charts.ts | 2 +- .../lens/dashboards/asset_details/index.ts | 6 +-- .../asset_details/hooks/use_date_range.ts | 53 +++++++++++++------ .../tabs/overview/kpis/kpi_grid.tsx | 3 ++ .../tabs/overview/metrics/metrics_grid.tsx | 20 +++---- .../asset_details/tabs/overview/overview.tsx | 17 +++--- .../infra/public/components/lens/index.tsx | 2 + .../public/components/lens/lens_chart.tsx | 39 ++++++-------- .../public/components/lens/lens_wrapper.tsx | 33 ++++++------ .../infra/public/components/lens/types.ts | 17 +++++- .../host_details_flyout/flyout_wrapper.tsx | 5 +- .../metrics/hosts/components/kpis/kpi.tsx | 4 +- .../hosts/components/tabs/metrics/chart.tsx | 4 +- .../metrics/hosts/hooks/use_host_count.ts | 5 +- 14 files changed, 123 insertions(+), 87 deletions(-) diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_metric_charts.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_metric_charts.ts index 0c31e1f2e664..9a0d4b0f7305 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_metric_charts.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/host/host_metric_charts.ts @@ -16,7 +16,7 @@ import { memoryUsage, memoryUsageBreakdown } from '../metric_charts/memory'; import { rxTx } from '../metric_charts/network'; import type { XYConfig } from '../metric_charts/types'; -export const hostMetricCharts: XYConfig[] = [ +export const hostMetricFlyoutCharts: XYConfig[] = [ cpuUsage, memoryUsage, normalizedLoad1m, diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/index.ts b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/index.ts index 119a55de9685..4f85d0a7bfba 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/index.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/dashboards/asset_details/index.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { hostMetricCharts, hostMetricChartsFullPage } from './host/host_metric_charts'; +import { hostMetricFlyoutCharts, hostMetricChartsFullPage } from './host/host_metric_charts'; import { hostKPICharts, KPIChartProps } from './host/host_kpi_charts'; import { nginxAccessCharts, nginxStubstatusCharts } from './host/nginx_charts'; export { type KPIChartProps }; export const assetDetailsDashboards = { - host: { hostMetricCharts, hostMetricChartsFullPage, hostKPICharts }, - nginxDashboard: { nginxStubstatusCharts, nginxAccessCharts }, + host: { hostMetricFlyoutCharts, hostMetricChartsFullPage, hostKPICharts }, + nginx: { nginxStubstatusCharts, nginxAccessCharts }, }; diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_date_range.ts b/x-pack/plugins/infra/public/components/asset_details/hooks/use_date_range.ts index 7e98c5683452..24f5e56ecedb 100644 --- a/x-pack/plugins/infra/public/components/asset_details/hooks/use_date_range.ts +++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_date_range.ts @@ -7,45 +7,66 @@ import type { TimeRange } from '@kbn/es-query'; import createContainer from 'constate'; -import { useCallback, useMemo } from 'react'; +import { useCallback, useState } from 'react'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; import { parseDateRange } from '../../../utils/datemath'; - import { toTimestampRange } from '../utils'; import { useAssetDetailsUrlState } from './use_asset_details_url_state'; -const DEFAULT_DATE_RANGE: TimeRange = { - from: 'now-15m', - to: 'now', -}; - export interface UseDateRangeProviderProps { initialDateRange: TimeRange; } +const DEFAULT_FROM_IN_MILLISECONDS = 15 * 60000; +const getDefaultDateRange = () => { + const now = Date.now(); + + return { + from: new Date(now - DEFAULT_FROM_IN_MILLISECONDS).toISOString(), + to: new Date(now).toISOString(), + }; +}; + export function useDateRangeProvider({ initialDateRange }: UseDateRangeProviderProps) { const [urlState, setUrlState] = useAssetDetailsUrlState(); const dateRange: TimeRange = urlState?.dateRange ?? initialDateRange; + const [parsedDateRange, setParsedDateRange] = useState(parseDateRange(dateRange)); + const [refreshTs, setRefreshTs] = useState(Date.now()); + + useEffectOnce(() => { + const { from, to } = getParsedDateRange(); + + // forces the date picker to initiallize with absolute dates. + setUrlState({ dateRange: { from, to } }); + }); const setDateRange = useCallback( (newDateRange: TimeRange) => { setUrlState({ dateRange: newDateRange }); + setParsedDateRange(parseDateRange(newDateRange)); + setRefreshTs(Date.now()); }, [setUrlState] ); - const parsedDateRange = useMemo(() => { - const { from = DEFAULT_DATE_RANGE.from, to = DEFAULT_DATE_RANGE.to } = - parseDateRange(dateRange); + const getParsedDateRange = useCallback(() => { + const defaultDateRange = getDefaultDateRange(); + const { from = defaultDateRange.from, to = defaultDateRange.to } = parsedDateRange; return { from, to }; - }, [dateRange]); + }, [parsedDateRange]); - const getDateRangeInTimestamp = useCallback( - () => toTimestampRange(parsedDateRange), - [parsedDateRange] - ); + const getDateRangeInTimestamp = useCallback(() => { + return toTimestampRange(getParsedDateRange()); + }, [getParsedDateRange]); - return { dateRange, setDateRange, getDateRangeInTimestamp }; + return { + dateRange, + getDateRangeInTimestamp, + getParsedDateRange, + refreshTs, + setDateRange, + }; } export const [DateRangeProvider, useDateRangeProviderContext] = diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/kpis/kpi_grid.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/kpis/kpi_grid.tsx index 7ffa55ed8297..011cb94c3525 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/kpis/kpi_grid.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/kpis/kpi_grid.tsx @@ -16,6 +16,7 @@ import { KPI_CHART_HEIGHT, AVERAGE_SUBTITLE, } from '../../../../../common/visualizations'; +import { useDateRangeProviderContext } from '../../../hooks/use_date_range'; interface Props { dataView?: DataView; @@ -24,6 +25,7 @@ interface Props { } export const KPIGrid = React.memo(({ nodeName, dataView, timeRange }: Props) => { + const { refreshTs } = useDateRangeProviderContext(); const filters = useMemo(() => { return [ buildCombinedHostsFilter({ @@ -43,6 +45,7 @@ export const KPIGrid = React.memo(({ nodeName, dataView, timeRange }: Props) => dataView={dataView} dateRange={timeRange} layers={{ ...layers, options: { ...layers.options, subtitle: AVERAGE_SUBTITLE } }} + lastReloadRequestTime={refreshTs} height={KPI_CHART_HEIGHT} filters={filters} title={title} diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_grid.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_grid.tsx index e84a1e19d4e0..0ec0f51dcd18 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_grid.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/overview/metrics/metrics_grid.tsx @@ -15,20 +15,22 @@ import { MetricsSectionTitle, NginxMetricsSectionTitle } from '../../../componen interface Props { assetName: string; - timeRange: TimeRange; + dateRange: TimeRange; metricsDataView?: DataView; logsDataView?: DataView; } +const { host, nginx } = assetDetailsDashboards; + export const MetricsGrid = React.memo( - ({ assetName, metricsDataView, logsDataView, timeRange }: Props) => { + ({ assetName, metricsDataView, logsDataView, dateRange: timeRange }: Props) => { return ( <>
({ - ...n, + ...nginx.nginxStubstatusCharts.map((chart) => ({ + ...chart, dependsOn: ['nginx.stubstatus'], })), - ...assetDetailsDashboards.nginxDashboard.nginxAccessCharts.map((n) => ({ - ...n, + ...nginx.nginxAccessCharts.map((chart) => ({ + ...chart, dependsOn: ['nginx.access'], })), ]} @@ -63,13 +65,13 @@ export const MetricsGridCompact = ({ assetName, metricsDataView, logsDataView, - timeRange, + dateRange: timeRange, }: Props) => (
{ - const { dateRange } = useDateRangeProviderContext(); + const { getParsedDateRange } = useDateRangeProviderContext(); const { asset, assetType, renderMode } = useAssetDetailsRenderPropsContext(); const { metadata, @@ -32,18 +32,19 @@ export const Overview = () => { } = useMetadataStateProviderContext(); const { logs, metrics } = useDataViewsProviderContext(); + const parsedDateRange = getParsedDateRange(); const isFullPageView = renderMode.mode !== 'flyout'; const metricsSection = isFullPageView ? ( ) : ( { return ( - + {fetchMetadataError ? ( @@ -88,12 +89,16 @@ export const Overview = () => { /> ) : ( - <>{metadataSummarySection} + metadataSummarySection )} - + {metricsSection} diff --git a/x-pack/plugins/infra/public/components/lens/index.tsx b/x-pack/plugins/infra/public/components/lens/index.tsx index ae05bd8e82fe..5da90461d1fa 100644 --- a/x-pack/plugins/infra/public/components/lens/index.tsx +++ b/x-pack/plugins/infra/public/components/lens/index.tsx @@ -10,3 +10,5 @@ export { ChartPlaceholder } from './chart_placeholder'; export { TooltipContent } from './metric_explanation/tooltip_content'; export { HostMetricsDocsLink } from './metric_explanation/host_metrics_docs_link'; export { HostMetricsExplanationContent } from './metric_explanation/host_metrics_explanation_content'; + +export * from './types'; diff --git a/x-pack/plugins/infra/public/components/lens/lens_chart.tsx b/x-pack/plugins/infra/public/components/lens/lens_chart.tsx index abcbe555a5d6..8ff3348c4319 100644 --- a/x-pack/plugins/infra/public/components/lens/lens_chart.tsx +++ b/x-pack/plugins/infra/public/components/lens/lens_chart.tsx @@ -56,7 +56,7 @@ export const LensChart = ({ const sytle: CSSProperties = useMemo(() => ({ height }), [height]); - const Lens = ( + const lens = ( ); - - const getContent = () => { - if (!toolTip) { - return Lens; - } - - return ( - - {/* EuiToolTip forwards some event handlers to the child component. + const content = !toolTip ? ( + lens + ) : ( + + {/* EuiToolTip forwards some event handlers to the child component. Wrapping Lens inside a div prevents that from causing unnecessary re-renders */} -
{Lens}
-
- ); - }; +
{lens}
+
+ ); return ( - {error ? : getContent()} + {error ? : content} ); }; diff --git a/x-pack/plugins/infra/public/components/lens/lens_wrapper.tsx b/x-pack/plugins/infra/public/components/lens/lens_wrapper.tsx index 8513881c22ad..d5c70be73033 100644 --- a/x-pack/plugins/infra/public/components/lens/lens_wrapper.tsx +++ b/x-pack/plugins/infra/public/components/lens/lens_wrapper.tsx @@ -5,7 +5,7 @@ * 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'; @@ -15,16 +15,7 @@ import { LensAttributes } from '@kbn/lens-embeddable-utils'; import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; import { ChartLoadingProgress, ChartPlaceholder } from './chart_placeholder'; import { parseDateRange } from '../../utils/datemath'; - -export type LensWrapperProps = Omit< - TypedLensByValueInput, - 'timeRange' | 'attributes' | 'viewMode' -> & { - attributes: LensAttributes | null; - dateRange: TimeRange; - extraActions: Action[]; - loading?: boolean; -}; +import type { LensWrapperProps } from './types'; export const LensWrapper = ({ attributes, @@ -32,6 +23,7 @@ export const LensWrapper = ({ filters, lastReloadRequestTime, loading = false, + onLoad, query, ...props }: LensWrapperProps) => { @@ -85,11 +77,17 @@ export const LensWrapper = ({ query, ]); - const onLoad = useCallback(() => { - if (!embeddableLoaded) { - setEmbeddableLoaded(true); - } - }, [embeddableLoaded]); + const handleOnLoad = useCallback( + (isLoading: boolean) => { + if (!embeddableLoaded) { + setEmbeddableLoaded(true); + } + if (onLoad) { + onLoad(isLoading); + } + }, + [embeddableLoaded, onLoad] + ); const parsedDateRange: TimeRange = useMemo(() => { const { from = state.dateRange.from, to = state.dateRange.to } = parseDateRange( @@ -104,7 +102,6 @@ export const LensWrapper = ({
& { + attributes: LensAttributes | null; + dateRange: TimeRange; + extraActions: Action[]; + loading?: boolean; +}; + +export type BrushEndArgs = Parameters>[0]; export type BaseChartProps = Pick< LensWrapperProps, diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx index 065b6739c41f..1fab43b5cd14 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx @@ -6,13 +6,11 @@ */ import React from 'react'; - import { useSourceContext } from '../../../../../containers/metrics_source'; import { useUnifiedSearchContext } from '../../hooks/use_unified_search'; import type { HostNodeRow } from '../../hooks/use_hosts_table'; import { AssetDetails } from '../../../../../components/asset_details/asset_details'; import { orderedFlyoutTabs } from './tabs'; -import { useAssetDetailsUrlState } from '../../../../../components/asset_details/hooks/use_asset_details_url_state'; export interface Props { node: HostNodeRow; @@ -22,13 +20,12 @@ export interface Props { export const FlyoutWrapper = ({ node: { name }, closeFlyout }: Props) => { const { source } = useSourceContext(); const { parsedDateRange } = useUnifiedSearchContext(); - const [urlState] = useAssetDetailsUrlState(); return source ? ( { - const { searchCriteria } = useUnifiedSearchContext(); + const { searchCriteria, parsedDateRange } = useUnifiedSearchContext(); const { dataView } = useMetricsDataViewContext(); const { requestTs, hostNodes, loading: hostsLoading } = useHostsViewContext(); const { data: hostCountData, isRequestRunning: hostCountLoading } = useHostCountContext(); @@ -52,7 +52,7 @@ export const Kpi = ({ id, title, layers, toolTip, height }: KPIChartProps & { he // we want it to reload only once the table has finished loading const { afterLoadedState } = useAfterLoadedState(loading, { lastReloadRequestTime: requestTs, - dateRange: searchCriteria.dateRange, + dateRange: parsedDateRange, query: shouldUseSearchCriteria ? searchCriteria.query : undefined, filters, }); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/chart.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/chart.tsx index 7bbf361e6b26..e8acd3a05fac 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/chart.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/chart.tsx @@ -23,7 +23,7 @@ export interface ChartProps extends Pick { - const { searchCriteria } = useUnifiedSearchContext(); + const { parsedDateRange, searchCriteria } = useUnifiedSearchContext(); const { dataView } = useMetricsDataViewContext(); const { requestTs, loading } = useHostsViewContext(); const { currentPage } = useHostsTableContext(); @@ -46,7 +46,7 @@ export const Chart = ({ id, title, layers, visualOptions, overrides }: ChartProp // we want it to reload only once the table has finished loading const { afterLoadedState } = useAfterLoadedState(loading, { lastReloadRequestTime: requestTs, - dateRange: searchCriteria.dateRange, + dateRange: parsedDateRange, query: shouldUseSearchCriteria ? searchCriteria.query : undefined, }); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_count.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_count.ts index 06ecb1a87e39..5d1f4766a775 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_count.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_count.ts @@ -27,7 +27,6 @@ export const useHostCount = () => { const { search: fetchHostCount, requests$ } = useDataSearch({ getRequest: useCallback(() => { const query = buildQuery(); - const dateRange = parsedDateRange; const filters: QueryDslQueryContainer = { bool: { @@ -42,8 +41,8 @@ export const useHostCount = () => { { range: { [dataView?.timeFieldName ?? '@timestamp']: { - gte: dateRange.from, - lte: dateRange.to, + gte: parsedDateRange.from, + lte: parsedDateRange.to, format: 'strict_date_optional_time', }, },