[8.6] [Synthetics] Update monitor detail data on refresh (#145468) (#146319)

# Backport

This will backport the following commits from `main` to `8.6`:
- [[Synthetics] Update monitor detail data on refresh
(#145468)](https://github.com/elastic/kibana/pull/145468)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Justin
Kambic","email":"jk@elastic.co"},"sourceCommit":{"committedDate":"2022-11-23T19:20:42Z","message":"[Synthetics]
Update monitor detail data on refresh (#145468)\n\n##
Summary\r\n\r\nResolves #143662.\r\n\r\nResolves #144367.\r\n\r\nThis
patch will add refresh capabilities to the monitor detail page
and\r\nits child views. Today, the page needs to be hard-refreshed in
order for\r\nthis to work.\r\n\r\n### Arch changes\r\n\r\n#### App-wide
refresh interval\r\n\r\nTo simplify how auto-refreshing works, I
have\r\n[extracted](https://github.com/elastic/kibana/pull/145468/files#diff-09f3a6de5b54c7d3933df933d83e963f6ff6a5c18a3d83238bd658623595fe35L47)\r\nthe
existing `interval` logic from the Overview page and put it into
the\r\n[`RefreshContext`](https://github.com/elastic/kibana/pull/145468/files#diff-660f9a1ed6f641662eff6682448bac7d1b167b4ea518ddc43634192b2959be1fR36)\r\nitself.
I am not overly fond of this solution, so if someone has a\r\nbetter
suggestion for how to handle this, I am open to
recommendations.\r\n\r\nThis refresh context also ties into the date
picker components we use\r\nfor some of these pages, so everything
should continue to work\r\nseamlessly, but there's going to be a
conflicting auto-refresh between\r\ndate pickers and the internal
context value itself. It might be\r\nworthwhile for us to discuss this
in a tech sync and have a dedicated\r\nenhancement.\r\n\r\n#### New
absolute date formatting hook\r\n\r\nI've introduced a [new hook for
changing relative date values into\r\nabsolute
ISO\r\nstrings](https://github.com/elastic/kibana/pull/145468/files#diff-48912058ac736ca8aece834bb50369cc5589b94b9c8b2b3e418e9d7690241525).\r\nThis
is because we are making extensive use of Exploratory
View\r\nEmbeddables, which do not have any kind of refresh mechanism
exposed.\r\nThe simplest way to get these to refresh when our
`lastRefresh` field\r\nupdates is to supply the embeddable with absolute
values, which\r\ncorrespond directly to the existing `from` and `to`
fields we're already\r\nusing.\r\n\r\n#### Existing hook
updates\r\n\r\nI have tried to avoid manipulating the existing behavior
of our fetch\r\nhooks, but there were a few cases where I added optional
parameters to\r\nthem to support refresh functionality. It's worth extra
attention to\r\nmake sure we're not adding something that we should
avoid for these\r\nhooks.\r\n\r\n### Testing this PR\r\n\r\nThe best way
to test this PR is to configure a monitor and open the\r\ndetail pages
for `Overview`, `History`, and `Errors`. Let your monitor\r\nrun for
some time and look at all the visualizations, while avoiding a\r\nhard
refresh. If you run this patch from source, you can also modify
the\r\n[interval](https://github.com/elastic/kibana/pull/145468/files#diff-660f9a1ed6f641662eff6682448bac7d1b167b4ea518ddc43634192b2959be1fR39)\r\nfrom
`30s` to some smaller value if you want to make sure things
are\r\nupdating where they should more
easily.","sha":"06b7596810a285868db58c9040da868ff8ab6e4d","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","Team:uptime","release_note:skip","ci:cloud-deploy","v8.6.0","v8.7.0"],"number":145468,"url":"https://github.com/elastic/kibana/pull/145468","mergeCommit":{"message":"[Synthetics]
Update monitor detail data on refresh (#145468)\n\n##
Summary\r\n\r\nResolves #143662.\r\n\r\nResolves #144367.\r\n\r\nThis
patch will add refresh capabilities to the monitor detail page
and\r\nits child views. Today, the page needs to be hard-refreshed in
order for\r\nthis to work.\r\n\r\n### Arch changes\r\n\r\n#### App-wide
refresh interval\r\n\r\nTo simplify how auto-refreshing works, I
have\r\n[extracted](https://github.com/elastic/kibana/pull/145468/files#diff-09f3a6de5b54c7d3933df933d83e963f6ff6a5c18a3d83238bd658623595fe35L47)\r\nthe
existing `interval` logic from the Overview page and put it into
the\r\n[`RefreshContext`](https://github.com/elastic/kibana/pull/145468/files#diff-660f9a1ed6f641662eff6682448bac7d1b167b4ea518ddc43634192b2959be1fR36)\r\nitself.
I am not overly fond of this solution, so if someone has a\r\nbetter
suggestion for how to handle this, I am open to
recommendations.\r\n\r\nThis refresh context also ties into the date
picker components we use\r\nfor some of these pages, so everything
should continue to work\r\nseamlessly, but there's going to be a
conflicting auto-refresh between\r\ndate pickers and the internal
context value itself. It might be\r\nworthwhile for us to discuss this
in a tech sync and have a dedicated\r\nenhancement.\r\n\r\n#### New
absolute date formatting hook\r\n\r\nI've introduced a [new hook for
changing relative date values into\r\nabsolute
ISO\r\nstrings](https://github.com/elastic/kibana/pull/145468/files#diff-48912058ac736ca8aece834bb50369cc5589b94b9c8b2b3e418e9d7690241525).\r\nThis
is because we are making extensive use of Exploratory
View\r\nEmbeddables, which do not have any kind of refresh mechanism
exposed.\r\nThe simplest way to get these to refresh when our
`lastRefresh` field\r\nupdates is to supply the embeddable with absolute
values, which\r\ncorrespond directly to the existing `from` and `to`
fields we're already\r\nusing.\r\n\r\n#### Existing hook
updates\r\n\r\nI have tried to avoid manipulating the existing behavior
of our fetch\r\nhooks, but there were a few cases where I added optional
parameters to\r\nthem to support refresh functionality. It's worth extra
attention to\r\nmake sure we're not adding something that we should
avoid for these\r\nhooks.\r\n\r\n### Testing this PR\r\n\r\nThe best way
to test this PR is to configure a monitor and open the\r\ndetail pages
for `Overview`, `History`, and `Errors`. Let your monitor\r\nrun for
some time and look at all the visualizations, while avoiding a\r\nhard
refresh. If you run this patch from source, you can also modify
the\r\n[interval](https://github.com/elastic/kibana/pull/145468/files#diff-660f9a1ed6f641662eff6682448bac7d1b167b4ea518ddc43634192b2959be1fR39)\r\nfrom
`30s` to some smaller value if you want to make sure things
are\r\nupdating where they should more
easily.","sha":"06b7596810a285868db58c9040da868ff8ab6e4d"}},"sourceBranch":"main","suggestedTargetBranches":["8.6"],"targetPullRequestStates":[{"branch":"8.6","label":"v8.6.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/145468","number":145468,"mergeCommit":{"message":"[Synthetics]
Update monitor detail data on refresh (#145468)\n\n##
Summary\r\n\r\nResolves #143662.\r\n\r\nResolves #144367.\r\n\r\nThis
patch will add refresh capabilities to the monitor detail page
and\r\nits child views. Today, the page needs to be hard-refreshed in
order for\r\nthis to work.\r\n\r\n### Arch changes\r\n\r\n#### App-wide
refresh interval\r\n\r\nTo simplify how auto-refreshing works, I
have\r\n[extracted](https://github.com/elastic/kibana/pull/145468/files#diff-09f3a6de5b54c7d3933df933d83e963f6ff6a5c18a3d83238bd658623595fe35L47)\r\nthe
existing `interval` logic from the Overview page and put it into
the\r\n[`RefreshContext`](https://github.com/elastic/kibana/pull/145468/files#diff-660f9a1ed6f641662eff6682448bac7d1b167b4ea518ddc43634192b2959be1fR36)\r\nitself.
I am not overly fond of this solution, so if someone has a\r\nbetter
suggestion for how to handle this, I am open to
recommendations.\r\n\r\nThis refresh context also ties into the date
picker components we use\r\nfor some of these pages, so everything
should continue to work\r\nseamlessly, but there's going to be a
conflicting auto-refresh between\r\ndate pickers and the internal
context value itself. It might be\r\nworthwhile for us to discuss this
in a tech sync and have a dedicated\r\nenhancement.\r\n\r\n#### New
absolute date formatting hook\r\n\r\nI've introduced a [new hook for
changing relative date values into\r\nabsolute
ISO\r\nstrings](https://github.com/elastic/kibana/pull/145468/files#diff-48912058ac736ca8aece834bb50369cc5589b94b9c8b2b3e418e9d7690241525).\r\nThis
is because we are making extensive use of Exploratory
View\r\nEmbeddables, which do not have any kind of refresh mechanism
exposed.\r\nThe simplest way to get these to refresh when our
`lastRefresh` field\r\nupdates is to supply the embeddable with absolute
values, which\r\ncorrespond directly to the existing `from` and `to`
fields we're already\r\nusing.\r\n\r\n#### Existing hook
updates\r\n\r\nI have tried to avoid manipulating the existing behavior
of our fetch\r\nhooks, but there were a few cases where I added optional
parameters to\r\nthem to support refresh functionality. It's worth extra
attention to\r\nmake sure we're not adding something that we should
avoid for these\r\nhooks.\r\n\r\n### Testing this PR\r\n\r\nThe best way
to test this PR is to configure a monitor and open the\r\ndetail pages
for `Overview`, `History`, and `Errors`. Let your monitor\r\nrun for
some time and look at all the visualizations, while avoiding a\r\nhard
refresh. If you run this patch from source, you can also modify
the\r\n[interval](https://github.com/elastic/kibana/pull/145468/files#diff-660f9a1ed6f641662eff6682448bac7d1b167b4ea518ddc43634192b2959be1fR39)\r\nfrom
`30s` to some smaller value if you want to make sure things
are\r\nupdating where they should more
easily.","sha":"06b7596810a285868db58c9040da868ff8ab6e4d"}}]}]
BACKPORT-->

Co-authored-by: Justin Kambic <jk@elastic.co>
This commit is contained in:
Shahzad 2022-11-24 18:19:35 +01:00 committed by GitHub
parent 1467f2491f
commit fcc857c2f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 132 additions and 41 deletions

View file

@ -11,7 +11,7 @@ import { isStepEnd } from '../../common/monitor_test_result/browser_steps_list';
import { JourneyStep, SyntheticsJourneyApiResponse } from '../../../../../../common/runtime_types';
import { fetchJourneySteps } from '../../../state';
export const useJourneySteps = (checkGroup?: string) => {
export const useJourneySteps = (checkGroup?: string, lastRefresh?: number) => {
const { stepIndex } = useParams<{ stepIndex: string }>();
const { checkGroupId: urlCheckGroup } = useParams<{ checkGroupId: string }>();
@ -23,7 +23,7 @@ export const useJourneySteps = (checkGroup?: string) => {
}
return fetchJourneySteps({ checkGroup: checkGroupId });
}, [checkGroupId]);
}, [checkGroupId, lastRefresh]);
const isFailed =
data?.steps.some(

View file

@ -13,6 +13,7 @@ import { useSelectedLocation } from './use_selected_location';
import { getMonitorRecentPingsAction, selectMonitorPingsMetadata } from '../../../state';
interface UseMonitorPingsProps {
lastRefresh?: number;
pageSize?: number;
pageIndex?: number;
from?: string;
@ -45,6 +46,7 @@ export const useMonitorPings = (props?: UseMonitorPingsProps) => {
dispatch,
monitorId,
locationLabel,
props?.lastRefresh,
props?.pageSize,
props?.pageIndex,
props?.from,

View file

@ -9,6 +9,7 @@ import { useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { ConfigKey } from '../../../../../../common/runtime_types';
import { useSyntheticsRefreshContext } from '../../../contexts';
import {
getMonitorAction,
selectEncryptedSyntheticsSavedMonitors,
@ -24,6 +25,7 @@ export const useSelectedMonitor = () => {
() => monitorsList.find((monitor) => monitor[ConfigKey.CONFIG_ID] === monitorId) ?? null,
[monitorId, monitorsList]
);
const { lastRefresh } = useSyntheticsRefreshContext();
const { syntheticsMonitor, syntheticsMonitorLoading } = useSelector(selectorMonitorDetailsState);
const dispatch = useDispatch();
@ -43,6 +45,10 @@ export const useSelectedMonitor = () => {
}
}, [dispatch, monitorId, availableMonitor, syntheticsMonitorLoading]);
useEffect(() => {
dispatch(getMonitorAction.get({ monitorId }));
}, [dispatch, monitorId, lastRefresh]);
return {
monitor: availableMonitor,
loading: syntheticsMonitorLoading || monitorListLoading,

View file

@ -12,10 +12,10 @@ import {
EuiTitle,
useEuiTheme,
} from '@elastic/eui';
import React, { useMemo } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FailedTestsCount } from './failed_tests_count';
import { useGetUrlParams } from '../../../hooks';
import { useAbsoluteDate, useGetUrlParams } from '../../../hooks';
import { SyntheticsDatePicker } from '../../common/date_picker/synthetics_date_picker';
import { MonitorErrorsCount } from '../monitor_summary/monitor_errors_count';
import { ErrorsList } from './errors_list';
@ -26,10 +26,7 @@ export const MonitorErrors = () => {
const { dateRangeStart, dateRangeEnd } = useGetUrlParams();
const time = useMemo(
() => ({ from: dateRangeStart, to: dateRangeEnd }),
[dateRangeEnd, dateRangeStart]
);
const time = useAbsoluteDate({ from: dateRangeStart, to: dateRangeEnd });
return (
<>
@ -43,10 +40,10 @@ export const MonitorErrors = () => {
</EuiTitle>
<EuiFlexGroup>
<EuiFlexItem>
<MonitorErrorsCount to={dateRangeEnd} from={dateRangeStart} />
<MonitorErrorsCount to={time.to} from={time.from} />
</EuiFlexItem>
<EuiFlexItem>
<FailedTestsCount to={dateRangeEnd} from={dateRangeStart} />
<FailedTestsCount from={time.from} to={time.to} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>

View file

@ -7,7 +7,7 @@
import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useCallback } from 'react';
import { useUrlParams } from '../../../hooks';
import { useAbsoluteDate, useUrlParams } from '../../../hooks';
import { useDimensions } from '../../../hooks';
import { SyntheticsDatePicker } from '../../common/date_picker/synthetics_date_picker';
import { AvailabilityPanel } from '../monitor_summary/availability_panel';
@ -28,6 +28,7 @@ const STATS_WIDTH_SINGLE_COLUMN_THRESHOLD = 360; // ✨ determined by trial and
export const MonitorHistory = () => {
const [useGetUrlParams, updateUrlParams] = useUrlParams();
const { dateRangeStart, dateRangeEnd } = useGetUrlParams();
const { from, to } = useAbsoluteDate({ from: dateRangeStart, to: dateRangeEnd });
const { elementRef: statsRef, width: statsWidth } = useDimensions<HTMLDivElement>();
const statsColumns = statsWidth && statsWidth < STATS_WIDTH_SINGLE_COLUMN_THRESHOLD ? 1 : 2;
@ -56,45 +57,45 @@ export const MonitorHistory = () => {
<EuiFlexItem>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem>
<MonitorCompleteCount from={dateRangeStart} to={dateRangeEnd} />
<MonitorCompleteCount from={from} to={to} />
</EuiFlexItem>
<EuiFlexItem>
<MonitorCompleteSparklines from={dateRangeStart} to={dateRangeEnd} />
<MonitorCompleteSparklines from={from} to={to} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem>
<AvailabilityPanel from={dateRangeStart} to={dateRangeEnd} />
<AvailabilityPanel from={from} to={to} />
</EuiFlexItem>
<EuiFlexItem>
<AvailabilitySparklines from={dateRangeStart} to={dateRangeEnd} />
<AvailabilitySparklines from={from} to={to} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem>
<MonitorErrorsCount from={dateRangeStart} to={dateRangeEnd} />
<MonitorErrorsCount from={from} to={to} />
</EuiFlexItem>
<EuiFlexItem>
<MonitorErrorSparklines from={dateRangeStart} to={dateRangeEnd} />
<MonitorErrorSparklines from={from} to={to} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem>
<DurationPanel from={dateRangeStart} to={dateRangeEnd} />
<DurationPanel from={from} to={to} />
</EuiFlexItem>
<EuiFlexItem>
<DurationSparklines from={dateRangeStart} to={dateRangeEnd} />
<DurationSparklines from={from} to={to} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<MonitorTotalRunsCount from={dateRangeStart} to={dateRangeEnd} />
<MonitorTotalRunsCount from={from} to={to} />
</EuiFlexItem>
</EuiFlexGrid>
</EuiPanel>
@ -104,15 +105,15 @@ export const MonitorHistory = () => {
<EuiTitle size="xs">
<h3>{DURATION_TREND_LABEL}</h3>
</EuiTitle>
<MonitorDurationTrend from={dateRangeStart} to={dateRangeEnd} />
<MonitorDurationTrend from={from} to={to} />
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<MonitorStatusPanel
from={dateRangeStart}
to={dateRangeEnd}
from={from}
to={to}
showViewHistoryButton={false}
periodCaption={''}
brushable={true}
@ -120,7 +121,7 @@ export const MonitorHistory = () => {
/>
</EuiFlexItem>
<EuiFlexItem>
<TestRunsTable from={dateRangeStart} to={dateRangeEnd} />
<TestRunsTable from={from} to={to} />
</EuiFlexItem>
</EuiFlexGroup>
);

View file

@ -30,7 +30,7 @@ import {
} from '../../../../../../common/runtime_types';
import { formatTestRunAt } from '../../../utils/monitor_test_result/test_time_formats';
import { useSyntheticsSettingsContext } from '../../../contexts';
import { useSyntheticsRefreshContext, useSyntheticsSettingsContext } from '../../../contexts';
import { BrowserStepsList } from '../../common/monitor_test_result/browser_steps_list';
import { SinglePingResult } from '../../common/monitor_test_result/single_ping_result';
import { parseBadgeStatus, StatusBadge } from '../../common/monitor_test_result/status_badge';
@ -43,10 +43,12 @@ import { useMonitorLatestPing } from '../hooks/use_monitor_latest_ping';
export const LastTestRun = () => {
const { euiTheme } = useEuiTheme();
const { latestPing, loading: pingsLoading } = useMonitorLatestPing();
const { lastRefresh } = useSyntheticsRefreshContext();
const { monitor } = useSelectedMonitor();
const { data: stepsData, loading: stepsLoading } = useJourneySteps(
latestPing?.monitor?.check_group
latestPing?.monitor?.check_group,
lastRefresh
);
const loading = stepsLoading || pingsLoading;

View file

@ -31,10 +31,13 @@ import { AvailabilitySparklines } from './availability_sparklines';
import { LastTestRun } from './last_test_run';
import { LAST_10_TEST_RUNS, TestRunsTable } from './test_runs_table';
import { MonitorErrorsCount } from './monitor_errors_count';
import { useAbsoluteDate } from '../../../hooks';
export const MonitorSummary = () => {
const { from, loading } = useEarliestStartDate();
const to = 'now';
const { from: fromRelative, loading } = useEarliestStartDate();
const toRelative = 'now';
const { from, to } = useAbsoluteDate({ from: fromRelative, to: toRelative });
if (loading) {
return <EuiLoadingSpinner size="xl" />;

View file

@ -16,9 +16,11 @@ import { useMonitorQueryId } from '../hooks/use_monitor_query_id';
import { useSelectedMonitor } from '../hooks/use_selected_monitor';
import { ClientPluginsStart } from '../../../../../plugin';
import { useSelectedLocation } from '../hooks/use_selected_location';
import { useAbsoluteDate } from '../../../hooks';
export const StepDurationPanel = ({ legendPosition }: { legendPosition?: Position }) => {
const { observability } = useKibana<ClientPluginsStart>().services;
const time = useAbsoluteDate({ from: 'now-24h/h', to: 'now' });
const { ExploratoryViewEmbeddable } = observability;
@ -60,6 +62,7 @@ export const StepDurationPanel = ({ legendPosition }: { legendPosition?: Positio
legendPosition={legendPosition}
attributes={[
{
time,
name: DURATION_BY_STEP_LABEL,
reportDefinitions: {
'monitor.id': [monitorId],
@ -67,7 +70,6 @@ export const StepDurationPanel = ({ legendPosition }: { legendPosition?: Positio
},
selectedMetricField: isBrowser ? 'synthetics.step.duration.us' : 'monitor.duration.us',
dataType: 'synthetics',
time: { from: 'now-24h/h', to: 'now' },
breakdown: isBrowser ? 'synthetics.step.name.keyword' : 'observer.geo.name',
operationType: 'last_value',
seriesType: 'area_stacked',

View file

@ -35,6 +35,7 @@ import { parseBadgeStatus, StatusBadge } from '../../common/monitor_test_result/
import { useSelectedMonitor } from '../hooks/use_selected_monitor';
import { useMonitorPings } from '../hooks/use_monitor_pings';
import { JourneyScreenshot } from '../../common/screenshot/journey_screenshot';
import { useSyntheticsRefreshContext } from '../../../contexts';
type SortableField = 'timestamp' | 'monitor.status' | 'monitor.duration.us';
@ -52,6 +53,7 @@ export const TestRunsTable = ({ paginable = true, from, to }: TestRunsTableProps
const [sortField, setSortField] = useState<SortableField>('timestamp');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const { lastRefresh } = useSyntheticsRefreshContext();
const {
pings,
total,
@ -59,6 +61,7 @@ export const TestRunsTable = ({ paginable = true, from, to }: TestRunsTableProps
} = useMonitorPings({
from,
to,
lastRefresh,
pageSize: page.size,
pageIndex: page.index,
});

View file

@ -37,20 +37,13 @@ export const OverviewPage: React.FC = () => {
const dispatch = useDispatch();
const { refreshApp, lastRefresh } = useSyntheticsRefreshContext();
const { lastRefresh } = useSyntheticsRefreshContext();
const { query } = useGetUrlParams();
const { search } = useLocation();
const pageState = useSelector(selectOverviewPageState);
const { loading: locationsLoading, locationsLoaded } = useSelector(selectServiceLocationsState);
useEffect(() => {
const interval = setInterval(() => {
refreshApp();
}, 1000 * 30);
return () => clearInterval(interval);
}, [refreshApp]);
useEffect(() => {
if (!locationsLoading && !locationsLoaded) {
dispatch(getServiceLocations());

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { createContext, useContext, useMemo, useState } from 'react';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
interface SyntheticsRefreshContext {
lastRefresh: number;
@ -24,14 +24,21 @@ export const SyntheticsRefreshContext = createContext(defaultContext);
export const SyntheticsRefreshContextProvider: React.FC = ({ children }) => {
const [lastRefresh, setLastRefresh] = useState<number>(Date.now());
const refreshApp = () => {
const refreshApp = useCallback(() => {
const refreshTime = Date.now();
setLastRefresh(refreshTime);
};
}, [setLastRefresh]);
const value = useMemo(() => {
return { lastRefresh, refreshApp };
}, [lastRefresh]);
}, [lastRefresh, refreshApp]);
useEffect(() => {
const interval = setInterval(() => {
refreshApp();
}, 1000 * 30);
return () => clearInterval(interval);
}, [refreshApp]);
return <SyntheticsRefreshContext.Provider value={value} children={children} />;
};

View file

@ -5,6 +5,7 @@
* 2.0.
*/
export * from './use_absolute_date';
export * from './use_url_params';
export * from './use_breadcrumbs';
export * from './use_service_allowed';

View file

@ -0,0 +1,51 @@
/*
* 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 datemath from '@elastic/datemath';
import { renderHook } from '@testing-library/react-hooks';
import moment, { Moment } from 'moment';
import { useAbsoluteDate } from './use_absolute_date';
describe('useAbsoluteDate', () => {
let datemathSpy: jest.SpyInstance<Moment | undefined>;
beforeEach(() => {
datemathSpy = jest.spyOn(datemath, 'parse');
});
afterEach(() => jest.clearAllMocks());
it('returns a parsed value for `from` and `to`', () => {
datemathSpy.mockReturnValueOnce(moment('2022-11-18T18:54:06.342Z'));
datemathSpy.mockReturnValueOnce(moment('2022-11-19T18:54:06.342Z'));
const {
result: {
current: { from, to },
},
} = renderHook(() => useAbsoluteDate({ from: 'now-15m', to: 'now' }));
expect(datemathSpy).toHaveBeenCalledTimes(2);
expect(datemathSpy.mock.calls).toEqual([['now-15m'], ['now']]);
expect(from).toEqual('2022-11-18T18:54:06.342Z');
expect(to).toEqual('2022-11-19T18:54:06.342Z');
});
it('returns the original string if datemath cannot parse the value', () => {
datemathSpy.mockReturnValue(undefined);
const {
result: {
current: { from, to },
},
} = renderHook(() => useAbsoluteDate({ from: 'someinvalidvalue', to: 'anotherinvalidvalue' }));
expect(datemathSpy).toHaveBeenCalledTimes(2);
expect(datemathSpy.mock.calls).toEqual([['someinvalidvalue'], ['anotherinvalidvalue']]);
expect(from).toEqual('someinvalidvalue');
expect(to).toEqual('anotherinvalidvalue');
});
});

View file

@ -0,0 +1,23 @@
/*
* 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 datemath from '@elastic/datemath';
import { useMemo } from 'react';
import { useSyntheticsRefreshContext } from '../contexts';
export function useAbsoluteDate({ from, to }: { from: string; to: string }) {
const { lastRefresh } = useSyntheticsRefreshContext();
return useMemo(
() => ({
from: datemath.parse(from)?.toISOString() ?? from,
to: datemath.parse(to)?.toISOString() ?? to,
}),
// we want to recompute these any time the app refreshes
// eslint-disable-next-line react-hooks/exhaustive-deps
[from, to, lastRefresh]
);
}