diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx index c602f4b0d14b0..be9a04e05e73b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/embeddable.tsx @@ -32,6 +32,7 @@ import { AddToCaseAction } from '../header/add_to_case_action'; import { observabilityFeatureId } from '../../../../../common'; export interface ExploratoryEmbeddableProps { + id?: string; appId?: 'securitySolutionUI' | 'observability'; appendTitle?: JSX.Element; attributes?: AllSeries; @@ -46,6 +47,7 @@ export interface ExploratoryEmbeddableProps { legendPosition?: Position; hideTicks?: boolean; onBrushEnd?: (param: { range: number[] }) => void; + onLoad?: (loading: boolean) => void; caseOwner?: string; reportConfigMap?: ReportConfigMap; reportType: ReportViewType; @@ -58,6 +60,7 @@ export interface ExploratoryEmbeddableProps { fontSize?: number; lineHeight?: number; dataTestSubj?: string; + searchSessionId?: string; } export interface ExploratoryEmbeddableComponentProps extends ExploratoryEmbeddableProps { @@ -93,6 +96,8 @@ export default function Embeddable({ noLabel, fontSize = 27, lineHeight = 32, + searchSessionId, + onLoad, }: ExploratoryEmbeddableComponentProps) { const LensComponent = lens?.EmbeddableComponent; const LensSaveModalComponent = lens?.SaveModalComponent; @@ -188,6 +193,9 @@ export default function Embeddable({ return <EuiText>No lens component</EuiText>; } + attributesJSON.state.searchSessionId = searchSessionId; + attributesJSON.searchSessionId = searchSessionId; + return ( <Wrapper $customHeight={customHeight} @@ -229,6 +237,8 @@ export default function Embeddable({ withDefaultActions={Boolean(withActions)} extraActions={actions} viewMode={ViewMode.VIEW} + searchSessionId={searchSessionId} + onLoad={onLoad} /> {isSaveOpen && attributesJSON && ( <LensSaveModalComponent diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/index.tsx index a8f24ec3ee5c5..fd77820865d47 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import type { CoreStart } from '@kbn/core/public'; @@ -38,7 +38,26 @@ export function getExploratoryViewEmbeddable( const lenStateHelperPromise: Promise<{ formula: FormulaPublicApi }> | null = null; + const lastRefreshed: Record<string, { from: string; to: string }> = {}; + + const hasSameTimeRange = (props: ExploratoryEmbeddableProps) => { + const { attributes } = props; + if (!attributes || attributes?.length === 0) { + return false; + } + const series = attributes[0]; + const { time } = series; + const { from, to } = time; + return attributes.every((seriesT) => { + const { time: timeT } = seriesT; + return timeT.from === from && timeT.to === to; + }); + }; + return (props: ExploratoryEmbeddableProps) => { + if (!services.data.search.session.getSessionId()) { + services.data.search.session.start(); + } const { dataTypesIndexPatterns, attributes, customHeight } = props; if (!dataViewsService || !lens || !attributes || attributes?.length === 0) { @@ -56,6 +75,18 @@ export function getExploratoryViewEmbeddable( return lens.stateHelperApi(); }, []); + const [loadCount, setLoadCount] = useState(0); + + const onLensLoaded = useCallback( + (lensLoaded: boolean) => { + if (lensLoaded && props.id && hasSameTimeRange(props) && !lastRefreshed[props.id]) { + lastRefreshed[props.id] = series.time; + } + setLoadCount((prev) => prev + 1); + }, + [props, series.time] + ); + const { dataViews, loading } = useAppDataView({ dataViewCache, dataViewsService, @@ -63,6 +94,24 @@ export function getExploratoryViewEmbeddable( seriesDataType: series?.dataType, }); + const embedProps = useMemo(() => { + const newProps = { ...props }; + if (props.sparklineMode) { + newProps.axisTitlesVisibility = { x: false, yRight: false, yLeft: false }; + newProps.legendIsVisible = false; + newProps.hideTicks = true; + } + if (props.id && lastRefreshed[props.id] && loadCount < 2) { + newProps.attributes = props.attributes?.map((seriesT) => ({ + ...seriesT, + time: lastRefreshed[props.id!], + })); + } else if (props.id) { + lastRefreshed[props.id] = series.time; + } + return newProps; + }, [loadCount, props, series.time]); + if (Object.keys(dataViews).length === 0 || loading || !lensHelper || lensLoading) { return ( <LoadingWrapper customHeight={customHeight}> @@ -75,13 +124,6 @@ export function getExploratoryViewEmbeddable( return <EmptyState height={props.customHeight} />; } - const embedProps = { ...props }; - if (props.sparklineMode) { - embedProps.axisTitlesVisibility = { x: false, yRight: false, yLeft: false }; - embedProps.legendIsVisible = false; - embedProps.hideTicks = true; - } - return ( <EuiErrorBoundary> <EuiThemeProvider darkMode={isDarkMode}> @@ -92,6 +134,8 @@ export function getExploratoryViewEmbeddable( dataViewState={dataViews} lens={lens} lensFormulaHelper={lensHelper.formula} + searchSessionId={services.data.search.session.getSessionId()} + onLoad={onLensLoaded} /> </Wrapper> </KibanaContextProvider> diff --git a/x-pack/plugins/synthetics/common/constants/synthetics/client_defaults.ts b/x-pack/plugins/synthetics/common/constants/synthetics/client_defaults.ts index 3ea9546a1bfac..6ae9dbfef955f 100644 --- a/x-pack/plugins/synthetics/common/constants/synthetics/client_defaults.ts +++ b/x-pack/plugins/synthetics/common/constants/synthetics/client_defaults.ts @@ -14,4 +14,13 @@ export const CLIENT_DEFAULTS_SYNTHETICS = { * The end of the default date range is now. */ DATE_RANGE_END: 'now', + + /** + * The application auto refreshes every 30s by default. + */ + AUTOREFRESH_INTERVAL_SECONDS: 60, + /** + * The application's autorefresh feature is enabled. + */ + AUTOREFRESH_IS_PAUSED: false, }; diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/alert_rules/default_status_alert.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/alert_rules/default_status_alert.journey.ts index 3e1c7a8430afb..666366b9555e4 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/alert_rules/default_status_alert.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/alert_rules/default_status_alert.journey.ts @@ -50,7 +50,7 @@ journey(`DefaultStatusAlert`, async ({ page, params }) => { }); step('Go to monitors page', async () => { - await syntheticsApp.navigateToOverview(true); + await syntheticsApp.navigateToOverview(true, 15); }); step('should create default status alert', async () => { @@ -194,6 +194,7 @@ journey(`DefaultStatusAlert`, async ({ page, params }) => { await page.click(byTestId('alert-status-filter-active-button')); await syntheticsApp.waitForLoadingToFinish(); + await page.waitForTimeout(10 * 1000); await page.click('[aria-label="View in app"]'); await page.click(byTestId('syntheticsMonitorOverviewTab')); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts index b3dcd9aaa4421..bf4ecd526e911 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts @@ -17,7 +17,7 @@ export * from './private_locations.journey'; export * from './alerting_default.journey'; export * from './global_parameters.journey'; export * from './detail_flyout'; -// export * from './alert_rules/default_status_alert.journey'; +export * from './alert_rules/default_status_alert.journey'; export * from './test_now_mode.journey'; export * from './data_retention.journey'; export * from './monitor_details_page/monitor_summary.journey'; diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/overview_sorting.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/overview_sorting.journey.ts index 1809fd66d1978..bed3e5f55d056 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/overview_sorting.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/overview_sorting.journey.ts @@ -32,7 +32,7 @@ journey('OverviewSorting', async ({ page, params }) => { }); step('Go to monitor-management', async () => { - await syntheticsApp.navigateToOverview(true); + await syntheticsApp.navigateToOverview(true, 15); }); step('sort alphabetical asc', async () => { diff --git a/x-pack/plugins/synthetics/e2e/page_objects/synthetics/synthetics_app.tsx b/x-pack/plugins/synthetics/e2e/page_objects/synthetics/synthetics_app.tsx index b3875a052880f..86e15182a740e 100644 --- a/x-pack/plugins/synthetics/e2e/page_objects/synthetics/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/e2e/page_objects/synthetics/synthetics_app.tsx @@ -43,8 +43,14 @@ export function syntheticsAppPageProvider({ page, kibanaUrl }: { page: Page; kib await this.waitForMonitorManagementLoadingToFinish(); }, - async navigateToOverview(doLogin = false) { - await page.goto(overview, { waitUntil: 'networkidle' }); + async navigateToOverview(doLogin = false, refreshInterval?: number) { + if (refreshInterval) { + await page.goto(`${overview}?refreshInterval=${refreshInterval}`, { + waitUntil: 'networkidle', + }); + } else { + await page.goto(overview, { waitUntil: 'networkidle' }); + } if (doLogin) { await this.loginToKibana(); } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/auto_refresh_button.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/auto_refresh_button.tsx new file mode 100644 index 0000000000000..6f40b000a6873 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/auto_refresh_button.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useRef } from 'react'; +import { EuiAutoRefreshButton, OnRefreshChangeProps } from '@elastic/eui'; +import { useDispatch, useSelector } from 'react-redux'; +import { CLIENT_DEFAULTS_SYNTHETICS } from '../../../../../../common/constants/synthetics/client_defaults'; +import { SyntheticsUrlParams } from '../../../utils/url_params'; +import { useUrlParams } from '../../../hooks'; +import { + selectRefreshInterval, + selectRefreshPaused, + setRefreshIntervalAction, + setRefreshPausedAction, +} from '../../../state'; +const { AUTOREFRESH_INTERVAL_SECONDS, AUTOREFRESH_IS_PAUSED } = CLIENT_DEFAULTS_SYNTHETICS; + +const replaceDefaults = ({ refreshPaused, refreshInterval }: Partial<SyntheticsUrlParams>) => { + return { + refreshInterval: refreshInterval === AUTOREFRESH_INTERVAL_SECONDS ? undefined : refreshInterval, + refreshPaused: refreshPaused === AUTOREFRESH_IS_PAUSED ? undefined : refreshPaused, + }; +}; +export const AutoRefreshButton = () => { + const dispatch = useDispatch(); + + const refreshPaused = useSelector(selectRefreshPaused); + const refreshInterval = useSelector(selectRefreshInterval); + + const [getUrlsParams, updateUrlParams] = useUrlParams(); + + const { refreshInterval: urlRefreshInterval, refreshPaused: urlIsPaused } = getUrlsParams(); + + const isFirstRender = useRef(true); + + useEffect(() => { + if (isFirstRender.current) { + // sync url state with redux state on first render + dispatch(setRefreshIntervalAction(urlRefreshInterval)); + dispatch(setRefreshPausedAction(urlIsPaused)); + isFirstRender.current = false; + } else { + // sync redux state with url state on subsequent renders + if (urlRefreshInterval !== refreshInterval || urlIsPaused !== refreshPaused) { + updateUrlParams( + replaceDefaults({ + refreshInterval, + refreshPaused, + }), + true + ); + } + } + }, [updateUrlParams, refreshInterval, refreshPaused, urlRefreshInterval, urlIsPaused, dispatch]); + + const onRefreshChange = (newProps: OnRefreshChangeProps) => { + dispatch(setRefreshIntervalAction(newProps.refreshInterval / 1000)); + dispatch(setRefreshPausedAction(newProps.isPaused)); + + updateUrlParams( + replaceDefaults({ + refreshInterval: newProps.refreshInterval / 1000, + refreshPaused: newProps.isPaused, + }), + true + ); + }; + + return ( + <EuiAutoRefreshButton + size="m" + isPaused={refreshPaused} + refreshInterval={refreshInterval * 1000} + onRefreshChange={onRefreshChange} + shortHand + /> + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/last_refreshed.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/last_refreshed.tsx new file mode 100644 index 0000000000000..28a4ef1c5b101 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/last_refreshed.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState } from 'react'; +import moment from 'moment'; +import { EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useSelector } from 'react-redux'; +import { SHORT_TIMESPAN_LOCALE, SHORT_TS_LOCALE } from '../../../../../../common/constants'; +import { useSyntheticsRefreshContext } from '../../../contexts'; +import { selectRefreshPaused } from '../../../state'; + +export function LastRefreshed() { + const { lastRefresh: lastRefreshed } = useSyntheticsRefreshContext(); + const [refresh, setRefresh] = useState(() => Date.now()); + + const refreshPaused = useSelector(selectRefreshPaused); + + useEffect(() => { + const interVal = setInterval(() => { + setRefresh(Date.now()); + }, 5000); + + return () => { + clearInterval(interVal); + }; + }, []); + + useEffect(() => { + setRefresh(Date.now()); + }, [lastRefreshed]); + + if (!lastRefreshed || refreshPaused) { + return null; + } + + const isWarning = moment().diff(moment(lastRefreshed), 'minutes') > 1; + const isDanger = moment().diff(moment(lastRefreshed), 'minutes') > 5; + + const prevLocal: string = moment.locale() ?? 'en'; + + const shortLocale = moment.locale(SHORT_TS_LOCALE) === SHORT_TS_LOCALE; + if (!shortLocale) { + moment.defineLocale(SHORT_TS_LOCALE, SHORT_TIMESPAN_LOCALE); + } + + const updatedDate = moment(lastRefreshed).from(refresh); + + // Need to reset locale so it doesn't effect other parts of the app + moment.locale(prevLocal); + + return ( + <EuiText + color={isDanger ? 'danger' : isWarning ? 'warning' : 'subdued'} + size="s" + css={{ lineHeight: '40px', fontWeight: isWarning ? 'bold' : undefined }} + > + <FormattedMessage + id="xpack.synthetics.lastUpdated.label" + defaultMessage="Updated {updatedDate}" + values={{ + updatedDate, + }} + /> + </EuiText> + ); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/refresh_button.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/refresh_button.tsx new file mode 100644 index 0000000000000..83a952204ebe7 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/refresh_button.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiButton } from '@elastic/eui'; +import { useSyntheticsRefreshContext } from '../../../contexts'; + +export function RefreshButton() { + const { refreshApp } = useSyntheticsRefreshContext(); + return ( + <EuiButton iconType="refresh" onClick={() => refreshApp()}> + {REFRESH_LABEL} + </EuiButton> + ); +} + +export const REFRESH_LABEL = i18n.translate('xpack.synthetics.overview.refresh', { + defaultMessage: 'Refresh', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.test.tsx index 5a417b511c119..6fae43af920e5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.test.tsx @@ -27,7 +27,7 @@ describe('SyntheticsDatePicker component', () => { it('uses shared date range state when there is no url date range state', async () => { const customHistory = createMemoryHistory({ - initialEntries: ['/?dateRangeStart=now-15m&dateRangeEnd=now'], + initialEntries: ['/?dateRangeStart=now-24h&dateRangeEnd=now'], }); jest.spyOn(customHistory, 'push'); @@ -37,8 +37,6 @@ describe('SyntheticsDatePicker component', () => { core: startPlugins, }); - expect(await findByText('~ 15 minutes ago')).toBeInTheDocument(); - expect(await findByText('~ 30 minutes ago')).toBeInTheDocument(); expect(customHistory.push).toHaveBeenCalledWith({ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.tsx index 61398c562d136..82f2874f9ffa2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.tsx @@ -7,6 +7,7 @@ import React, { useContext, useEffect } from 'react'; import { EuiSuperDatePicker } from '@elastic/eui'; +import { CLIENT_DEFAULTS_SYNTHETICS } from '../../../../../../common/constants/synthetics/client_defaults'; import { useUrlParams } from '../../../hooks'; import { CLIENT_DEFAULTS } from '../../../../../../common/constants'; import { @@ -16,7 +17,7 @@ import { } from '../../../contexts'; const isSyntheticsDefaultDateRange = (dateRangeStart: string, dateRangeEnd: string) => { - const { DATE_RANGE_START, DATE_RANGE_END } = CLIENT_DEFAULTS; + const { DATE_RANGE_START, DATE_RANGE_END } = CLIENT_DEFAULTS_SYNTHETICS; return dateRangeStart === DATE_RANGE_START && dateRangeEnd === DATE_RANGE_END; }; @@ -31,12 +32,7 @@ export const SyntheticsDatePicker = ({ fullWidth }: { fullWidth?: boolean }) => // read time from state and update the url const sharedTimeState = data?.query.timefilter.timefilter.getTime(); - const { - autorefreshInterval, - autorefreshIsPaused, - dateRangeStart: start, - dateRangeEnd: end, - } = getUrlParams(); + const { dateRangeStart: start, dateRangeEnd: end } = getUrlParams(); useEffect(() => { const { from, to } = sharedTimeState ?? {}; @@ -70,8 +66,6 @@ export const SyntheticsDatePicker = ({ fullWidth }: { fullWidth?: boolean }) => start={start} end={end} commonlyUsedRanges={euiCommonlyUsedRanges} - isPaused={autorefreshIsPaused} - refreshInterval={autorefreshInterval} onTimeChange={({ start: startN, end: endN }) => { if (data?.query?.timefilter?.timefilter) { data?.query.timefilter.timefilter.setTime({ from: startN, to: endN }); @@ -81,13 +75,6 @@ export const SyntheticsDatePicker = ({ fullWidth }: { fullWidth?: boolean }) => refreshApp(); }} onRefresh={refreshApp} - onRefreshChange={({ isPaused, refreshInterval }) => { - updateUrl({ - autorefreshInterval: - refreshInterval === undefined ? autorefreshInterval : refreshInterval, - autorefreshIsPaused: isPaused, - }); - }} /> ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/header/action_menu_content.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/header/action_menu_content.test.tsx index 343c548385515..b672e732c791e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/header/action_menu_content.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/header/action_menu_content.test.tsx @@ -14,7 +14,7 @@ describe('ActionMenuContent', () => { const { getByRole, getByText } = render(<ActionMenuContent />); const settingsAnchor = getByRole('link', { name: 'Navigate to the Uptime settings page' }); - expect(settingsAnchor.getAttribute('href')).toBe('/settings?dateRangeStart=now-24h'); + expect(settingsAnchor.getAttribute('href')).toBe('/settings'); expect(getByText('Settings')); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/header/action_menu_content.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/header/action_menu_content.tsx index a47d1cd986249..a81b4a76eba99 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/header/action_menu_content.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/header/action_menu_content.tsx @@ -11,6 +11,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useHistory, useRouteMatch } from 'react-router-dom'; import { createExploratoryViewUrl } from '@kbn/observability-plugin/public'; +import { LastRefreshed } from '../components/last_refreshed'; +import { AutoRefreshButton } from '../components/auto_refresh_button'; import { useSyntheticsSettingsContext } from '../../../contexts'; import { useGetUrlParams } from '../../../hooks'; import { MONITOR_ROUTE, SETTINGS_ROUTE } from '../../../../../../common/constants'; @@ -66,6 +68,8 @@ export function ActionMenuContent(): React.ReactElement { return ( <EuiHeaderLinks gutterSize="xs"> + <LastRefreshed /> + <AutoRefreshButton /> <ToggleAlertFlyoutButton /> <EuiHeaderLink diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_earliest_start_date.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_range_from.ts similarity index 67% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_earliest_start_date.ts rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_range_from.ts index 14429fe2477ff..fe858f0a0056a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_earliest_start_date.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_monitor_range_from.ts @@ -7,18 +7,21 @@ import { useMemo } from 'react'; import moment from 'moment'; +import { useRefreshedRange } from '../../../hooks'; import { useSelectedMonitor } from './use_selected_monitor'; -export const useEarliestStartDate = () => { +export const useMonitorRangeFrom = () => { const { monitor, loading } = useSelectedMonitor(); + const { from, to } = useRefreshedRange(30, 'days'); + return useMemo(() => { if (monitor?.created_at) { const diff = moment(monitor?.created_at).diff(moment().subtract(30, 'day'), 'days'); if (diff > 0) { - return { from: monitor?.created_at, loading }; + return { to, from: monitor?.created_at, loading }; } } - return { from: 'now-30d/d', loading }; - }, [monitor?.created_at, loading]); + return { to, from, loading }; + }, [monitor?.created_at, to, from, loading]); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx index cea0378020c57..fe2d6028151db 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_monitor.tsx @@ -16,6 +16,7 @@ import { selectMonitorListState, selectorMonitorDetailsState, selectorError, + selectRefreshInterval, } from '../../../state'; export const useSelectedMonitor = (monId?: string) => { @@ -26,13 +27,14 @@ export const useSelectedMonitor = (monId?: string) => { } const monitorsList = useSelector(selectEncryptedSyntheticsSavedMonitors); const { loading: monitorListLoading } = useSelector(selectMonitorListState); + const refreshInterval = useSelector(selectRefreshInterval); const monitorFromList = useMemo( () => monitorsList.find((monitor) => monitor[ConfigKey.CONFIG_ID] === monitorId) ?? null, [monitorId, monitorsList] ); const error = useSelector(selectorError); - const { lastRefresh, refreshInterval } = useSyntheticsRefreshContext(); + const { lastRefresh } = useSyntheticsRefreshContext(); const { syntheticsMonitor, syntheticsMonitorLoading, syntheticsMonitorDispatchedAt } = useSelector(selectorMonitorDetailsState); const dispatch = useDispatch(); @@ -60,7 +62,7 @@ export const useSelectedMonitor = (monId?: string) => { !syntheticsMonitorLoading && !monitorListLoading && syntheticsMonitorDispatchedAt > 0 && - Date.now() - syntheticsMonitorDispatchedAt > refreshInterval + Date.now() - syntheticsMonitorDispatchedAt > refreshInterval * 1000 ) { dispatch(getMonitorAction.get({ monitorId })); } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_tab_content.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_tab_content.tsx index cc7ea08168ccc..d16e3faf353de 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_tab_content.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/errors_tab_content.tsx @@ -15,7 +15,7 @@ import { MonitorErrorsCount } from '../monitor_summary/monitor_errors_count'; import { FailedTestsCount } from './failed_tests_count'; import { MonitorFailedTests } from './failed_tests'; import { ErrorsList } from './errors_list'; -import { useAbsoluteDate, useGetUrlParams } from '../../../hooks'; +import { useRefreshedRangeFromUrl } from '../../../hooks'; import { useMonitorQueryId } from '../hooks/use_monitor_query_id'; export const ErrorsTabContent = ({ @@ -25,9 +25,7 @@ export const ErrorsTabContent = ({ errorStates: PingState[]; loading: boolean; }) => { - const { dateRangeStart, dateRangeEnd } = useGetUrlParams(); - - const time = useAbsoluteDate({ from: dateRangeStart, to: dateRangeEnd }); + const time = useRefreshedRangeFromUrl(); const monitorId = useMonitorQueryId(); @@ -39,11 +37,16 @@ export const ErrorsTabContent = ({ <EuiFlexGroup> <EuiFlexItem> {monitorId && ( - <MonitorErrorsCount from={time.from} to={time.to} monitorId={[monitorId]} /> + <MonitorErrorsCount + from={time.from} + to={time.to} + monitorId={[monitorId]} + id="monitorsErrorsCountErrors" + /> )} </EuiFlexItem> <EuiFlexItem> - <FailedTestsCount from={time.from} to={time.to} /> + <FailedTestsCount from={time.from} to={time.to} id="failedTestsCountErrors" /> </EuiFlexItem> </EuiFlexGroup> </PanelWithTitle> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/failed_tests_count.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/failed_tests_count.tsx index 8192142932dc8..99ef360b886b3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/failed_tests_count.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/failed_tests_count.tsx @@ -12,7 +12,7 @@ import { FAILED_TESTS_LABEL } from './failed_tests'; import { ClientPluginsStart } from '../../../../../plugin'; import { useMonitorQueryId } from '../hooks/use_monitor_query_id'; -export const FailedTestsCount = (time: { to: string; from: string }) => { +export const FailedTestsCount = ({ from, to, id }: { to: string; from: string; id: string }) => { const { observability } = useKibana<ClientPluginsStart>().services; const { ExploratoryViewEmbeddable } = observability; @@ -27,10 +27,11 @@ export const FailedTestsCount = (time: { to: string; from: string }) => { return ( <ExploratoryViewEmbeddable + id={id} reportType="single-metric" attributes={[ { - time, + time: { from, to }, reportDefinitions: { 'monitor.id': [monitorId], 'observer.geo.name': [selectedLocation?.label], diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx index ce367ef72ba3d..4d2794e9da995 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx @@ -15,6 +15,7 @@ import { } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; +import { useMonitorDetailsPage } from '../use_monitor_details_page'; import { useMonitorErrors } from '../hooks/use_monitor_errors'; import { SyntheticsDatePicker } from '../../common/date_picker/synthetics_date_picker'; import { ErrorsTabContent } from './errors_tab_content'; @@ -26,6 +27,11 @@ export const MonitorErrors = () => { const emptyState = !loading && errorStates.length === 0; + const redirect = useMonitorDetailsPage(); + if (redirect) { + return redirect; + } + return ( <> <SyntheticsDatePicker fullWidth={true} /> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx index b4f40aeef45c1..5dd0570cde7e3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx @@ -7,7 +7,8 @@ import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useCallback } from 'react'; -import { useAbsoluteDate, useUrlParams } from '../../../hooks'; +import { useMonitorDetailsPage } from '../use_monitor_details_page'; +import { useRefreshedRangeFromUrl, useUrlParams } from '../../../hooks'; import { useDimensions } from '../../../hooks'; import { SyntheticsDatePicker } from '../../common/date_picker/synthetics_date_picker'; import { AvailabilityPanel } from '../monitor_summary/availability_panel'; @@ -27,9 +28,8 @@ import { useMonitorQueryId } from '../hooks/use_monitor_query_id'; const STATS_WIDTH_SINGLE_COLUMN_THRESHOLD = 360; // ✨ determined by trial and error export const MonitorHistory = () => { - const [useGetUrlParams, updateUrlParams] = useUrlParams(); - const { dateRangeStart, dateRangeEnd } = useGetUrlParams(); - const { from, to } = useAbsoluteDate({ from: dateRangeStart, to: dateRangeEnd }); + const [, updateUrlParams] = useUrlParams(); + const { from, to } = useRefreshedRangeFromUrl(); const { elementRef: statsRef, width: statsWidth } = useDimensions<HTMLDivElement>(); const statsColumns = statsWidth && statsWidth < STATS_WIDTH_SINGLE_COLUMN_THRESHOLD ? 1 : 2; @@ -42,6 +42,10 @@ export const MonitorHistory = () => { ); const monitorId = useMonitorQueryId(); + const redirect = useMonitorDetailsPage(); + if (redirect) { + return redirect; + } return ( <EuiFlexGroup direction="column" gutterSize="m"> @@ -70,10 +74,14 @@ export const MonitorHistory = () => { <EuiFlexItem> <EuiFlexGroup gutterSize="xs"> <EuiFlexItem> - <AvailabilityPanel from={from} to={to} /> + <AvailabilityPanel from={from} to={to} id="availabilityPercentageHistory" /> </EuiFlexItem> <EuiFlexItem> - <AvailabilitySparklines from={from} to={to} /> + <AvailabilitySparklines + from={from} + to={to} + id="availabilitySparklineHistory" + /> </EuiFlexItem> </EuiFlexGroup> </EuiFlexItem> @@ -81,12 +89,22 @@ export const MonitorHistory = () => { <EuiFlexGroup gutterSize="xs"> <EuiFlexItem> {monitorId && ( - <MonitorErrorsCount from={from} to={to} monitorId={[monitorId]} /> + <MonitorErrorsCount + from={from} + to={to} + monitorId={[monitorId]} + id="monitorErrorsCountHistory" + /> )} </EuiFlexItem> <EuiFlexItem> {monitorId && ( - <MonitorErrorSparklines from={from} to={to} monitorId={[monitorId]} /> + <MonitorErrorSparklines + from={from} + to={to} + monitorId={[monitorId]} + id="monitorErrorsSparklineHistory" + /> )} </EuiFlexItem> </EuiFlexGroup> @@ -94,10 +112,10 @@ export const MonitorHistory = () => { <EuiFlexItem> <EuiFlexGroup gutterSize="xs"> <EuiFlexItem> - <DurationPanel from={from} to={to} /> + <DurationPanel from={from} to={to} id="durationAvgValueHistory" /> </EuiFlexItem> <EuiFlexItem> - <DurationSparklines from={from} to={to} /> + <DurationSparklines from={from} to={to} id="durationAvgSparklineHistory" /> </EuiFlexItem> </EuiFlexGroup> </EuiFlexItem> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_panel.tsx index cec258a0ea77f..abb5fe617d80e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_panel.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_panel.tsx @@ -16,6 +16,7 @@ import { useSelectedLocation } from '../hooks/use_selected_location'; interface AvailabilityPanelprops { from: string; to: string; + id: string; } export const AvailabilityPanel = (props: AvailabilityPanelprops) => { @@ -34,6 +35,7 @@ export const AvailabilityPanel = (props: AvailabilityPanelprops) => { return ( <ExploratoryViewEmbeddable + id={props.id} align="left" customHeight="70px" reportType={ReportTypes.SINGLE_METRIC} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx index 81ae25d7927fa..4c28fff035d22 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx @@ -16,6 +16,7 @@ import { useSelectedLocation } from '../hooks/use_selected_location'; interface AvailabilitySparklinesProps { from: string; to: string; + id: string; } export const AvailabilitySparklines = (props: AvailabilitySparklinesProps) => { @@ -36,6 +37,7 @@ export const AvailabilitySparklines = (props: AvailabilitySparklinesProps) => { return ( <ExploratoryViewEmbeddable + id={props.id} customHeight="70px" reportType={ReportTypes.KPI} axisTitlesVisibility={{ x: false, yRight: false, yLeft: false }} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_panel.tsx index c646d84833dea..0f859701b6fc8 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_panel.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_panel.tsx @@ -16,6 +16,7 @@ import { useSelectedLocation } from '../hooks/use_selected_location'; interface DurationPanelProps { from: string; to: string; + id: string; } export const DurationPanel = (props: DurationPanelProps) => { @@ -34,6 +35,7 @@ export const DurationPanel = (props: DurationPanelProps) => { return ( <ExploratoryViewEmbeddable + id={props.id} align="left" customHeight="70px" reportType={ReportTypes.SINGLE_METRIC} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx index 01cb826e4b2e3..ca4468763a8f6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx @@ -16,6 +16,7 @@ import { useSelectedLocation } from '../hooks/use_selected_location'; interface DurationSparklinesProps { from: string; to: string; + id: string; } export const DurationSparklines = (props: DurationSparklinesProps) => { @@ -36,6 +37,7 @@ export const DurationSparklines = (props: DurationSparklinesProps) => { return ( <> <ExploratoryViewEmbeddable + id={props.id} reportType={ReportTypes.KPI} axisTitlesVisibility={{ x: false, yRight: false, yLeft: false }} legendIsVisible={false} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_trend.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_trend.tsx index 3df6553380247..b534a7d13d508 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_trend.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_trend.tsx @@ -31,6 +31,7 @@ export const MonitorDurationTrend = (props: MonitorDurationTrendProps) => { return ( <ExploratoryViewEmbeddable + id="monitorDurationTrend" customHeight="240px" reportType="kpi-over-time" attributes={Object.keys(metricsToShow).map((metric) => ({ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_complete_count.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_complete_count.tsx index b9b2c191cdab5..8e8d1a0f6600a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_complete_count.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_complete_count.tsx @@ -32,6 +32,7 @@ export const MonitorCompleteCount = (props: MonitorCompleteCountProps) => { return ( <ExploratoryViewEmbeddable + id="monitorCompleteCount" align="left" reportType={ReportTypes.SINGLE_METRIC} attributes={[ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_complete_sparklines.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_complete_sparklines.tsx index ccb129a17adc2..6b92a6e2b937e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_complete_sparklines.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_complete_sparklines.tsx @@ -33,6 +33,7 @@ export const MonitorCompleteSparklines = (props: Props) => { return ( <ExploratoryViewEmbeddable + id="monitorCompleteSparklines" reportType="kpi-over-time" axisTitlesVisibility={{ x: false, yRight: false, yLeft: false }} legendIsVisible={false} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_error_sparklines.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_error_sparklines.tsx index 53bcf72ca091c..8643f3ef4d009 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_error_sparklines.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_error_sparklines.tsx @@ -16,8 +16,9 @@ interface Props { from: string; to: string; monitorId: string[]; + id: string; } -export const MonitorErrorSparklines = ({ from, to, monitorId }: Props) => { +export const MonitorErrorSparklines = ({ from, to, monitorId, id }: Props) => { const { observability } = useKibana<ClientPluginsStart>().services; const { ExploratoryViewEmbeddable } = observability; @@ -34,6 +35,7 @@ export const MonitorErrorSparklines = ({ from, to, monitorId }: Props) => { return ( <ExploratoryViewEmbeddable + id={id} reportType="kpi-over-time" axisTitlesVisibility={{ x: false, yRight: false, yLeft: false }} legendIsVisible={false} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx index fd823cf1a5437..31a311d3199bd 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx @@ -16,9 +16,10 @@ interface MonitorErrorsCountProps { from: string; to: string; monitorId: string[]; + id: string; } -export const MonitorErrorsCount = ({ monitorId, from, to }: MonitorErrorsCountProps) => { +export const MonitorErrorsCount = ({ monitorId, from, to, id }: MonitorErrorsCountProps) => { const { observability } = useKibana<ClientPluginsStart>().services; const { ExploratoryViewEmbeddable } = observability; @@ -33,6 +34,7 @@ export const MonitorErrorsCount = ({ monitorId, from, to }: MonitorErrorsCountPr return ( <ExploratoryViewEmbeddable + id={id} align="left" customHeight="70px" reportType={ReportTypes.SINGLE_METRIC} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx index 94de32f1ac4cc..b087da10f767c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_summary.tsx @@ -10,9 +10,10 @@ import { EuiTitle, EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } fro import { i18n } from '@kbn/i18n'; import { LoadWhenInView } from '@kbn/observability-plugin/public'; +import { useMonitorDetailsPage } from '../use_monitor_details_page'; +import { useMonitorRangeFrom } from '../hooks/use_monitor_range_from'; import { MonitorAlerts } from './monitor_alerts'; import { useMonitorQueryId } from '../hooks/use_monitor_query_id'; -import { useEarliestStartDate } from '../hooks/use_earliest_start_date'; import { MonitorErrorSparklines } from './monitor_error_sparklines'; import { MonitorStatusPanel } from '../monitor_status/monitor_status_panel'; import { DurationSparklines } from './duration_sparklines'; @@ -25,18 +26,19 @@ 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: fromRelative } = useEarliestStartDate(); - const toRelative = 'now'; - - const { from, to } = useAbsoluteDate({ from: fromRelative, to: toRelative }); + const { from, to } = useMonitorRangeFrom(); const monitorId = useMonitorQueryId(); const dateLabel = from === 'now-30d/d' ? LAST_30_DAYS_LABEL : TO_DATE_LABEL; + const redirect = useMonitorDetailsPage(); + if (redirect) { + return redirect; + } + return ( <> <EuiFlexGroup gutterSize="m"> @@ -59,23 +61,35 @@ export const MonitorSummary = () => { </EuiFlexGroup> <EuiFlexGroup gutterSize="s"> <EuiFlexItem grow={false}> - <AvailabilityPanel from={from} to={to} /> + <AvailabilityPanel from={from} to={to} id="availabilityPercentageSummary" /> </EuiFlexItem> <EuiFlexItem> - <AvailabilitySparklines from={from} to={to} /> + <AvailabilitySparklines from={from} to={to} id="availabilitySparklineSummary" /> </EuiFlexItem> <EuiFlexItem grow={false} style={{ marginLeft: 40 }}> - <DurationPanel from={from} to={to} /> + <DurationPanel from={from} to={to} id="durationAvgValueSummary" /> </EuiFlexItem> <EuiFlexItem> - <DurationSparklines from={from} to={to} /> + <DurationSparklines from={from} to={to} id="durationAvgSparklineSummary" /> </EuiFlexItem> <EuiFlexItem grow={false} style={{ marginLeft: 40 }}> - {monitorId && <MonitorErrorsCount from={from} to={to} monitorId={[monitorId]} />} + {monitorId && ( + <MonitorErrorsCount + from={from} + to={to} + monitorId={[monitorId]} + id="monitorErrorsCountSummary" + /> + )} </EuiFlexItem> <EuiFlexItem> {monitorId && ( - <MonitorErrorSparklines from={from} to={to} monitorId={[monitorId]} /> + <MonitorErrorSparklines + from={from} + to={to} + monitorId={[monitorId]} + id="monitorErrorsSparklineSummary" + /> )} </EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_total_runs_count.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_total_runs_count.tsx index deb02dc98dc87..ef34498c92ab9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_total_runs_count.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_total_runs_count.tsx @@ -32,6 +32,7 @@ export const MonitorTotalRunsCount = (props: MonitorTotalRunsCountProps) => { return ( <ExploratoryViewEmbeddable + id="monitorTotalRunsCount" align="left" reportType={ReportTypes.SINGLE_METRIC} attributes={[ diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/step_duration_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/step_duration_panel.tsx index c84bfb4e0978b..35f12afcd8b40 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/step_duration_panel.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/step_duration_panel.tsx @@ -68,6 +68,7 @@ export const StepDurationPanel = ({ </EuiFlexGroup> <ExploratoryViewEmbeddable + id="stepDurationLines" axisTitlesVisibility={{ yLeft: false, yRight: false, x: false }} customHeight={'300px'} reportType={ReportTypes.KPI} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/route_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/route_config.tsx index 1a2ab66b06ab6..c54ced0b0d94d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/route_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/route_config.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { useHistory, useRouteMatch } from 'react-router-dom'; import { EuiIcon, EuiPageHeaderProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { RefreshButton } from '../common/components/refresh_button'; import { MonitorNotFoundPage } from './monitor_not_found_page'; import { MonitorDetailsPageTitle } from './monitor_details_page_title'; import { RunTestManually } from './run_test_manually'; @@ -20,7 +21,6 @@ import { MonitorErrors } from './monitor_errors/monitor_errors'; import { MonitorHistory } from './monitor_history/monitor_history'; import { MonitorSummary } from './monitor_summary/monitor_summary'; import { EditMonitorLink } from './monitor_summary/edit_monitor_link'; -import { MonitorDetailsPage } from './monitor_details_page'; import { MONITOR_ERRORS_ROUTE, MONITOR_HISTORY_ROUTE, @@ -42,11 +42,7 @@ export const getMonitorDetailsRoute = ( values: { baseTitle }, }), path: MONITOR_ROUTE, - component: () => ( - <MonitorDetailsPage> - <MonitorSummary /> - </MonitorDetailsPage> - ), + component: MonitorSummary, dataTestSubj: 'syntheticsMonitorDetailsPage', pageHeader: getMonitorSummaryHeader(history, syntheticsPath, 'overview'), }, @@ -56,11 +52,7 @@ export const getMonitorDetailsRoute = ( values: { baseTitle }, }), path: MONITOR_HISTORY_ROUTE, - component: () => ( - <MonitorDetailsPage> - <MonitorHistory /> - </MonitorDetailsPage> - ), + component: MonitorHistory, dataTestSubj: 'syntheticsMonitorHistoryPage', pageHeader: getMonitorSummaryHeader(history, syntheticsPath, 'history'), }, @@ -70,11 +62,7 @@ export const getMonitorDetailsRoute = ( values: { baseTitle }, }), path: MONITOR_ERRORS_ROUTE, - component: () => ( - <MonitorDetailsPage> - <MonitorErrors /> - </MonitorDetailsPage> - ), + component: MonitorErrors, dataTestSubj: 'syntheticsMonitorHistoryPage', pageHeader: getMonitorSummaryHeader(history, syntheticsPath, 'errors'), }, @@ -84,7 +72,7 @@ export const getMonitorDetailsRoute = ( values: { baseTitle }, }), path: MONITOR_NOT_FOUND_ROUTE, - component: () => <MonitorNotFoundPage />, + component: MonitorNotFoundPage, dataTestSubj: 'syntheticsMonitorNotFoundPage', pageHeader: { breadcrumbs: [getMonitorsBreadcrumb(syntheticsPath)], @@ -127,6 +115,7 @@ const getMonitorSummaryHeader = ( pageTitle: <MonitorDetailsPageTitle />, breadcrumbs: [getMonitorsBreadcrumb(syntheticsPath)], rightSideItems: [ + <RefreshButton />, <EditMonitorLink />, <RunTestManually />, <MonitorDetailsLastRun />, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/use_monitor_details_page.tsx similarity index 90% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/use_monitor_details_page.tsx index beb7c6e9585d4..831bd9fb446c5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/use_monitor_details_page.tsx @@ -13,7 +13,7 @@ import { ConfigKey } from '../../../../../common/runtime_types'; import { useMonitorListBreadcrumbs } from '../monitors_page/hooks/use_breadcrumbs'; import { useSelectedMonitor } from './hooks/use_selected_monitor'; -export const MonitorDetailsPage: React.FC<{ children: React.ReactElement }> = ({ children }) => { +export const useMonitorDetailsPage = () => { const { monitor, error } = useSelectedMonitor(); const { monitorId } = useParams<{ monitorId: string }>(); @@ -27,5 +27,5 @@ export const MonitorDetailsPage: React.FC<{ children: React.ReactElement }> = ({ ) { return <Redirect to={MONITOR_NOT_FOUND_ROUTE.replace(':monitorId', monitorId)} />; } - return children; + return null; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/no_monitors_found.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/no_monitors_found.test.tsx index 36d98281cc1a0..a995b0617dd38 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/no_monitors_found.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/no_monitors_found.test.tsx @@ -18,7 +18,7 @@ describe('NoMonitorsFound', () => { useUrlParamsSpy = jest.spyOn(URL, 'useUrlParams'); updateUrlParamsMock = jest.fn(); - useUrlParamsSpy.mockImplementation(() => [jest.fn(), updateUrlParamsMock]); + useUrlParamsSpy.mockImplementation(() => [jest.fn().mockReturnValue({}), updateUrlParamsMock]); }); afterEach(() => { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/search_field.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/search_field.test.tsx index 3bd1e226cdd7d..0e0db9ffc9c84 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/search_field.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/search_field.test.tsx @@ -21,7 +21,7 @@ describe('SearchField', () => { useGetUrlParamsSpy = jest.spyOn(URL, 'useGetUrlParams'); updateUrlParamsMock = jest.fn(); - useUrlParamsSpy.mockImplementation(() => [jest.fn(), updateUrlParamsMock]); + useUrlParamsSpy.mockImplementation(() => [jest.fn().mockReturnValue({}), updateUrlParamsMock]); }); afterEach(() => { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx index 84865837bac6e..9fbb1efceeb2d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx @@ -11,7 +11,7 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useTheme } from '@kbn/observability-plugin/public'; import { ReportTypes } from '@kbn/observability-plugin/public'; -import { useAbsoluteDate } from '../../../../hooks'; +import { useRefreshedRange } from '../../../../hooks'; import { ClientPluginsStart } from '../../../../../../plugin'; import * as labels from '../labels'; @@ -21,7 +21,7 @@ export const MonitorTestRunsCount = ({ monitorIds }: { monitorIds: string[] }) = const { ExploratoryViewEmbeddable } = observability; - const { from: absFrom, to: absTo } = useAbsoluteDate({ from: 'now-30d', to: 'now' }); + const { from, to } = useRefreshedRange(30, 'days'); return ( <ExploratoryViewEmbeddable @@ -29,7 +29,7 @@ export const MonitorTestRunsCount = ({ monitorIds }: { monitorIds: string[] }) = reportType={ReportTypes.SINGLE_METRIC} attributes={[ { - time: { from: absFrom, to: absTo }, + time: { from, to }, reportDefinitions: { 'monitor.id': monitorIds.length > 0 ? monitorIds : ['false-monitor-id'], // Show no data when monitorIds is empty }, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx index 8b8d03cd6d879..8791e8e1ce6a8 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx @@ -10,7 +10,7 @@ import React, { useMemo } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useTheme } from '@kbn/observability-plugin/public'; -import { useAbsoluteDate } from '../../../../hooks'; +import { useRefreshedRange } from '../../../../hooks'; import { ClientPluginsStart } from '../../../../../../plugin'; import * as labels from '../labels'; @@ -21,7 +21,7 @@ export const MonitorTestRunsSparkline = ({ monitorIds }: { monitorIds: string[] const theme = useTheme(); - const { from, to } = useAbsoluteDate({ from: 'now-30d', to: 'now' }); + const { from, to } = useRefreshedRange(30, 'days'); const attributes = useMemo(() => { return [ @@ -44,6 +44,7 @@ export const MonitorTestRunsSparkline = ({ monitorIds }: { monitorIds: string[] return ( <ExploratoryViewEmbeddable + id="monitor-test-runs-sparkline" reportType="kpi-over-time" axisTitlesVisibility={{ x: false, yRight: false, yLeft: false }} legendIsVisible={false} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx index 13629e2e64c54..ffcec3f0aeb74 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx @@ -20,11 +20,11 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useSelector } from 'react-redux'; import { selectOverviewStatus } from '../../../../state/overview_status'; import { AlertsLink } from '../../../common/links/view_alerts'; -import { useAbsoluteDate } from '../../../../hooks'; +import { useRefreshedRange } from '../../../../hooks'; import { ClientPluginsStart } from '../../../../../../plugin'; export const OverviewAlerts = () => { - const { from, to } = useAbsoluteDate({ from: 'now-12h', to: 'now' }); + const { from, to } = useRefreshedRange(12, 'hours'); const { observability } = useKibana<ClientPluginsStart>().services; const { ExploratoryViewEmbeddable } = observability; @@ -47,6 +47,7 @@ export const OverviewAlerts = () => { <EuiFlexGroup alignItems="center" gutterSize="m"> <EuiFlexItem grow={false}> <ExploratoryViewEmbeddable + id="monitorActiveAlertsCount" dataTestSubj="monitorActiveAlertsCount" reportType="single-metric" customHeight="70px" @@ -74,6 +75,7 @@ export const OverviewAlerts = () => { </EuiFlexItem> <EuiFlexItem> <ExploratoryViewEmbeddable + id="monitorActiveAlertsOverTime" sparklineMode customHeight="70px" reportType="kpi-over-time" diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors.tsx index bfc57a2b55778..68548fa1af700 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors.tsx @@ -18,7 +18,7 @@ import { useSelector } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { selectOverviewStatus } from '../../../../../state/overview_status'; import { OverviewErrorsSparklines } from './overview_errors_sparklines'; -import { useAbsoluteDate } from '../../../../../hooks'; +import { useRefreshedRange } from '../../../../../hooks'; import { OverviewErrorsCount } from './overview_errors_count'; export function OverviewErrors() { @@ -26,7 +26,7 @@ export function OverviewErrors() { const loading = !status?.allIds || status?.allIds.length === 0; - const { from, to } = useAbsoluteDate({ from: 'now-6h', to: 'now' }); + const { from, to } = useRefreshedRange(6, 'hours'); return ( <EuiPanel hasShadow={false} hasBorder> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_count.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_count.tsx index e2036ec82738c..1d0ce0a90fc3b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_count.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_count.tsx @@ -32,6 +32,7 @@ export const OverviewErrorsCount = ({ return ( <ExploratoryViewEmbeddable + id="overviewErrorsCount" align="left" customHeight="70px" reportType={ReportTypes.SINGLE_METRIC} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_sparklines.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_sparklines.tsx index 93199935d556b..110aacad28a67 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_sparklines.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_sparklines.tsx @@ -27,6 +27,7 @@ export const OverviewErrorsSparklines = ({ from, to, monitorIds }: Props) => { return ( <ExploratoryViewEmbeddable + id="overviewErrorsSparklines" reportType="kpi-over-time" axisTitlesVisibility={{ x: false, yRight: false, yLeft: false }} legendIsVisible={false} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/quick_filters.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/quick_filters.test.tsx index e2643d915d4ac..6b4d0817c367b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/quick_filters.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/quick_filters.test.tsx @@ -22,7 +22,7 @@ describe('QuickFilters', () => { useGetUrlParamsSpy = jest.spyOn(URL, 'useGetUrlParams'); updateUrlParamsMock = jest.fn(); - useUrlParamsSpy.mockImplementation(() => [jest.fn(), updateUrlParamsMock]); + useUrlParamsSpy.mockImplementation(() => [jest.fn().mockReturnValue({}), updateUrlParamsMock]); }); afterEach(() => { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/route_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/route_config.tsx index 84b85cdb2579f..57f40f5676593 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/route_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/route_config.tsx @@ -7,9 +7,10 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useHistory } from 'react-router-dom'; +import { useHistory, useLocation } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; +import { RefreshButton } from '../common/components/refresh_button'; import { OverviewPage } from './overview/overview_page'; import { MonitorsPageHeader } from './management/page_header/monitors_page_header'; import { CreateMonitorButton } from './create_monitor_button'; @@ -19,9 +20,14 @@ import { MONITORS_ROUTE, OVERVIEW_ROUTE } from '../../../../../common/constants' export const getMonitorsRoute = ( history: ReturnType<typeof useHistory>, + location: ReturnType<typeof useLocation>, syntheticsPath: string, baseTitle: string ): RouteProps[] => { + const sharedProps = { + pageTitle: <MonitorsPageHeader />, + rightSideItems: [<RefreshButton />, <CreateMonitorButton />], + }; return [ { title: i18n.translate('xpack.synthetics.overviewRoute.title', { @@ -32,9 +38,8 @@ export const getMonitorsRoute = ( component: OverviewPage, dataTestSubj: 'syntheticsOverviewPage', pageHeader: { - pageTitle: <MonitorsPageHeader />, - rightSideItems: [<CreateMonitorButton />], - tabs: getMonitorsTabs(syntheticsPath, 'overview'), + ...sharedProps, + tabs: getMonitorsTabs(syntheticsPath, 'overview', location), }, }, { @@ -46,15 +51,18 @@ export const getMonitorsRoute = ( component: MonitorsPageWithServiceAllowed, dataTestSubj: 'syntheticsMonitorManagementPage', pageHeader: { - pageTitle: <MonitorsPageHeader />, - rightSideItems: [<CreateMonitorButton />], - tabs: getMonitorsTabs(syntheticsPath, 'management'), + ...sharedProps, + tabs: getMonitorsTabs(syntheticsPath, 'management', location), }, }, ]; }; -const getMonitorsTabs = (syntheticsPath: string, selected: 'overview' | 'management') => { +const getMonitorsTabs = ( + syntheticsPath: string, + selected: 'overview' | 'management', + location: ReturnType<typeof useLocation> +) => { return [ { label: ( @@ -63,7 +71,7 @@ const getMonitorsTabs = (syntheticsPath: string, selected: 'overview' | 'managem defaultMessage="Overview" /> ), - href: `${syntheticsPath}${OVERVIEW_ROUTE}`, + href: `${syntheticsPath}${OVERVIEW_ROUTE}${location.search}`, isSelected: selected === 'overview', 'data-test-subj': 'syntheticsMonitorOverviewTab', }, @@ -74,7 +82,7 @@ const getMonitorsTabs = (syntheticsPath: string, selected: 'overview' | 'managem defaultMessage="Management" /> ), - href: `${syntheticsPath}${MONITORS_ROUTE}`, + href: `${syntheticsPath}${MONITORS_ROUTE}${location.search}`, isSelected: selected === 'management', 'data-test-subj': 'syntheticsMonitorManagementTab', }, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings.ts index 6f53af4cb75be..1f7d2ac810bbd 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings.ts @@ -115,7 +115,7 @@ export const useNetworkTimings = (checkGroupIdArg?: string, stepIndexArg?: numbe }, }, }, - [checkGroupId, stepIndex], + [], { name: `stepNetworkTimingsMetrics/${checkGroupId}/${stepIndex}` } ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings_prev.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings_prev.ts index ec6f96e15259c..65fc47865b103 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings_prev.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings_prev.ts @@ -158,9 +158,9 @@ export const useNetworkTimingsPrevious24Hours = ( }, }, }, - [configId, stepIndex, checkGroupId], + [], { - name: `stepNetworkPreviousTimings/${configId}/${stepIndex}`, + name: `stepNetworkPreviousTimings/${configId}/${checkGroupId}/${stepIndex}`, isRequestReady: Boolean(timestamp), } ); @@ -196,7 +196,7 @@ export const useNetworkTimingsPrevious24Hours = ( }; return { - loading, + loading: loading && !data, timings, timingsWithLabels: getTimingWithLabels(timings), }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_metrics.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_metrics.ts index 7c7d1ecb89828..fba2917c6ece2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_metrics.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_metrics.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { useEsSearch } from '@kbn/observability-plugin/public'; import { useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; +import { useReduxEsSearch } from '../../../hooks/use_redux_es_search'; import { formatBytes } from './use_object_metrics'; import { formatMillisecond } from '../step_metrics/step_metrics'; import { @@ -36,7 +36,7 @@ export const useStepMetrics = (step?: JourneyStep) => { const checkGroupId = step?.monitor.check_group ?? urlParams.checkGroupId; const stepIndex = step?.synthetics.step?.index ?? urlParams.stepIndex; - const { data } = useEsSearch( + const { data } = useReduxEsSearch( { index: SYNTHETICS_INDEX_PATTERN, body: { @@ -91,11 +91,11 @@ export const useStepMetrics = (step?: JourneyStep) => { }, }, }, - [stepIndex, checkGroupId], - { name: 'stepMetrics' } + [], + { name: `stepMetrics/${checkGroupId}/${stepIndex}` } ); - const { data: transferData } = useEsSearch( + const { data: transferData } = useReduxEsSearch( { index: SYNTHETICS_INDEX_PATTERN, body: { @@ -104,9 +104,6 @@ export const useStepMetrics = (step?: JourneyStep) => { 'synthetics.payload.transfer_size': { type: 'double', }, - 'synthetics.payload.resource_size': { - type: 'double', - }, }, query: { bool: { @@ -135,17 +132,12 @@ export const useStepMetrics = (step?: JourneyStep) => { field: 'synthetics.payload.transfer_size', }, }, - resourceSize: { - sum: { - field: 'synthetics.payload.resource_size', - }, - }, }, }, }, - [stepIndex, checkGroupId], + [], { - name: 'stepMetricsFromNetworkInfos', + name: `stepMetricsFromNetworkInfos/${checkGroupId}/${stepIndex}`, } ); @@ -153,10 +145,6 @@ export const useStepMetrics = (step?: JourneyStep) => { const transferDataVal = transferData?.aggregations?.transferSize?.value ?? 0; return { - ...(data?.aggregations ?? {}), - transferData: transferData?.aggregations?.transferSize?.value ?? 0, - resourceSize: transferData?.aggregations?.resourceSize?.value ?? 0, - metrics: [ { label: STEP_DURATION_LABEL, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_prev_metrics.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_prev_metrics.ts index d29c142c03083..9940620f7cc17 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_prev_metrics.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_prev_metrics.ts @@ -6,7 +6,6 @@ */ import { useParams } from 'react-router-dom'; -import { useEsSearch } from '@kbn/observability-plugin/public'; import { formatBytes } from './use_object_metrics'; import { formatMillisecond } from '../step_metrics/step_metrics'; import { @@ -20,6 +19,7 @@ import { import { JourneyStep } from '../../../../../../common/runtime_types'; import { median } from './use_network_timings_prev'; import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; +import { useReduxEsSearch } from '../../../hooks/use_redux_es_search'; export const MONITOR_DURATION_US = 'monitor.duration.us'; export const SYNTHETICS_CLS = 'browser.experience.cls'; @@ -41,7 +41,7 @@ export const useStepPrevMetrics = (step?: JourneyStep) => { const checkGroupId = step?.monitor.check_group ?? urlParams.checkGroupId; const stepIndex = step?.synthetics.step?.index ?? urlParams.stepIndex; - const { data, loading } = useEsSearch( + const { data, loading } = useReduxEsSearch( { index: SYNTHETICS_INDEX_PATTERN, body: { @@ -112,10 +112,10 @@ export const useStepPrevMetrics = (step?: JourneyStep) => { }, }, }, - [monitorId, checkGroupId, stepIndex], - { name: 'previousStepMetrics' } + [], + { name: `previousStepMetrics/${monitorId}/${checkGroupId}/${stepIndex}` } ); - const { data: transferData } = useEsSearch( + const { data: transferData } = useReduxEsSearch( { index: SYNTHETICS_INDEX_PATTERN, body: { @@ -174,9 +174,9 @@ export const useStepPrevMetrics = (step?: JourneyStep) => { }, }, }, - [monitorId, checkGroupId, stepIndex], + [], { - name: 'previousStepMetricsFromNetworkInfos', + name: `previousStepMetricsFromNetworkInfos/${monitorId}/${checkGroupId}/${stepIndex}`, } ); @@ -210,7 +210,7 @@ export const useStepPrevMetrics = (step?: JourneyStep) => { const medianStepDuration = median(stepDuration); return { - loading, + loading: loading && !metrics, metrics: [ { label: STEP_DURATION_LABEL, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx index b6ee5e1616c5c..b1ac1b391696f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_refresh_context.tsx @@ -6,18 +6,16 @@ */ import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { selectRefreshInterval, selectRefreshPaused } from '../state'; interface SyntheticsRefreshContext { lastRefresh: number; - refreshInterval: number; refreshApp: () => void; } -export const APP_DEFAULT_REFRESH_INTERVAL = 1000 * 30; - const defaultContext: SyntheticsRefreshContext = { lastRefresh: 0, - refreshInterval: APP_DEFAULT_REFRESH_INTERVAL, refreshApp: () => { throw new Error('App refresh was not initialized, set it when you invoke the context'); }, @@ -28,21 +26,32 @@ export const SyntheticsRefreshContext = createContext(defaultContext); export const SyntheticsRefreshContextProvider: React.FC = ({ children }) => { const [lastRefresh, setLastRefresh] = useState<number>(Date.now()); + const refreshPaused = useSelector(selectRefreshPaused); + const refreshInterval = useSelector(selectRefreshInterval); + const refreshApp = useCallback(() => { const refreshTime = Date.now(); setLastRefresh(refreshTime); }, [setLastRefresh]); const value = useMemo(() => { - return { lastRefresh, refreshApp, refreshInterval: APP_DEFAULT_REFRESH_INTERVAL }; + return { + lastRefresh, + refreshApp, + }; }, [lastRefresh, refreshApp]); useEffect(() => { + if (refreshPaused) { + return; + } const interval = setInterval(() => { - refreshApp(); - }, value.refreshInterval); + if (document.visibilityState !== 'hidden') { + refreshApp(); + } + }, refreshInterval * 1000); return () => clearInterval(interval); - }, [refreshApp, value.refreshInterval]); + }, [refreshPaused, refreshApp, refreshInterval]); return <SyntheticsRefreshContext.Provider value={value} children={children} />; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_absolute_date.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_absolute_date.ts index 5eb74058d6f67..cba04921a9a06 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_absolute_date.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_absolute_date.ts @@ -7,7 +7,9 @@ import datemath from '@elastic/datemath'; import { useMemo } from 'react'; +import moment, { DurationInputArg1, DurationInputArg2 } from 'moment'; import { useSyntheticsRefreshContext } from '../contexts'; +import { useGetUrlParams } from './use_url_params'; export function useAbsoluteDate({ from, to }: { from: string; to: string }) { const { lastRefresh } = useSyntheticsRefreshContext(); @@ -21,3 +23,30 @@ export function useAbsoluteDate({ from, to }: { from: string; to: string }) { [from, to, lastRefresh] ); } + +export function useRefreshedRange(inp: DurationInputArg1, unit: DurationInputArg2) { + const { lastRefresh } = useSyntheticsRefreshContext(); + + return useMemo( + () => ({ + from: moment(lastRefresh).subtract(inp, unit).toISOString(), + to: new Date(lastRefresh).toISOString(), + }), + [lastRefresh, inp, unit] + ); +} + +const isDefaultRange = (from: string, to: string) => { + return from === 'now-24h' && to === 'now'; +}; + +export function useRefreshedRangeFromUrl() { + const { dateRangeStart, dateRangeEnd } = useGetUrlParams(); + const isDefault = isDefaultRange(dateRangeStart, dateRangeEnd); + + const absRange = useAbsoluteDate({ from: dateRangeStart, to: dateRangeEnd }); + + const defaultRange = useRefreshedRange(24, 'hours'); + + return isDefault ? defaultRange : absRange; +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_url_params.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_url_params.test.tsx index 37a0c3ad7e56f..c067f3b17108f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_url_params.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_url_params.test.tsx @@ -10,7 +10,9 @@ import userEvent from '@testing-library/user-event'; import { render } from '../utils/testing'; import React, { useState, Fragment } from 'react'; import { useUrlParams, SyntheticsUrlParamsHook } from './use_url_params'; -import { APP_DEFAULT_REFRESH_INTERVAL, SyntheticsRefreshContext } from '../contexts'; +import { SyntheticsRefreshContext } from '../contexts'; +import { CLIENT_DEFAULTS_SYNTHETICS } from '../../../../common/constants/synthetics/client_defaults'; +const { AUTOREFRESH_INTERVAL_SECONDS } = CLIENT_DEFAULTS_SYNTHETICS; interface MockUrlParamsComponentProps { hook: SyntheticsUrlParamsHook; @@ -30,7 +32,7 @@ const UseUrlParamsTestComponent = ({ <button id="setUrlParams" onClick={() => { - updateUrlParams(updateParams); + updateUrlParams(updateParams as any); }} > Set url params @@ -54,11 +56,13 @@ describe('useUrlParams', () => { it('accepts router props, updates URL params, and returns the current params', async () => { const { findByText, history } = render( <SyntheticsRefreshContext.Provider - value={{ - lastRefresh: 123, - refreshApp: jest.fn(), - refreshInterval: APP_DEFAULT_REFRESH_INTERVAL, - }} + value={ + { + lastRefresh: 123, + refreshApp: jest.fn(), + refreshInterval: AUTOREFRESH_INTERVAL_SECONDS, + } as any + } > <UseUrlParamsTestComponent hook={useUrlParams} /> </SyntheticsRefreshContext.Provider> @@ -78,11 +82,13 @@ describe('useUrlParams', () => { it('clears search when null is passed to params', async () => { const { findByText, history } = render( <SyntheticsRefreshContext.Provider - value={{ - lastRefresh: 123, - refreshApp: jest.fn(), - refreshInterval: APP_DEFAULT_REFRESH_INTERVAL, - }} + value={ + { + lastRefresh: 123, + refreshApp: jest.fn(), + refreshInterval: AUTOREFRESH_INTERVAL_SECONDS, + } as any + } > <UseUrlParamsTestComponent hook={useUrlParams} updateParams={null} /> </SyntheticsRefreshContext.Provider> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_url_params.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_url_params.ts index 56d547361db2a..74d31c1e52b04 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_url_params.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_url_params.ts @@ -16,9 +16,7 @@ function getParsedParams(search: string) { export type GetUrlParams = () => SyntheticsUrlParams; export type UpdateUrlParams = ( - updatedParams: { - [key: string]: string | number | boolean | undefined; - } | null, + updatedParams: Partial<SyntheticsUrlParams> | null, replaceState?: boolean ) => void; @@ -42,10 +40,12 @@ export const useUrlParams: SyntheticsUrlParamsHook = () => { ...updatedParams, }; + const urlKeys = Object.keys(mergedParams) as Array<keyof SyntheticsUrlParams>; + const updatedSearch = updatedParams ? stringify( // drop any parameters that have no value - Object.keys(mergedParams).reduce((params, key) => { + urlKeys.reduce((params, key) => { const value = mergedParams[key]; if (value === undefined || value === '') { return params; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/render_app.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/render_app.tsx index 76b86c0b82383..85f494b61a87f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/render_app.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/render_app.tsx @@ -78,6 +78,7 @@ export function renderApp( ReactDOM.render(<SyntheticsApp {...props} />, appMountParameters.element); return () => { + startPlugins.data.search.session.clear(); ReactDOM.unmountComponentAtNode(appMountParameters.element); }; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx index 02ea72e39a99e..58f4fc3e1c7b3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx @@ -8,9 +8,8 @@ import { EuiThemeComputed } from '@elastic/eui/src/services/theme/types'; import React, { FC, useEffect } from 'react'; import { EuiButtonEmpty, EuiLink, useEuiTheme } from '@elastic/eui'; -import { Switch, useHistory } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; - +import { Switch, useHistory, useLocation } from 'react-router-dom'; import { OutPortal } from 'react-reverse-portal'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -64,6 +63,7 @@ export const MONITOR_MANAGEMENT_LABEL = i18n.translate( const getRoutes = ( euiTheme: EuiThemeComputed, history: ReturnType<typeof useHistory>, + location: ReturnType<typeof useLocation>, syntheticsPath: string ): RouteProps[] => { return [ @@ -72,7 +72,7 @@ const getRoutes = ( getTestRunDetailsRoute(history, syntheticsPath, baseTitle), getStepDetailsRoute(history, syntheticsPath, baseTitle), ...getMonitorDetailsRoute(history, syntheticsPath, baseTitle), - ...getMonitorsRoute(history, syntheticsPath, baseTitle), + ...getMonitorsRoute(history, location, syntheticsPath, baseTitle), { title: i18n.translate('xpack.synthetics.gettingStartedRoute.title', { defaultMessage: 'Synthetics Getting Started | {baseTitle}', @@ -176,10 +176,12 @@ export const PageRouter: FC = () => { const { addInspectorRequest } = useInspectorContext(); const { euiTheme } = useEuiTheme(); const history = useHistory(); + const location = useLocation(); const routes = getRoutes( euiTheme, history, + location, application.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID) ); const PageTemplateComponent = observability.navigation.PageTemplate; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/actions.ts index d613b3f3d3509..92f261e29dde5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/actions.ts @@ -27,3 +27,5 @@ export const toggleIntegrationsPopover = createAction<PopoverState>( ); export const setSelectedMonitorId = createAction<string>('[UI] SET MONITOR ID'); +export const setRefreshPausedAction = createAction<boolean>('[UI] SET REFRESH PAUSED'); +export const setRefreshIntervalAction = createAction<number>('[UI] SET REFRESH INTERVAL'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/index.ts index 618e6dd524767..9a8a41308d4a9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/index.ts @@ -7,6 +7,7 @@ import { createReducer } from '@reduxjs/toolkit'; +import { CLIENT_DEFAULTS_SYNTHETICS } from '../../../../../common/constants/synthetics/client_defaults'; import { PopoverState, toggleIntegrationsPopover, @@ -16,7 +17,10 @@ import { setAlertFlyoutVisible, setSearchTextAction, setSelectedMonitorId, + setRefreshPausedAction, + setRefreshIntervalAction, } from './actions'; +const { AUTOREFRESH_INTERVAL_SECONDS, AUTOREFRESH_IS_PAUSED } = CLIENT_DEFAULTS_SYNTHETICS; export interface UiState { alertFlyoutVisible: boolean; @@ -26,6 +30,8 @@ export interface UiState { searchText: string; integrationsPopoverOpen: PopoverState | null; monitorId: string; + refreshInterval: number; + refreshPaused: boolean; } const initialState: UiState = { @@ -35,6 +41,8 @@ const initialState: UiState = { searchText: '', integrationsPopoverOpen: null, monitorId: '', + refreshInterval: AUTOREFRESH_INTERVAL_SECONDS, + refreshPaused: AUTOREFRESH_IS_PAUSED, }; export const uiReducer = createReducer(initialState, (builder) => { @@ -59,6 +67,12 @@ export const uiReducer = createReducer(initialState, (builder) => { }) .addCase(setSelectedMonitorId, (state, action) => { state.monitorId = action.payload; + }) + .addCase(setRefreshPausedAction, (state, action) => { + state.refreshPaused = action.payload; + }) + .addCase(setRefreshIntervalAction, (state, action) => { + state.refreshInterval = action.payload; }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/selectors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/selectors.ts index 8896e85f68290..657980c3f6431 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/selectors.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/ui/selectors.ts @@ -31,3 +31,12 @@ export const selectEsKuery = createSelector(uiStateSelector, ({ esKuery }) => es export const selectSearchText = createSelector(uiStateSelector, ({ searchText }) => searchText); export const selectMonitorId = createSelector(uiStateSelector, ({ monitorId }) => monitorId); + +export const selectRefreshPaused = createSelector( + uiStateSelector, + ({ refreshPaused }) => refreshPaused +); +export const selectRefreshInterval = createSelector( + uiStateSelector, + ({ refreshInterval }) => refreshInterval +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts index 848acbe6e9927..051f0731c62e6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts @@ -29,6 +29,8 @@ export const mockState: SyntheticsAppState = { integrationsPopoverOpen: null, searchText: '', monitorId: '', + refreshInterval: 60, + refreshPaused: true, }, serviceLocations: { throttling: DEFAULT_THROTTLING, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.test.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.test.ts index 0af05a77269ad..efabb2034e434 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.test.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.test.ts @@ -40,8 +40,6 @@ describe('getSupportedUrlParams', () => { const expected = { absoluteDateRangeEnd: 20, absoluteDateRangeStart: 20, - autorefreshInterval: 23, - autorefreshIsPaused: false, dateRangeEnd: 'now', dateRangeStart: 'now-15m', search: 'monitor.status: down', @@ -52,15 +50,17 @@ describe('getSupportedUrlParams', () => { }); it('returns default values', () => { - const { AUTOREFRESH_INTERVAL, AUTOREFRESH_IS_PAUSED, FILTERS, SEARCH, STATUS_FILTER } = - CLIENT_DEFAULTS; - const { DATE_RANGE_START, DATE_RANGE_END } = CLIENT_DEFAULTS_SYNTHETICS; + const { FILTERS, SEARCH, STATUS_FILTER } = CLIENT_DEFAULTS; + const { + DATE_RANGE_START, + DATE_RANGE_END, + AUTOREFRESH_INTERVAL_SECONDS, + AUTOREFRESH_IS_PAUSED, + } = CLIENT_DEFAULTS_SYNTHETICS; const result = getSupportedUrlParams({}); expect(result).toEqual({ absoluteDateRangeStart: MOCK_DATE_VALUE, absoluteDateRangeEnd: MOCK_DATE_VALUE, - autorefreshInterval: AUTOREFRESH_INTERVAL, - autorefreshIsPaused: AUTOREFRESH_IS_PAUSED, dateRangeStart: DATE_RANGE_START, dateRangeEnd: DATE_RANGE_END, excludedFilters: '', @@ -75,6 +75,8 @@ describe('getSupportedUrlParams', () => { projects: [], schedules: [], tags: [], + refreshInterval: AUTOREFRESH_INTERVAL_SECONDS, + refreshPaused: AUTOREFRESH_IS_PAUSED, }); }); @@ -86,7 +88,6 @@ describe('getSupportedUrlParams', () => { const expected = { absoluteDateRangeEnd: 20, absoluteDateRangeStart: 20, - autorefreshInterval: 60000, }; expect(result).toMatchObject(expected); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts index 0822069a5625d..ccdc9de7b6a5a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts @@ -16,8 +16,8 @@ import { parseAbsoluteDate } from './parse_absolute_date'; export interface SyntheticsUrlParams { absoluteDateRangeStart: number; absoluteDateRangeEnd: number; - autorefreshInterval: number; - autorefreshIsPaused: boolean; + refreshInterval: number; + refreshPaused: boolean; dateRangeStart: string; dateRangeEnd: string; pagination?: string; @@ -38,17 +38,11 @@ export interface SyntheticsUrlParams { groupOrderBy?: MonitorOverviewState['groupBy']['order']; } -const { - ABSOLUTE_DATE_RANGE_START, - ABSOLUTE_DATE_RANGE_END, - AUTOREFRESH_INTERVAL, - AUTOREFRESH_IS_PAUSED, - SEARCH, - FILTERS, - STATUS_FILTER, -} = CLIENT_DEFAULTS; +const { ABSOLUTE_DATE_RANGE_START, ABSOLUTE_DATE_RANGE_END, SEARCH, FILTERS, STATUS_FILTER } = + CLIENT_DEFAULTS; -const { DATE_RANGE_START, DATE_RANGE_END } = CLIENT_DEFAULTS_SYNTHETICS; +const { DATE_RANGE_START, DATE_RANGE_END, AUTOREFRESH_INTERVAL_SECONDS, AUTOREFRESH_IS_PAUSED } = + CLIENT_DEFAULTS_SYNTHETICS; /** * Gets the current URL values for the application. If no item is present @@ -80,8 +74,8 @@ export const getSupportedUrlParams = (params: { }); const { - autorefreshInterval, - autorefreshIsPaused, + refreshInterval, + refreshPaused, dateRangeStart, dateRangeEnd, filters, @@ -114,8 +108,8 @@ export const getSupportedUrlParams = (params: { ABSOLUTE_DATE_RANGE_END, { roundUp: true } ), - autorefreshInterval: parseUrlInt(autorefreshInterval, AUTOREFRESH_INTERVAL), - autorefreshIsPaused: parseIsPaused(autorefreshIsPaused, AUTOREFRESH_IS_PAUSED), + refreshInterval: parseUrlInt(refreshInterval, AUTOREFRESH_INTERVAL_SECONDS), + refreshPaused: parseIsPaused(refreshPaused, AUTOREFRESH_IS_PAUSED), dateRangeStart: dateRangeStart || DATE_RANGE_START, dateRangeEnd: dateRangeEnd || DATE_RANGE_END, filters: filters || FILTERS, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.test.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.test.ts index c2c0d9caea8eb..6f9ace8634d64 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.test.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.test.ts @@ -12,8 +12,8 @@ describe('stringifyUrlParams', () => { const result = stringifyUrlParams({ absoluteDateRangeStart: 1000, absoluteDateRangeEnd: 2000, - autorefreshInterval: 50000, - autorefreshIsPaused: false, + refreshInterval: 50000, + refreshPaused: false, dateRangeStart: 'now-15m', dateRangeEnd: 'now', filters: 'monitor.id: bar', @@ -22,7 +22,7 @@ describe('stringifyUrlParams', () => { statusFilter: 'up', }); expect(result).toMatchInlineSnapshot( - `"?absoluteDateRangeStart=1000&absoluteDateRangeEnd=2000&autorefreshInterval=50000&autorefreshIsPaused=false&dateRangeStart=now-15m&dateRangeEnd=now&filters=monitor.id%3A%20bar&focusConnectorField=true&search=monitor.id%3A%20foo&statusFilter=up"` + `"?absoluteDateRangeStart=1000&absoluteDateRangeEnd=2000&refreshInterval=50000&refreshPaused=false&dateRangeStart=now-15m&dateRangeEnd=now&filters=monitor.id%3A%20bar&focusConnectorField=true&search=monitor.id%3A%20foo&statusFilter=up"` ); }); @@ -31,8 +31,8 @@ describe('stringifyUrlParams', () => { { absoluteDateRangeStart: 1000, absoluteDateRangeEnd: 2000, - autorefreshInterval: 50000, - autorefreshIsPaused: false, + refreshInterval: 50000, + refreshPaused: false, dateRangeStart: 'now-15m', dateRangeEnd: 'now', filters: 'monitor.id: bar', @@ -44,7 +44,7 @@ describe('stringifyUrlParams', () => { true ); expect(result).toMatchInlineSnapshot( - `"?autorefreshInterval=50000&filters=monitor.id%3A%20bar"` + `"?refreshInterval=50000&dateRangeStart=now-15m&filters=monitor.id%3A%20bar"` ); expect(result.includes('pagination')).toBeFalsy(); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.ts index 59c71af795cce..7f0dd94237593 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/stringify_url_params.ts @@ -6,16 +6,14 @@ */ import { stringify } from 'query-string'; +import { CLIENT_DEFAULTS_SYNTHETICS } from '../../../../../common/constants/synthetics/client_defaults'; import { SyntheticsUrlParams } from './get_supported_url_params'; import { CLIENT_DEFAULTS } from '../../../../../common/constants'; -const { - AUTOREFRESH_INTERVAL, - AUTOREFRESH_IS_PAUSED, - DATE_RANGE_START, - DATE_RANGE_END, - FOCUS_CONNECTOR_FIELD, -} = CLIENT_DEFAULTS; +const { FOCUS_CONNECTOR_FIELD } = CLIENT_DEFAULTS; + +const { DATE_RANGE_START, DATE_RANGE_END, AUTOREFRESH_INTERVAL_SECONDS, AUTOREFRESH_IS_PAUSED } = + CLIENT_DEFAULTS_SYNTHETICS; export const stringifyUrlParams = (params: Partial<SyntheticsUrlParams>, ignoreEmpty = false) => { if (ignoreEmpty) { @@ -24,29 +22,35 @@ export const stringifyUrlParams = (params: Partial<SyntheticsUrlParams>, ignoreE delete params.absoluteDateRangeStart; delete params.absoluteDateRangeEnd; - Object.keys(params).forEach((key: string) => { - // @ts-ignore - const val = params[key]; - if (val == null || val === '') { - // @ts-ignore - delete params[key]; - } - if (key === 'dateRangeStart' && val === DATE_RANGE_START) { - delete params[key]; - } - if (key === 'dateRangeEnd' && val === DATE_RANGE_END) { - delete params[key]; - } - if (key === 'autorefreshIsPaused' && val === AUTOREFRESH_IS_PAUSED) { - delete params[key]; - } - if (key === 'autorefreshInterval' && val === AUTOREFRESH_INTERVAL) { - delete params[key]; - } - if (key === 'focusConnectorField' && val === FOCUS_CONNECTOR_FIELD) { - delete params[key]; - } - }); + replaceDefaults(params); } return `?${stringify(params, { sort: false })}`; }; + +const replaceDefaults = (params: Partial<SyntheticsUrlParams>) => { + Object.keys(params).forEach((key: string) => { + // @ts-ignore + const val = params[key]; + if (val == null || val === '' || val === undefined) { + // @ts-ignore + delete params[key]; + } + if (key === 'dateRangeStart' && val === DATE_RANGE_START) { + delete params[key]; + } + if (key === 'dateRangeEnd' && val === DATE_RANGE_END) { + delete params[key]; + } + if (key === 'refreshPaused' && val === AUTOREFRESH_IS_PAUSED) { + delete params[key]; + } + if (key === 'refreshInterval' && val === AUTOREFRESH_INTERVAL_SECONDS) { + delete params[key]; + } + if (key === 'focusConnectorField' && val === FOCUS_CONNECTOR_FIELD) { + delete params[key]; + } + }); + + return params; +}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/action_menu_content.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/action_menu_content.tsx index e1330d9924261..a0c7f68d6b67a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/action_menu_content.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/common/header/action_menu_content.tsx @@ -13,11 +13,11 @@ import { useHistory, useRouteMatch } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { createExploratoryViewUrl } from '@kbn/observability-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { stringifyUrlParams } from '../../../lib/helper/url_params/stringify_url_params'; import { useUptimeSettingsContext } from '../../../contexts/uptime_settings_context'; import { useGetUrlParams } from '../../../hooks'; import { ToggleAlertFlyoutButton } from '../../overview/alerts/alerts_containers'; import { MONITOR_ROUTE, SETTINGS_ROUTE } from '../../../../../common/constants'; -import { stringifyUrlParams } from '../../../../apps/synthetics/utils/url_params/stringify_url_params'; import { InspectorHeaderLink } from './inspector_header_link'; import { monitorStatusSelector } from '../../../state/selectors'; import { ManageMonitorsBtn } from './manage_monitors_btn'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_name_col.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_name_col.tsx index 0b6cc173acde1..795d195d6da5a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_name_col.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/monitor_name_col.tsx @@ -8,9 +8,9 @@ import React, { useState, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiText } from '@elastic/eui'; +import { stringifyUrlParams } from '../../../../lib/helper/url_params/stringify_url_params'; import { MonitorPageLink } from '../../../common/monitor_page_link'; import { useGetUrlParams } from '../../../../hooks'; -import { stringifyUrlParams } from '../../../../../apps/synthetics/utils/url_params/stringify_url_params'; import { MonitorSummary } from '../../../../../../common/runtime_types/monitor'; import { useFilterUpdate } from '../../../../hooks/use_filter_update'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/most_recent_error.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/most_recent_error.tsx index c5cdc60253f94..59d1a2bfbb80c 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/most_recent_error.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list_drawer/most_recent_error.tsx @@ -13,9 +13,9 @@ import { } from '@elastic/eui'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { stringifyUrlParams } from '../../../../lib/helper/url_params/stringify_url_params'; import { MonitorPageLink } from '../../../common/monitor_page_link'; import { useGetUrlParams } from '../../../../hooks'; -import { stringifyUrlParams } from '../../../../../apps/synthetics/utils/url_params/stringify_url_params'; import { PingError } from '../../../../../../common/runtime_types'; interface MostRecentErrorProps { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_breadcrumbs.ts b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_breadcrumbs.ts index 0befbeb14832c..ec37b3b7a8014 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_breadcrumbs.ts @@ -10,8 +10,8 @@ import { i18n } from '@kbn/i18n'; import { MouseEvent, useEffect } from 'react'; import { EuiBreadcrumb } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { stringifyUrlParams } from '../lib/helper/url_params/stringify_url_params'; import { UptimeUrlParams } from '../lib/helper'; -import { stringifyUrlParams } from '../../apps/synthetics/utils/url_params/stringify_url_params'; import { useUrlParams } from '.'; import { PLUGIN } from '../../../common/constants/plugin'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/rtl_helpers.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/rtl_helpers.tsx index 88ca59f02d1a2..a2288ff031e1d 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/rtl_helpers.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/rtl_helpers.tsx @@ -31,10 +31,10 @@ import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mo import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { Store } from 'redux'; +import { stringifyUrlParams } from './url_params/stringify_url_params'; import { mockState } from '../__mocks__/uptime_store.mock'; import { MountWithReduxProvider } from './helper_with_redux'; import { AppState } from '../../state'; -import { stringifyUrlParams } from '../../../apps/synthetics/utils/url_params/stringify_url_params'; import { ClientPluginsStart } from '../../../plugin'; import { UptimeRefreshContextProvider, UptimeStartupPluginsContextProvider } from '../../contexts'; import { kibanaService } from '../../state/kibana_service'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/url_params/stringify_url_params.ts b/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/url_params/stringify_url_params.ts new file mode 100644 index 0000000000000..53bd763399b8f --- /dev/null +++ b/x-pack/plugins/synthetics/public/legacy_uptime/lib/helper/url_params/stringify_url_params.ts @@ -0,0 +1,52 @@ +/* + * 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 { stringify } from 'query-string'; +import { UptimeUrlParams } from '..'; +import { CLIENT_DEFAULTS } from '../../../../../common/constants'; + +const { + FOCUS_CONNECTOR_FIELD, + DATE_RANGE_START, + DATE_RANGE_END, + AUTOREFRESH_INTERVAL, + AUTOREFRESH_IS_PAUSED, +} = CLIENT_DEFAULTS; + +export const stringifyUrlParams = (params: Partial<UptimeUrlParams>, ignoreEmpty = false) => { + if (ignoreEmpty) { + // We don't want to encode this values because they are often set to Date.now(), the relative + // values in dateRangeStart are better for a URL. + delete params.absoluteDateRangeStart; + delete params.absoluteDateRangeEnd; + + Object.keys(params).forEach((key: string) => { + // @ts-ignore + const val = params[key]; + if (val == null || val === '') { + // @ts-ignore + delete params[key]; + } + if (key === 'dateRangeStart' && val === DATE_RANGE_START) { + delete params[key]; + } + if (key === 'dateRangeEnd' && val === DATE_RANGE_END) { + delete params[key]; + } + if (key === 'autorefreshIsPaused' && val === AUTOREFRESH_IS_PAUSED) { + delete params[key]; + } + if (key === 'autorefreshInterval' && val === AUTOREFRESH_INTERVAL) { + delete params[key]; + } + if (key === 'focusConnectorField' && val === FOCUS_CONNECTOR_FIELD) { + delete params[key]; + } + }); + } + return `?${stringify(params, { sort: false })}`; +}; diff --git a/x-pack/plugins/ux/public/application/ux_app.tsx b/x-pack/plugins/ux/public/application/ux_app.tsx index e96012cd41d12..ca9fd81bcae81 100644 --- a/x-pack/plugins/ux/public/application/ux_app.tsx +++ b/x-pack/plugins/ux/public/application/ux_app.tsx @@ -208,6 +208,7 @@ export const renderApp = ({ element ); return () => { + corePlugins.data.search.session.clear(); ReactDOM.unmountComponentAtNode(element); }; };