From c0cc62db9f4a4b142e1d89294d8ced41ad35130c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 4 Mar 2022 16:23:30 +0100 Subject: [PATCH 01/20] Create context for the date picker values --- .../public/application/index.tsx | 9 +- .../public/context/date_picker_context.tsx | 125 ++++++++++++++++++ 2 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/observability/public/context/date_picker_context.tsx diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 69bf9cbe3ce40..695838a646f80 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -19,6 +19,7 @@ import { } from '../../../../../src/plugins/kibana_react/public'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import type { LazyObservabilityPageTemplateProps } from '../components/shared/page_template/lazy_page_template'; +import { DatePickerContextProvider } from '../context/date_picker_context'; import { HasDataContextProvider } from '../context/has_data_context'; import { PluginContext } from '../context/plugin_context'; import { useRouteParams } from '../hooks/use_route_params'; @@ -90,9 +91,11 @@ export const renderApp = ({ - - - + + + + + diff --git a/x-pack/plugins/observability/public/context/date_picker_context.tsx b/x-pack/plugins/observability/public/context/date_picker_context.tsx new file mode 100644 index 0000000000000..f2d81aa21cde7 --- /dev/null +++ b/x-pack/plugins/observability/public/context/date_picker_context.tsx @@ -0,0 +1,125 @@ +/* + * 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, { createContext, useState, useMemo, useCallback } from 'react'; +import { useLocation } from 'react-router-dom'; +import { parse } from 'query-string'; +import { ObservabilityPublicPluginsStart } from '..'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { getAbsoluteTime } from '../utils/date'; + +export interface DatePickerContextValue { + relativeStart: string; + relativeEnd: string; + absoluteStart?: number; + absoluteEnd?: number; + refreshInterval: number; + refreshPaused: boolean; + updateTimeRange: (params: { start: string; end: string }) => void; + updateRefreshInterval: (params: { interval: number; isPaused: boolean }) => void; +} + +/** + * This context contains the time range (both relative and absolute) and the + * autorefresh status of the overview page date picker. + * It also updates the URL when any of the values change + */ +export const DatePickerContext = createContext({} as DatePickerContextValue); + +export function DatePickerContextProvider({ children }: { children: React.ReactElement }) { + const { data } = useKibana().services; + + const [lastUpdated, setLastUpdated] = useState(Date.now()); + + const defaultTimeRange = data.query.timefilter.timefilter.getTimeDefaults(); + const sharedTimeRange = data.query.timefilter.timefilter.getTime(); + const defaultRefreshInterval = data.query.timefilter.timefilter.getRefreshIntervalDefaults(); + const sharedRefreshInterval = data.query.timefilter.timefilter.getRefreshInterval(); + + const { + rangeFrom = sharedTimeRange.from ?? defaultTimeRange.from, + rangeTo = sharedTimeRange.to ?? defaultTimeRange.to, + refreshInterval = sharedRefreshInterval.value || defaultRefreshInterval.value || 10000, // we want to override a default of 0 + refreshPaused = sharedRefreshInterval.pause ?? defaultRefreshInterval.pause, + } = parse(useLocation().search, { + sort: false, + }); + + const relativeStart = rangeFrom as string; + const relativeEnd = rangeTo as string; + + const absoluteStart = useMemo( + () => getAbsoluteTime(relativeStart), + // `lastUpdated` works as a cache buster + // eslint-disable-next-line react-hooks/exhaustive-deps + [relativeStart, lastUpdated] + ); + + const absoluteEnd = useMemo( + () => getAbsoluteTime(relativeEnd, { roundUp: true }), + // `lastUpdated` works as a cache buster + // eslint-disable-next-line react-hooks/exhaustive-deps + [relativeEnd, lastUpdated] + ); + + const updateTimeRange = useCallback( + ({ start, end }: { start: string; end: string }) => { + data.query.timefilter.timefilter.setTime({ from: start, to: end }); + setLastUpdated(Date.now()); + }, + [data.query.timefilter.timefilter] + ); + + const updateRefreshInterval = useCallback( + ({ interval, isPaused }) => { + data.query.timefilter.timefilter.setRefreshInterval({ value: interval, pause: isPaused }); + }, + [data.query.timefilter.timefilter] + ); + + return ( + + {children} + + ); +} + +function parseRefreshInterval(value: string | string[] | number | null): number { + switch (typeof value) { + case 'number': + return value; + case 'string': + return parseInt(value, 10) || 0; + default: + return 0; + } +} + +function parseRefreshPaused(value: string | string[] | boolean | null): boolean { + if (typeof value === 'boolean') { + return value; + } + + switch (value) { + case 'false': + return false; + case 'true': + default: + return true; + } +} From fc3a05af3f78383485cb8125d1928d00eb8df8c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 4 Mar 2022 16:24:16 +0100 Subject: [PATCH 02/20] Use the new context to feed the datepicker values --- .../public/hooks/use_date_picker_context.ts | 13 +++++++++++++ .../public/pages/overview/old_overview_page.tsx | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/observability/public/hooks/use_date_picker_context.ts diff --git a/x-pack/plugins/observability/public/hooks/use_date_picker_context.ts b/x-pack/plugins/observability/public/hooks/use_date_picker_context.ts new file mode 100644 index 0000000000000..e4d42d4e25f32 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_date_picker_context.ts @@ -0,0 +1,13 @@ +/* + * 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 { useContext } from 'react'; +import { DatePickerContext } from '../context/date_picker_context'; + +export function useDatePickerContext() { + return useContext(DatePickerContext); +} diff --git a/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx b/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx index ac5900ca3dc6a..671be65780a35 100644 --- a/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx +++ b/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx @@ -32,6 +32,7 @@ import { AlertsTableTGrid } from '../alerts/containers/alerts_table_t_grid/alert import { SectionContainer } from '../../components/app/section'; import { ObservabilityAppServices } from '../../application/types'; import { useGetUserCasesPermissions } from '../../hooks/use_get_user_cases_permissions'; +import { useDatePickerContext } from '../../hooks/use_date_picker_context'; interface Props { routeParams: RouteParams<'/overview'>; } @@ -57,7 +58,8 @@ export function OverviewPage({ routeParams }: Props) { const { core, ObservabilityPageTemplate } = usePluginContext(); - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, refreshInterval, refreshPaused } = + useDatePickerContext(); const relativeTime = { start: relativeStart, end: relativeEnd }; const absoluteTime = { start: absoluteStart, end: absoluteEnd }; @@ -105,8 +107,6 @@ export function OverviewPage({ routeParams }: Props) { docsLink: core.docLinks.links.observability.guide, }); - const { refreshInterval = 10000, refreshPaused = true } = routeParams.query; - return ( Date: Fri, 4 Mar 2022 16:32:52 +0100 Subject: [PATCH 03/20] Update the URL when updating the values --- .../public/context/date_picker_context.tsx | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/observability/public/context/date_picker_context.tsx b/x-pack/plugins/observability/public/context/date_picker_context.tsx index f2d81aa21cde7..bbfacdbcba3b7 100644 --- a/x-pack/plugins/observability/public/context/date_picker_context.tsx +++ b/x-pack/plugins/observability/public/context/date_picker_context.tsx @@ -6,9 +6,9 @@ */ import React, { createContext, useState, useMemo, useCallback } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useLocation, useHistory } from 'react-router-dom'; import { parse } from 'query-string'; -import { ObservabilityPublicPluginsStart } from '..'; +import { fromQuery, ObservabilityPublicPluginsStart, toQuery } from '..'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { getAbsoluteTime } from '../utils/date'; @@ -31,10 +31,31 @@ export interface DatePickerContextValue { export const DatePickerContext = createContext({} as DatePickerContextValue); export function DatePickerContextProvider({ children }: { children: React.ReactElement }) { - const { data } = useKibana().services; + const location = useLocation(); + const history = useHistory(); + + const updateUrl = useCallback( + (nextQuery: { + rangeFrom?: string; + rangeTo?: string; + refreshPaused?: boolean; + refreshInterval?: number; + }) => { + history.push({ + ...location, + search: fromQuery({ + ...toQuery(location.search), + ...nextQuery, + }), + }); + }, + [history, location] + ); const [lastUpdated, setLastUpdated] = useState(Date.now()); + const { data } = useKibana().services; + const defaultTimeRange = data.query.timefilter.timefilter.getTimeDefaults(); const sharedTimeRange = data.query.timefilter.timefilter.getTime(); const defaultRefreshInterval = data.query.timefilter.timefilter.getRefreshIntervalDefaults(); @@ -45,7 +66,7 @@ export function DatePickerContextProvider({ children }: { children: React.ReactE rangeTo = sharedTimeRange.to ?? defaultTimeRange.to, refreshInterval = sharedRefreshInterval.value || defaultRefreshInterval.value || 10000, // we want to override a default of 0 refreshPaused = sharedRefreshInterval.pause ?? defaultRefreshInterval.pause, - } = parse(useLocation().search, { + } = parse(location.search, { sort: false, }); @@ -69,16 +90,19 @@ export function DatePickerContextProvider({ children }: { children: React.ReactE const updateTimeRange = useCallback( ({ start, end }: { start: string; end: string }) => { data.query.timefilter.timefilter.setTime({ from: start, to: end }); + updateUrl({ rangeFrom: start, rangeTo: end }); setLastUpdated(Date.now()); }, - [data.query.timefilter.timefilter] + [data.query.timefilter.timefilter, updateUrl] ); const updateRefreshInterval = useCallback( ({ interval, isPaused }) => { + updateUrl({ refreshInterval: interval, refreshPaused: isPaused }); data.query.timefilter.timefilter.setRefreshInterval({ value: interval, pause: isPaused }); + setLastUpdated(Date.now()); }, - [data.query.timefilter.timefilter] + [data.query.timefilter.timefilter, updateUrl] ); return ( From 5c02912d5989972b7186efcd3646cefbae110281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 4 Mar 2022 16:38:58 +0100 Subject: [PATCH 04/20] Update refresh and range using new context --- .../components/shared/date_picker/index.tsx | 54 ++++++------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/date_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/date_picker/index.tsx index ac32ad31d77b4..fde4b94460017 100644 --- a/x-pack/plugins/observability/public/components/shared/date_picker/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/date_picker/index.tsx @@ -6,13 +6,10 @@ */ import { EuiSuperDatePicker } from '@elastic/eui'; -import React, { useEffect } from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; -import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import React, { useCallback } from 'react'; import { UI_SETTINGS, useKibanaUISettings } from '../../../hooks/use_kibana_ui_settings'; -import { fromQuery, toQuery } from '../../../utils/url'; import { TimePickerQuickRange } from './typings'; -import { ObservabilityPublicPluginsStart } from '../../../plugin'; +import { useDatePickerContext } from '../../../hooks/use_date_picker_context'; export interface DatePickerProps { rangeFrom?: string; @@ -29,19 +26,7 @@ export function DatePicker({ refreshInterval, onTimeRangeRefresh, }: DatePickerProps) { - const location = useLocation(); - const history = useHistory(); - const { data } = useKibana().services; - - useEffect(() => { - // set time if both to and from are given in the url - if (rangeFrom && rangeTo) { - data.query.timefilter.timefilter.setTime({ - from: rangeFrom, - to: rangeTo, - }); - } - }, [data, rangeFrom, rangeTo]); + const { updateTimeRange, updateRefreshInterval } = useDatePickerContext(); const timePickerQuickRanges = useKibanaUISettings( UI_SETTINGS.TIMEPICKER_QUICK_RANGES @@ -53,21 +38,6 @@ export function DatePicker({ label: display, })); - function updateUrl(nextQuery: { - rangeFrom?: string; - rangeTo?: string; - refreshPaused?: boolean; - refreshInterval?: number; - }) { - history.push({ - ...location, - search: fromQuery({ - ...toQuery(location.search), - ...nextQuery, - }), - }); - } - function onRefreshChange({ isPaused, refreshInterval: interval, @@ -75,23 +45,29 @@ export function DatePicker({ isPaused: boolean; refreshInterval: number; }) { - updateUrl({ refreshPaused: isPaused, refreshInterval: interval }); + updateRefreshInterval({ isPaused, interval }); } - function onTimeChange({ start, end }: { start: string; end: string }) { - updateUrl({ rangeFrom: start, rangeTo: end }); - } + const onRefresh = useCallback( + (newRange: { start: string; end: string }) => { + if (onTimeRangeRefresh) { + onTimeRangeRefresh(newRange); + } + updateTimeRange(newRange); + }, + [onTimeRangeRefresh, updateTimeRange] + ); return ( ); } From 914093048422ac225b8064aeb79181db330182c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 4 Mar 2022 16:40:29 +0100 Subject: [PATCH 05/20] fixup! Use the new context to feed the datepicker values --- .../observability/public/pages/overview/old_overview_page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx b/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx index 671be65780a35..8c7216712c71f 100644 --- a/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx +++ b/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx @@ -20,7 +20,6 @@ import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; import { useFetcher } from '../../hooks/use_fetcher'; import { useHasData } from '../../hooks/use_has_data'; import { usePluginContext } from '../../hooks/use_plugin_context'; -import { useTimeRange } from '../../hooks/use_time_range'; import { useAlertIndexNames } from '../../hooks/use_alert_index_names'; import { RouteParams } from '../../routes'; import { getNewsFeed } from '../../services/get_news_feed'; From c9141774b0dfa0269442db4895823c0eb20b0257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 4 Mar 2022 16:45:08 +0100 Subject: [PATCH 06/20] Simplify overview page --- .../pages/overview/old_overview_page.tsx | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx b/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx index 8c7216712c71f..fa6d90cb103ce 100644 --- a/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx +++ b/x-pack/plugins/observability/public/pages/overview/old_overview_page.tsx @@ -60,27 +60,19 @@ export function OverviewPage({ routeParams }: Props) { const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, refreshInterval, refreshPaused } = useDatePickerContext(); - const relativeTime = { start: relativeStart, end: relativeEnd }; - const absoluteTime = { start: absoluteStart, end: absoluteEnd }; - const { data: newsFeed } = useFetcher(() => getNewsFeed({ core }), [core]); const { hasAnyData, isAllRequestsComplete } = useHasData(); const refetch = useRef<() => void>(); - const bucketSize = calculateBucketSize({ - start: absoluteTime.start, - end: absoluteTime.end, - }); - - const bucketSizeValue = useMemo(() => { - if (bucketSize?.bucketSize) { - return { - bucketSize: bucketSize.bucketSize, - intervalString: bucketSize.intervalString, - }; - } - }, [bucketSize?.bucketSize, bucketSize?.intervalString]); + const bucketSize = useMemo( + () => + calculateBucketSize({ + start: absoluteStart, + end: absoluteEnd, + }), + [absoluteStart, absoluteEnd] + ); const setRefetch = useCallback((ref) => { refetch.current = ref; @@ -115,8 +107,8 @@ export function OverviewPage({ routeParams }: Props) { pageTitle: overviewPageTitle, rightSideItems: [ @@ -153,7 +145,7 @@ export function OverviewPage({ routeParams }: Props) { {/* Data sections */} - {hasAnyData && } + {hasAnyData && } From a2c01311b0412e33a2187ca1d847f0c99c17e0f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 4 Mar 2022 16:52:44 +0100 Subject: [PATCH 07/20] Update all sections when the time changes --- .../components/app/section/apm/index.tsx | 9 +++--- .../components/app/section/logs/index.tsx | 9 +++--- .../components/app/section/metrics/index.tsx | 28 ++++++++----------- .../components/app/section/uptime/index.tsx | 9 +++--- .../components/app/section/ux/index.tsx | 9 +++--- 5 files changed, 28 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 6c61ecb3f270e..3ba6f7a8c490d 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -21,7 +21,7 @@ import moment from 'moment'; import React, { useContext } from 'react'; import { useHistory } from 'react-router-dom'; import { ThemeContext } from 'styled-components'; -import { useTimeRange } from '../../../../hooks/use_time_range'; +import { useDatePickerContext } from '../../../../hooks/use_date_picker_context'; import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { useChartTheme } from '../../../../hooks/use_chart_theme'; @@ -58,11 +58,11 @@ export function APMSection({ bucketSize }: Props) { const chartTheme = useChartTheme(); const history = useHistory(); const { forceUpdate, hasDataMap } = useHasData(); - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext(); const { data, status } = useFetcher( () => { - if (bucketSize) { + if (bucketSize && absoluteStart && absoluteEnd) { return getDataHandler('apm')?.fetchData({ absoluteTime: { start: absoluteStart, end: absoluteEnd }, relativeTime: { start: relativeStart, end: relativeEnd }, @@ -70,9 +70,8 @@ export function APMSection({ bucketSize }: Props) { }); } }, - // Absolute times shouldn't be used here, since it would refetch on every render // eslint-disable-next-line react-hooks/exhaustive-deps - [bucketSize, relativeStart, relativeEnd, forceUpdate] + [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate] ); if (!hasDataMap.apm?.hasData) { diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index 78c23638a91bd..c60087337e9ca 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -26,7 +26,7 @@ import { getDataHandler } from '../../../../data_handler'; import { useChartTheme } from '../../../../hooks/use_chart_theme'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useHasData } from '../../../../hooks/use_has_data'; -import { useTimeRange } from '../../../../hooks/use_time_range'; +import { useDatePickerContext } from '../../../../hooks/use_date_picker_context'; import { LogsFetchDataResponse } from '../../../../typings'; import { formatStatValue } from '../../../../utils/format_stat_value'; import { ChartContainer } from '../../chart_container'; @@ -57,11 +57,11 @@ export function LogsSection({ bucketSize }: Props) { const history = useHistory(); const chartTheme = useChartTheme(); const { forceUpdate, hasDataMap } = useHasData(); - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext(); const { data, status } = useFetcher( () => { - if (bucketSize) { + if (bucketSize && absoluteStart && absoluteEnd) { return getDataHandler('infra_logs')?.fetchData({ absoluteTime: { start: absoluteStart, end: absoluteEnd }, relativeTime: { start: relativeStart, end: relativeEnd }, @@ -69,9 +69,8 @@ export function LogsSection({ bucketSize }: Props) { }); } }, - // Absolute times shouldn't be used here, since it would refetch on every render // eslint-disable-next-line react-hooks/exhaustive-deps - [bucketSize, relativeStart, relativeEnd, forceUpdate] + [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate] ); if (!hasDataMap.infra_logs?.hasData) { diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index f7f35552fb686..e3f03d5e88a63 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -25,7 +25,7 @@ import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useHasData } from '../../../../hooks/use_has_data'; -import { useTimeRange } from '../../../../hooks/use_time_range'; +import { useDatePickerContext } from '../../../../hooks/use_date_picker_context'; import { HostLink } from './host_link'; import { formatDuration } from './lib/format_duration'; import { MetricWithSparkline } from './metric_with_sparkline'; @@ -51,25 +51,21 @@ const bytesPerSecondFormatter = (value: NumberOrNull) => export function MetricsSection({ bucketSize }: Props) { const { forceUpdate, hasDataMap } = useHasData(); - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext(); const [sortDirection, setSortDirection] = useState('asc'); const [sortField, setSortField] = useState('uptime'); const [sortedData, setSortedData] = useState(null); - const { data, status } = useFetcher( - () => { - if (bucketSize) { - return getDataHandler('infra_metrics')?.fetchData({ - absoluteTime: { start: absoluteStart, end: absoluteEnd }, - relativeTime: { start: relativeStart, end: relativeEnd }, - ...bucketSize, - }); - } - }, - // Absolute times shouldn't be used here, since it would refetch on every render + const { data, status } = useFetcher(() => { + if (bucketSize && absoluteStart && absoluteEnd) { + return getDataHandler('infra_metrics')?.fetchData({ + absoluteTime: { start: absoluteStart, end: absoluteEnd }, + relativeTime: { start: relativeStart, end: relativeEnd }, + ...bucketSize, + }); + } // eslint-disable-next-line react-hooks/exhaustive-deps - [bucketSize, relativeStart, relativeEnd, forceUpdate] - ); + }, [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate]); const handleTableChange = useCallback( ({ sort }: Criteria) => { @@ -125,7 +121,7 @@ export function MetricsSection({ bucketSize }: Props) { ), }, diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 5176b3f0721c8..b52d795b89e5e 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -27,7 +27,7 @@ import { getDataHandler } from '../../../../data_handler'; import { useChartTheme } from '../../../../hooks/use_chart_theme'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useHasData } from '../../../../hooks/use_has_data'; -import { useTimeRange } from '../../../../hooks/use_time_range'; +import { useDatePickerContext } from '../../../../hooks/use_date_picker_context'; import { Series } from '../../../../typings'; import { ChartContainer } from '../../chart_container'; import { StyledStat } from '../../styled_stat'; @@ -43,11 +43,11 @@ export function UptimeSection({ bucketSize }: Props) { const chartTheme = useChartTheme(); const history = useHistory(); const { forceUpdate, hasDataMap } = useHasData(); - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext(); const { data, status } = useFetcher( () => { - if (bucketSize) { + if (bucketSize && absoluteStart && absoluteEnd) { return getDataHandler('synthetics')?.fetchData({ absoluteTime: { start: absoluteStart, end: absoluteEnd }, relativeTime: { start: relativeStart, end: relativeEnd }, @@ -55,9 +55,8 @@ export function UptimeSection({ bucketSize }: Props) { }); } }, - // Absolute times shouldn't be used here, since it would refetch on every render // eslint-disable-next-line react-hooks/exhaustive-deps - [bucketSize, relativeStart, relativeEnd, forceUpdate] + [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate] ); if (!hasDataMap.synthetics?.hasData) { diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.tsx index 6863916f9bb8c..5fc4a0d233b5d 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.tsx @@ -11,7 +11,7 @@ import { SectionContainer } from '../'; import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useHasData } from '../../../../hooks/use_has_data'; -import { useTimeRange } from '../../../../hooks/use_time_range'; +import { useDatePickerContext } from '../../../../hooks/use_date_picker_context'; import CoreVitals from '../../../shared/core_web_vitals'; import { BucketSize } from '../../../../pages/overview'; @@ -21,13 +21,13 @@ interface Props { export function UXSection({ bucketSize }: Props) { const { forceUpdate, hasDataMap } = useHasData(); - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext(); const uxHasDataResponse = hasDataMap.ux; const serviceName = uxHasDataResponse?.serviceName as string; const { data, status } = useFetcher( () => { - if (serviceName && bucketSize) { + if (serviceName && bucketSize && absoluteStart && absoluteEnd) { return getDataHandler('ux')?.fetchData({ absoluteTime: { start: absoluteStart, end: absoluteEnd }, relativeTime: { start: relativeStart, end: relativeEnd }, @@ -36,9 +36,8 @@ export function UXSection({ bucketSize }: Props) { }); } }, - // Absolute times shouldn't be used here, since it would refetch on every render // eslint-disable-next-line react-hooks/exhaustive-deps - [bucketSize, relativeStart, relativeEnd, forceUpdate, serviceName] + [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate, serviceName] ); if (!uxHasDataResponse?.hasData) { From 0dc800e266a8b3e232735ba2242d30cace30976c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 4 Mar 2022 16:55:32 +0100 Subject: [PATCH 08/20] Use new context in HasDataContext --- .../observability/public/context/has_data_context.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability/public/context/has_data_context.tsx b/x-pack/plugins/observability/public/context/has_data_context.tsx index f9bb183725727..240f6a0a8be2b 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.tsx @@ -12,7 +12,7 @@ import { asyncForEach } from '@kbn/std'; import { getDataHandler } from '../data_handler'; import { FETCH_STATUS } from '../hooks/use_fetcher'; import { usePluginContext } from '../hooks/use_plugin_context'; -import { useTimeRange } from '../hooks/use_time_range'; +import { useDatePickerContext } from '../hooks/use_date_picker_context'; import { getObservabilityAlerts } from '../services/get_observability_alerts'; import { ObservabilityFetchDataPlugins } from '../typings/fetch_overview_data'; import { ApmIndicesConfig } from '../../common/typings'; @@ -44,7 +44,7 @@ const apps: DataContextApps[] = ['apm', 'synthetics', 'infra_logs', 'infra_metri export function HasDataContextProvider({ children }: { children: React.ReactNode }) { const { core } = usePluginContext(); const [forceUpdate, setForceUpdate] = useState(''); - const { absoluteStart, absoluteEnd } = useTimeRange(); + const { absoluteStart, absoluteEnd } = useDatePickerContext(); const [hasDataMap, setHasDataMap] = useState({}); @@ -76,7 +76,7 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode }; switch (app) { case 'ux': - const params = { absoluteTime: { start: absoluteStart, end: absoluteEnd } }; + const params = { absoluteTime: { start: absoluteStart!, end: absoluteEnd! } }; const resultUx = await getDataHandler(app)?.hasData(params); updateState({ hasData: resultUx?.hasData, From 41c6f919e6eab7d19a5ff604b97e1cb25074d62a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 4 Mar 2022 16:55:45 +0100 Subject: [PATCH 09/20] Use new context in new overview page --- .../observability/public/pages/overview/overview_page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/overview/overview_page.tsx b/x-pack/plugins/observability/public/pages/overview/overview_page.tsx index d75f9b0573aae..bc8e72f28ec10 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview_page.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview_page.tsx @@ -12,7 +12,7 @@ import { DatePicker } from '../../components/shared/date_picker'; import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; import { useHasData } from '../../hooks/use_has_data'; import { usePluginContext } from '../../hooks/use_plugin_context'; -import { useTimeRange } from '../../hooks/use_time_range'; +import { useDatePickerContext } from '../../hooks/use_date_picker_context'; import { RouteParams } from '../../routes'; import { getNoDataConfig } from '../../utils/no_data_config'; import { LoadingObservability } from './loading_observability'; @@ -36,7 +36,7 @@ export function OverviewPage({ routeParams }: Props) { const { core, ObservabilityPageTemplate } = usePluginContext(); - const { relativeStart, relativeEnd } = useTimeRange(); + const { relativeStart, relativeEnd } = useDatePickerContext(); const relativeTime = { start: relativeStart, end: relativeEnd }; From 6c5e27ff9c663d07c555b84b243dfe6e0ec73070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 4 Mar 2022 16:56:14 +0100 Subject: [PATCH 10/20] Remove old `useTimeRange` hook --- .../public/hooks/use_time_range.test.ts | 114 ------------------ .../public/hooks/use_time_range.ts | 41 ------- 2 files changed, 155 deletions(-) delete mode 100644 x-pack/plugins/observability/public/hooks/use_time_range.test.ts delete mode 100644 x-pack/plugins/observability/public/hooks/use_time_range.ts diff --git a/x-pack/plugins/observability/public/hooks/use_time_range.test.ts b/x-pack/plugins/observability/public/hooks/use_time_range.test.ts deleted file mode 100644 index bbf3096e55107..0000000000000 --- a/x-pack/plugins/observability/public/hooks/use_time_range.test.ts +++ /dev/null @@ -1,114 +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 { useTimeRange } from './use_time_range'; -import * as pluginContext from './use_plugin_context'; -import { AppMountParameters, CoreStart } from 'kibana/public'; -import { ObservabilityPublicPluginsStart } from '../plugin'; -import * as kibanaUISettings from './use_kibana_ui_settings'; -import { createObservabilityRuleTypeRegistryMock } from '../rules/observability_rule_type_registry_mock'; - -jest.mock('react-router-dom', () => ({ - useLocation: () => ({ - pathname: '/observability/overview/', - search: '', - }), -})); - -describe('useTimeRange', () => { - beforeAll(() => { - jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ - core: {} as CoreStart, - appMountParameters: {} as AppMountParameters, - config: { - unsafe: { - alertingExperience: { enabled: true }, - cases: { enabled: true }, - overviewNext: { enabled: false }, - }, - }, - plugins: { - data: { - query: { - timefilter: { - timefilter: { - getTime: jest.fn().mockImplementation(() => ({ - from: '2020-10-08T06:00:00.000Z', - to: '2020-10-08T07:00:00.000Z', - })), - }, - }, - }, - }, - } as unknown as ObservabilityPublicPluginsStart, - observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), - ObservabilityPageTemplate: () => null, - })); - jest.spyOn(kibanaUISettings, 'useKibanaUISettings').mockImplementation(() => ({ - from: '2020-10-08T05:00:00.000Z', - to: '2020-10-08T06:00:00.000Z', - })); - }); - - describe('when range from and to are not provided', () => { - describe('when data plugin has time set', () => { - it('returns ranges and absolute times from data plugin', () => { - const relativeStart = '2020-10-08T06:00:00.000Z'; - const relativeEnd = '2020-10-08T07:00:00.000Z'; - const timeRange = useTimeRange(); - expect(timeRange).toEqual({ - relativeStart, - relativeEnd, - absoluteStart: new Date(relativeStart).valueOf(), - absoluteEnd: new Date(relativeEnd).valueOf(), - }); - }); - }); - describe("when data plugin doesn't have time set", () => { - beforeAll(() => { - jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ - core: {} as CoreStart, - appMountParameters: {} as AppMountParameters, - config: { - unsafe: { - alertingExperience: { enabled: true }, - cases: { enabled: true }, - overviewNext: { enabled: false }, - }, - }, - plugins: { - data: { - query: { - timefilter: { - timefilter: { - getTime: jest.fn().mockImplementation(() => ({ - from: undefined, - to: undefined, - })), - }, - }, - }, - }, - } as unknown as ObservabilityPublicPluginsStart, - observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), - ObservabilityPageTemplate: () => null, - })); - }); - it('returns ranges and absolute times from kibana default settings', () => { - const relativeStart = '2020-10-08T05:00:00.000Z'; - const relativeEnd = '2020-10-08T06:00:00.000Z'; - const timeRange = useTimeRange(); - expect(timeRange).toEqual({ - relativeStart, - relativeEnd, - absoluteStart: new Date(relativeStart).valueOf(), - absoluteEnd: new Date(relativeEnd).valueOf(), - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/observability/public/hooks/use_time_range.ts b/x-pack/plugins/observability/public/hooks/use_time_range.ts deleted file mode 100644 index aa120d6968bfb..0000000000000 --- a/x-pack/plugins/observability/public/hooks/use_time_range.ts +++ /dev/null @@ -1,41 +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 { parse } from 'query-string'; -import { useLocation } from 'react-router-dom'; -import { TimePickerTimeDefaults } from '../components/shared/date_picker/typings'; -import { getAbsoluteTime } from '../utils/date'; -import { UI_SETTINGS, useKibanaUISettings } from './use_kibana_ui_settings'; -import { usePluginContext } from './use_plugin_context'; - -const getParsedParams = (search: string) => { - return parse(search.slice(1), { sort: false }); -}; - -export function useTimeRange() { - const { plugins } = usePluginContext(); - - const timePickerTimeDefaults = useKibanaUISettings( - UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS - ); - - const timePickerSharedState = plugins.data.query.timefilter.timefilter.getTime(); - - const { rangeFrom, rangeTo } = getParsedParams(useLocation().search); - - const relativeStart = (rangeFrom ?? - timePickerSharedState.from ?? - timePickerTimeDefaults.from) as string; - const relativeEnd = (rangeTo ?? timePickerSharedState.to ?? timePickerTimeDefaults.to) as string; - - return { - relativeStart, - relativeEnd, - absoluteStart: getAbsoluteTime(relativeStart)!, - absoluteEnd: getAbsoluteTime(relativeEnd, { roundUp: true })!, - }; -} From 23eccbdc2c70a182af9d1bf8058c304adfc6756c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 7 Mar 2022 15:43:27 +0100 Subject: [PATCH 11/20] Update tests --- .../shared/date_picker/date_picker.test.tsx | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx b/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx index 53324e7df3af2..34fb77338cdc7 100644 --- a/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx @@ -15,6 +15,7 @@ import qs from 'query-string'; import { DatePicker } from './'; import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; import { of } from 'rxjs'; +import { DatePickerContextProvider } from '../../../context/date_picker_context'; let history: MemoryHistory; @@ -69,7 +70,13 @@ function mountDatePicker(initialParams: { data: { query: { timefilter: { - timefilter: { setTime: setTimeSpy, getTime: getTimeSpy }, + timefilter: { + setTime: setTimeSpy, + getTime: getTimeSpy, + getTimeDefaults: jest.fn().mockReturnValue({}), + getRefreshIntervalDefaults: jest.fn().mockReturnValue({}), + getRefreshInterval: jest.fn().mockReturnValue({}), + }, }, }, }, @@ -79,7 +86,9 @@ function mountDatePicker(initialParams: { }, }} > - + + + ); @@ -152,17 +161,6 @@ describe('DatePicker', () => { }); describe('if both `rangeTo` and `rangeFrom` is set', () => { - it('calls setTime ', async () => { - const { setTimeSpy } = mountDatePicker({ - rangeTo: 'now-20m', - rangeFrom: 'now-22m', - }); - expect(setTimeSpy).toHaveBeenCalledWith({ - to: 'now-20m', - from: 'now-22m', - }); - }); - it('does not update the url', () => { expect(mockHistoryReplace).toHaveBeenCalledTimes(0); }); From d9b6a8aca8b24bb4275ccd736f743c9d03f3b87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 8 Mar 2022 12:36:01 +0100 Subject: [PATCH 12/20] Clean up the tests --- .../public/context/has_data_context.test.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/x-pack/plugins/observability/public/context/has_data_context.test.tsx b/x-pack/plugins/observability/public/context/has_data_context.test.tsx index 4d4c96d1c1110..6fb16c8c7528c 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.test.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.test.tsx @@ -11,7 +11,6 @@ import React from 'react'; import { registerDataHandler, unregisterDataHandler } from '../data_handler'; import { useHasData } from '../hooks/use_has_data'; import * as routeParams from '../hooks/use_route_params'; -import * as timeRange from '../hooks/use_time_range'; import { HasData, ObservabilityFetchDataPlugins } from '../typings/fetch_overview_data'; import { HasDataContextProvider } from './has_data_context'; import * as pluginContext from '../hooks/use_plugin_context'; @@ -57,19 +56,6 @@ function registerApps( describe('HasDataContextProvider', () => { beforeAll(() => { - jest.spyOn(routeParams, 'useRouteParams').mockImplementation(() => ({ - query: { - from: relativeStart, - to: relativeEnd, - }, - path: {}, - })); - jest.spyOn(timeRange, 'useTimeRange').mockImplementation(() => ({ - relativeStart, - relativeEnd, - absoluteStart: new Date(relativeStart).valueOf(), - absoluteEnd: new Date(relativeEnd).valueOf(), - })); jest.spyOn(pluginContext, 'usePluginContext').mockReturnValue({ core: { http: { get: jest.fn() } } as unknown as CoreStart, } as PluginContextValue); From bdeb332514c4816ca78c354cc52bc6c50cabc800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 8 Mar 2022 17:48:33 +0100 Subject: [PATCH 13/20] Set up the date range in the URL on first load --- .../observability/public/context/date_picker_context.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/plugins/observability/public/context/date_picker_context.tsx b/x-pack/plugins/observability/public/context/date_picker_context.tsx index bbfacdbcba3b7..624fb71e8c4bc 100644 --- a/x-pack/plugins/observability/public/context/date_picker_context.tsx +++ b/x-pack/plugins/observability/public/context/date_picker_context.tsx @@ -6,6 +6,7 @@ */ import React, { createContext, useState, useMemo, useCallback } from 'react'; +import useMount from 'react-use/lib/useMount'; import { useLocation, useHistory } from 'react-router-dom'; import { parse } from 'query-string'; import { fromQuery, ObservabilityPublicPluginsStart, toQuery } from '..'; @@ -105,6 +106,10 @@ export function DatePickerContextProvider({ children }: { children: React.ReactE [data.query.timefilter.timefilter, updateUrl] ); + useMount(() => { + updateUrl({ rangeFrom: relativeStart, rangeTo: relativeEnd }); + }); + return ( Date: Tue, 8 Mar 2022 18:40:37 +0100 Subject: [PATCH 14/20] Properly force a refresh with absolute ranges --- .../public/components/app/section/apm/index.tsx | 6 ++++-- .../public/components/app/section/logs/index.tsx | 7 +++++-- .../components/app/section/metrics/index.tsx | 14 ++++++++++++-- .../components/app/section/uptime/index.tsx | 6 ++++-- .../public/components/app/section/ux/index.tsx | 15 +++++++++++++-- .../public/context/date_picker_context.tsx | 2 ++ 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index 3ba6f7a8c490d..8a96b8ccfa70c 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -58,7 +58,8 @@ export function APMSection({ bucketSize }: Props) { const chartTheme = useChartTheme(); const history = useHistory(); const { forceUpdate, hasDataMap } = useHasData(); - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext(); + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, lastUpdated } = + useDatePickerContext(); const { data, status } = useFetcher( () => { @@ -70,8 +71,9 @@ export function APMSection({ bucketSize }: Props) { }); } }, + // `forceUpdate` and `lastUpdated` should trigger a reload // eslint-disable-next-line react-hooks/exhaustive-deps - [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate] + [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate, lastUpdated] ); if (!hasDataMap.apm?.hasData) { diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index c60087337e9ca..80a14a3824e00 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -57,7 +57,8 @@ export function LogsSection({ bucketSize }: Props) { const history = useHistory(); const chartTheme = useChartTheme(); const { forceUpdate, hasDataMap } = useHasData(); - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext(); + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, lastUpdated } = + useDatePickerContext(); const { data, status } = useFetcher( () => { @@ -69,8 +70,10 @@ export function LogsSection({ bucketSize }: Props) { }); } }, + + // `forceUpdate` and `lastUpdated` trigger a reload // eslint-disable-next-line react-hooks/exhaustive-deps - [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate] + [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate, lastUpdated] ); if (!hasDataMap.infra_logs?.hasData) { diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index e3f03d5e88a63..39757d79bab96 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -51,7 +51,8 @@ const bytesPerSecondFormatter = (value: NumberOrNull) => export function MetricsSection({ bucketSize }: Props) { const { forceUpdate, hasDataMap } = useHasData(); - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext(); + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, lastUpdated } = + useDatePickerContext(); const [sortDirection, setSortDirection] = useState('asc'); const [sortField, setSortField] = useState('uptime'); const [sortedData, setSortedData] = useState(null); @@ -64,8 +65,17 @@ export function MetricsSection({ bucketSize }: Props) { ...bucketSize, }); } + // `forceUpdate` and `lastUpdated` should trigger a reload // eslint-disable-next-line react-hooks/exhaustive-deps - }, [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate]); + }, [ + bucketSize, + relativeStart, + relativeEnd, + absoluteStart, + absoluteEnd, + forceUpdate, + lastUpdated, + ]); const handleTableChange = useCallback( ({ sort }: Criteria) => { diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index b52d795b89e5e..84779e5270e46 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -43,7 +43,8 @@ export function UptimeSection({ bucketSize }: Props) { const chartTheme = useChartTheme(); const history = useHistory(); const { forceUpdate, hasDataMap } = useHasData(); - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext(); + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, lastUpdated } = + useDatePickerContext(); const { data, status } = useFetcher( () => { @@ -55,8 +56,9 @@ export function UptimeSection({ bucketSize }: Props) { }); } }, + // `forceUpdate` and `lastUpdated` should trigger a reload // eslint-disable-next-line react-hooks/exhaustive-deps - [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate] + [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate, lastUpdated] ); if (!hasDataMap.synthetics?.hasData) { diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.tsx index 5fc4a0d233b5d..4d1ff07c85a08 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.tsx @@ -21,7 +21,8 @@ interface Props { export function UXSection({ bucketSize }: Props) { const { forceUpdate, hasDataMap } = useHasData(); - const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext(); + const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, lastUpdated } = + useDatePickerContext(); const uxHasDataResponse = hasDataMap.ux; const serviceName = uxHasDataResponse?.serviceName as string; @@ -36,8 +37,18 @@ export function UXSection({ bucketSize }: Props) { }); } }, + // `forceUpdate` and `lastUpdated` should trigger a reload // eslint-disable-next-line react-hooks/exhaustive-deps - [bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate, serviceName] + [ + bucketSize, + relativeStart, + relativeEnd, + absoluteStart, + absoluteEnd, + forceUpdate, + serviceName, + lastUpdated, + ] ); if (!uxHasDataResponse?.hasData) { diff --git a/x-pack/plugins/observability/public/context/date_picker_context.tsx b/x-pack/plugins/observability/public/context/date_picker_context.tsx index 624fb71e8c4bc..711d0adc0e522 100644 --- a/x-pack/plugins/observability/public/context/date_picker_context.tsx +++ b/x-pack/plugins/observability/public/context/date_picker_context.tsx @@ -22,6 +22,7 @@ export interface DatePickerContextValue { refreshPaused: boolean; updateTimeRange: (params: { start: string; end: string }) => void; updateRefreshInterval: (params: { interval: number; isPaused: boolean }) => void; + lastUpdated: number; } /** @@ -121,6 +122,7 @@ export function DatePickerContextProvider({ children }: { children: React.ReactE refreshPaused: parseRefreshPaused(refreshPaused), updateTimeRange, updateRefreshInterval, + lastUpdated, }} > {children} From a55cf54dae0b99d242c5c8a23da63f83f9126257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Wed, 9 Mar 2022 12:06:32 +0100 Subject: [PATCH 15/20] Update/clean up tests --- .../observability/public/application/application.test.tsx | 8 +++++++- .../components/shared/date_picker/date_picker.test.tsx | 5 +++-- .../public/context/has_data_context.test.tsx | 4 ---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/observability/public/application/application.test.tsx b/x-pack/plugins/observability/public/application/application.test.tsx index d28667d147b29..3b66a9478a534 100644 --- a/x-pack/plugins/observability/public/application/application.test.tsx +++ b/x-pack/plugins/observability/public/application/application.test.tsx @@ -34,7 +34,13 @@ describe('renderApp', () => { data: { query: { timefilter: { - timefilter: { setTime: jest.fn(), getTime: jest.fn().mockImplementation(() => ({})) }, + timefilter: { + setTime: jest.fn(), + getTime: jest.fn().mockReturnValue({}), + getTimeDefaults: jest.fn().mockReturnValue({}), + getRefreshInterval: jest.fn().mockReturnValue({}), + getRefreshIntervalDefaults: jest.fn().mockReturnValue({}), + }, }, }, }, diff --git a/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx b/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx index 34fb77338cdc7..713063e42eda6 100644 --- a/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx @@ -115,7 +115,8 @@ describe('DatePicker', () => { rangeTo: 'now', }); - expect(mockHistoryReplace).toHaveBeenCalledTimes(0); + // It updates the URL when it doesn't contain the range. + expect(mockHistoryPush).toHaveBeenCalledTimes(1); wrapper.find(EuiSuperDatePicker).props().onTimeChange({ start: 'now-90m', @@ -123,7 +124,7 @@ describe('DatePicker', () => { isInvalid: false, isQuickSelection: true, }); - expect(mockHistoryPush).toHaveBeenCalledTimes(1); + expect(mockHistoryPush).toHaveBeenCalledTimes(2); expect(mockHistoryPush).toHaveBeenLastCalledWith( expect.objectContaining({ search: 'rangeFrom=now-90m&rangeTo=now-60m', diff --git a/x-pack/plugins/observability/public/context/has_data_context.test.tsx b/x-pack/plugins/observability/public/context/has_data_context.test.tsx index 6fb16c8c7528c..8e33b6d329d49 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.test.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.test.tsx @@ -10,7 +10,6 @@ import { CoreStart } from 'kibana/public'; import React from 'react'; import { registerDataHandler, unregisterDataHandler } from '../data_handler'; import { useHasData } from '../hooks/use_has_data'; -import * as routeParams from '../hooks/use_route_params'; import { HasData, ObservabilityFetchDataPlugins } from '../typings/fetch_overview_data'; import { HasDataContextProvider } from './has_data_context'; import * as pluginContext from '../hooks/use_plugin_context'; @@ -20,9 +19,6 @@ import { createMemoryHistory } from 'history'; import { ApmIndicesConfig } from '../../common/typings'; import { act } from '@testing-library/react'; -const relativeStart = '2020-10-08T06:00:00.000Z'; -const relativeEnd = '2020-10-08T07:00:00.000Z'; - const sampleAPMIndices = { transaction: 'apm-*' } as ApmIndicesConfig; function wrapper({ children }: { children: React.ReactElement }) { From 00403b36f1195148f445fbedc0a3c4ecef0a7a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Thu, 10 Mar 2022 22:15:04 +0100 Subject: [PATCH 16/20] Add provider to UXApp --- x-pack/plugins/observability/public/index.ts | 1 + .../plugins/ux/public/application/ux_app.tsx | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index 72b3b1b46f69e..4769777d4c892 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -52,6 +52,7 @@ export const plugin: PluginInitializer< export * from './components/shared/action_menu/'; export type { UXMetrics } from './components/shared/core_web_vitals/'; +export { DatePickerContextProvider } from './context/date_picker_context'; export { getCoreVitalsComponent, HeaderMenuPortal, diff --git a/x-pack/plugins/ux/public/application/ux_app.tsx b/x-pack/plugins/ux/public/application/ux_app.tsx index 29ec41ab67ab5..03767959e4d17 100644 --- a/x-pack/plugins/ux/public/application/ux_app.tsx +++ b/x-pack/plugins/ux/public/application/ux_app.tsx @@ -34,6 +34,7 @@ import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin'; import { UXActionMenu } from '../components/app/rum_dashboard/action_menu'; import { + DatePickerContextProvider, InspectorContextProvider, useBreadcrumbs, } from '../../../observability/public'; @@ -133,14 +134,16 @@ export function UXAppRoot({ > - - - - - - - - + + + + + + + + + + From c3be483abe07bf4ff9b00a2f075b4f48283f583e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 14 Mar 2022 12:27:17 +0100 Subject: [PATCH 17/20] Reload data in UX app with stable ranges --- .../components/app/rum_dashboard/hooks/use_ux_query.ts | 7 +++++-- .../app/rum_dashboard/impactful_metrics/js_errors.tsx | 6 ++++-- .../app/rum_dashboard/page_load_distribution/index.tsx | 5 ++++- .../app/rum_dashboard/page_views_trend/index.tsx | 6 ++++-- .../app/rum_dashboard/panels/web_application_select.tsx | 5 ++++- .../app/rum_dashboard/visitor_breakdown/index.tsx | 6 ++++-- .../rum_dashboard/visitor_breakdown_map/embedded_map.tsx | 4 ++-- 7 files changed, 27 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_ux_query.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_ux_query.ts index e3f697e02a295..3a2106a079d4a 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_ux_query.ts +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_ux_query.ts @@ -9,7 +9,7 @@ import { useMemo } from 'react'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; export function useUxQuery() { - const { urlParams, uxUiFilters } = useLegacyUrlParams(); + const { rangeId, urlParams, uxUiFilters } = useLegacyUrlParams(); const { start, end, searchTerm, percentile } = urlParams; @@ -27,7 +27,10 @@ export function useUxQuery() { } return null; - }, [start, end, searchTerm, percentile, uxUiFilters]); + + // `rangeId` acts as a cache buster for stable date ranges like `Today` + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [start, end, searchTerm, percentile, uxUiFilters, rangeId]); return queryParams; } diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/impactful_metrics/js_errors.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/impactful_metrics/js_errors.tsx index c8dada5dce40b..760ed2cba5390 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/impactful_metrics/js_errors.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/impactful_metrics/js_errors.tsx @@ -32,7 +32,7 @@ interface JSErrorItem { } export function JSErrors() { - const { urlParams, uxUiFilters } = useLegacyUrlParams(); + const { rangeId, urlParams, uxUiFilters } = useLegacyUrlParams(); const { start, end, serviceName, searchTerm } = urlParams; @@ -56,7 +56,9 @@ export function JSErrors() { } return Promise.resolve(null); }, - [start, end, serviceName, uxUiFilters, pagination, searchTerm] + // `rangeId` acts as a cache buster for stable ranges like "Today" + // eslint-disable-next-line react-hooks/exhaustive-deps + [start, end, serviceName, uxUiFilters, pagination, searchTerm, rangeId] ); const { diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/index.tsx index ef32ad53b3ccf..dd3c05b7084c1 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/index.tsx @@ -32,7 +32,7 @@ export interface PercentileRange { export function PageLoadDistribution() { const { http } = useKibanaServices(); - const { urlParams, uxUiFilters } = useLegacyUrlParams(); + const { rangeId, urlParams, uxUiFilters } = useLegacyUrlParams(); const { start, end, rangeFrom, rangeTo, searchTerm } = urlParams; @@ -67,6 +67,8 @@ export function PageLoadDistribution() { } return Promise.resolve(null); }, + // `rangeId` acts as a cache buster for stable ranges like "Today" + // eslint-disable-next-line react-hooks/exhaustive-deps [ end, start, @@ -75,6 +77,7 @@ export function PageLoadDistribution() { percentileRange.max, searchTerm, serviceName, + rangeId, ] ); diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/page_views_trend/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_views_trend/index.tsx index 48cf17089edcb..062332f680d82 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/page_views_trend/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_views_trend/index.tsx @@ -26,7 +26,7 @@ import { BreakdownItem } from '../../../../../typings/ui_filters'; export function PageViewsTrend() { const { http } = useKibanaServices(); - const { urlParams, uxUiFilters } = useLegacyUrlParams(); + const { rangeId, urlParams, uxUiFilters } = useLegacyUrlParams(); const { serviceName } = uxUiFilters; const { start, end, searchTerm, rangeTo, rangeFrom } = urlParams; @@ -54,7 +54,9 @@ export function PageViewsTrend() { } return Promise.resolve(undefined); }, - [start, end, serviceName, uxUiFilters, searchTerm, breakdown] + // `rangeId` acts as a cache buster for stable ranges like "Today" + // eslint-disable-next-line react-hooks/exhaustive-deps + [start, end, serviceName, uxUiFilters, searchTerm, breakdown, rangeId] ); const exploratoryViewLink = createExploratoryViewUrl( diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/panels/web_application_select.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/panels/web_application_select.tsx index e7d86db0557a5..1985f20a8d5c4 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/panels/web_application_select.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/panels/web_application_select.tsx @@ -13,6 +13,7 @@ import { RUM_AGENT_NAMES } from '../../../../../common/agent_name'; export function WebApplicationSelect() { const { + rangeId, urlParams: { start, end }, } = useLegacyUrlParams(); @@ -30,7 +31,9 @@ export function WebApplicationSelect() { }); } }, - [start, end] + // `rangeId` works as a cache buster for ranges that never change, like `Today` + // eslint-disable-next-line react-hooks/exhaustive-deps + [start, end, rangeId] ); const rumServiceNames = data?.rumServices ?? []; diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown/index.tsx index ccf6502575991..a6be5a8340d08 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown/index.tsx @@ -13,7 +13,7 @@ import { useFetcher } from '../../../../hooks/use_fetcher'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; export function VisitorBreakdown() { - const { urlParams, uxUiFilters } = useLegacyUrlParams(); + const { rangeId, urlParams, uxUiFilters } = useLegacyUrlParams(); const { start, end, searchTerm } = urlParams; @@ -35,7 +35,9 @@ export function VisitorBreakdown() { } return Promise.resolve(null); }, - [end, start, uxUiFilters, searchTerm] + // `rangeId` acts as a cache buster for stable ranges like "Today" + // eslint-disable-next-line react-hooks/exhaustive-deps + [end, start, uxUiFilters, searchTerm, rangeId] ); return ( diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.tsx index 55845c44e5e27..780682d66ae3b 100644 --- a/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.tsx @@ -45,7 +45,7 @@ const EmbeddedPanel = styled.div` `; export function EmbeddedMapComponent() { - const { urlParams } = useLegacyUrlParams(); + const { rangeId, urlParams } = useLegacyUrlParams(); const { start, end, serviceName } = urlParams; @@ -124,7 +124,7 @@ export function EmbeddedMapComponent() { embeddable.reload(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [start, end]); + }, [start, end, rangeId]); useEffect(() => { async function setupEmbeddable() { From 8db770abefda13921055b9fb5c14fbe12e308241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 14 Mar 2022 15:21:48 +0100 Subject: [PATCH 18/20] Fix tests --- .../ux/public/application/application.test.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/ux/public/application/application.test.tsx b/x-pack/plugins/ux/public/application/application.test.tsx index 15b351581c680..1023a7b83345b 100644 --- a/x-pack/plugins/ux/public/application/application.test.tsx +++ b/x-pack/plugins/ux/public/application/application.test.tsx @@ -58,7 +58,19 @@ const mockCorePlugins = { ), }, }, - data: {}, + data: { + query: { + timefilter: { + timefilter: { + setTime: jest.fn(), + getTime: jest.fn().mockReturnValue({}), + getTimeDefaults: jest.fn().mockReturnValue({}), + getRefreshIntervalDefaults: jest.fn().mockReturnValue({}), + getRefreshInterval: jest.fn().mockReturnValue({}), + }, + }, + }, + }, }; const coreStart = coreMock.createStart({ basePath: '/basepath' }); From ce437a4d90e6599185c432eeaa54c6146cab3d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 14 Mar 2022 17:04:34 +0100 Subject: [PATCH 19/20] Fix mock import --- .../columns/ping_timestamp/ping_timestamp.test.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx index ed74b502add11..1e853bf3461dc 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx @@ -16,16 +16,9 @@ import moment from 'moment'; import '../../../../../lib/__mocks__/use_composite_image.mock'; import { mockRef } from '../../../../../lib/__mocks__/screenshot_ref.mock'; -mockReduxHooks(); - -jest.mock('../../../../../../../observability/public', () => { - const originalModule = jest.requireActual('../../../../../../../observability/public'); +jest.mock('../../../../../../../observability/public'); - return { - ...originalModule, - useFetcher: jest.fn().mockReturnValue({ data: null, status: 'pending' }), - }; -}); +mockReduxHooks(); describe('Ping Timestamp component', () => { let checkGroup: string; From d0220ec67012db8963441429379d4c22fb2a5ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 15 Mar 2022 13:09:11 +0100 Subject: [PATCH 20/20] Fix mocks --- .../step_screenshot_display.test.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/synthetics/step_screenshot_display.test.tsx b/x-pack/plugins/uptime/public/components/synthetics/step_screenshot_display.test.tsx index 5b86ed525bc31..ad8666f77ce6b 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/step_screenshot_display.test.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/step_screenshot_display.test.tsx @@ -12,20 +12,24 @@ import * as observabilityPublic from '../../../../observability/public'; import '../../lib/__mocks__/use_composite_image.mock'; import { mockRef } from '../../lib/__mocks__/screenshot_ref.mock'; -jest.mock('../../../../observability/public', () => { - const originalModule = jest.requireActual('../../../../observability/public'); - - return { - ...originalModule, - useFetcher: jest.fn().mockReturnValue({ data: null, status: 'success' }), - }; -}); +jest.mock('../../../../observability/public'); jest.mock('react-use/lib/useIntersection', () => () => ({ isIntersecting: true, })); describe('StepScreenshotDisplayProps', () => { + beforeAll(() => { + jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ + data: null, + status: observabilityPublic.FETCH_STATUS.SUCCESS, + refetch: () => {}, + }); + }); + + afterAll(() => { + (observabilityPublic.useFetcher as any).mockClear(); + }); it('displays screenshot thumbnail when present', () => { const { getByAltText } = render(