[Synthetics] Fix/refactor usage of default url params, fixes loading states (#150367)

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Shahzad 2023-02-21 13:03:23 +01:00 committed by GitHub
parent c074504973
commit db251edf2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 651 additions and 235 deletions

View file

@ -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

View file

@ -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>

View file

@ -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,
};

View file

@ -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'));

View file

@ -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';

View file

@ -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 () => {

View file

@ -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();
}

View file

@ -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
/>
);
};

View file

@ -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>
);
}

View file

@ -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',
});

View file

@ -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({

View file

@ -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,
});
}}
/>
);
};

View file

@ -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'));
});

View file

@ -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

View file

@ -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]);
};

View file

@ -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 }));
}

View file

@ -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>

View file

@ -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],

View file

@ -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} />

View file

@ -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>

View file

@ -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}

View file

@ -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 }}

View file

@ -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}

View file

@ -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}

View file

@ -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) => ({

View file

@ -32,6 +32,7 @@ export const MonitorCompleteCount = (props: MonitorCompleteCountProps) => {
return (
<ExploratoryViewEmbeddable
id="monitorCompleteCount"
align="left"
reportType={ReportTypes.SINGLE_METRIC}
attributes={[

View file

@ -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}

View file

@ -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}

View file

@ -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}

View file

@ -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>

View file

@ -32,6 +32,7 @@ export const MonitorTotalRunsCount = (props: MonitorTotalRunsCountProps) => {
return (
<ExploratoryViewEmbeddable
id="monitorTotalRunsCount"
align="left"
reportType={ReportTypes.SINGLE_METRIC}
attributes={[

View file

@ -68,6 +68,7 @@ export const StepDurationPanel = ({
</EuiFlexGroup>
<ExploratoryViewEmbeddable
id="stepDurationLines"
axisTitlesVisibility={{ yLeft: false, yRight: false, x: false }}
customHeight={'300px'}
reportType={ReportTypes.KPI}

View file

@ -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 />,

View file

@ -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;
};

View file

@ -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(() => {

View file

@ -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(() => {

View file

@ -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
},

View file

@ -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}

View file

@ -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"

View file

@ -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>

View file

@ -32,6 +32,7 @@ export const OverviewErrorsCount = ({
return (
<ExploratoryViewEmbeddable
id="overviewErrorsCount"
align="left"
customHeight="70px"
reportType={ReportTypes.SINGLE_METRIC}

View file

@ -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}

View file

@ -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(() => {

View file

@ -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',
},

View file

@ -115,7 +115,7 @@ export const useNetworkTimings = (checkGroupIdArg?: string, stepIndexArg?: numbe
},
},
},
[checkGroupId, stepIndex],
[],
{ name: `stepNetworkTimingsMetrics/${checkGroupId}/${stepIndex}` }
);

View file

@ -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),
};

View file

@ -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,

View file

@ -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,

View file

@ -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} />;
};

View file

@ -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;
}

View file

@ -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>

View file

@ -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;

View file

@ -78,6 +78,7 @@ export function renderApp(
ReactDOM.render(<SyntheticsApp {...props} />, appMountParameters.element);
return () => {
startPlugins.data.search.session.clear();
ReactDOM.unmountComponentAtNode(appMountParameters.element);
};
}

View file

@ -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;

View file

@ -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');

View file

@ -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;
});
});

View file

@ -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
);

View file

@ -29,6 +29,8 @@ export const mockState: SyntheticsAppState = {
integrationsPopoverOpen: null,
searchText: '',
monitorId: '',
refreshInterval: 60,
refreshPaused: true,
},
serviceLocations: {
throttling: DEFAULT_THROTTLING,

View file

@ -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);

View file

@ -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,

View file

@ -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();

View file

@ -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;
};

View file

@ -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';

View file

@ -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';

View file

@ -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 {

View file

@ -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';

View file

@ -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';

View file

@ -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 })}`;
};

View file

@ -208,6 +208,7 @@ export const renderApp = ({
element
);
return () => {
corePlugins.data.search.session.clear();
ReactDOM.unmountComponentAtNode(element);
};
};