mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[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:
parent
c074504973
commit
db251edf2d
69 changed files with 651 additions and 235 deletions
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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',
|
||||
});
|
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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'));
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]);
|
||||
};
|
|
@ -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 }));
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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) => ({
|
||||
|
|
|
@ -32,6 +32,7 @@ export const MonitorCompleteCount = (props: MonitorCompleteCountProps) => {
|
|||
|
||||
return (
|
||||
<ExploratoryViewEmbeddable
|
||||
id="monitorCompleteCount"
|
||||
align="left"
|
||||
reportType={ReportTypes.SINGLE_METRIC}
|
||||
attributes={[
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -32,6 +32,7 @@ export const MonitorTotalRunsCount = (props: MonitorTotalRunsCountProps) => {
|
|||
|
||||
return (
|
||||
<ExploratoryViewEmbeddable
|
||||
id="monitorTotalRunsCount"
|
||||
align="left"
|
||||
reportType={ReportTypes.SINGLE_METRIC}
|
||||
attributes={[
|
||||
|
|
|
@ -68,6 +68,7 @@ export const StepDurationPanel = ({
|
|||
</EuiFlexGroup>
|
||||
|
||||
<ExploratoryViewEmbeddable
|
||||
id="stepDurationLines"
|
||||
axisTitlesVisibility={{ yLeft: false, yRight: false, x: false }}
|
||||
customHeight={'300px'}
|
||||
reportType={ReportTypes.KPI}
|
||||
|
|
|
@ -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 />,
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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(() => {
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -32,6 +32,7 @@ export const OverviewErrorsCount = ({
|
|||
|
||||
return (
|
||||
<ExploratoryViewEmbeddable
|
||||
id="overviewErrorsCount"
|
||||
align="left"
|
||||
customHeight="70px"
|
||||
reportType={ReportTypes.SINGLE_METRIC}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -115,7 +115,7 @@ export const useNetworkTimings = (checkGroupIdArg?: string, stepIndexArg?: numbe
|
|||
},
|
||||
},
|
||||
},
|
||||
[checkGroupId, stepIndex],
|
||||
[],
|
||||
{ name: `stepNetworkTimingsMetrics/${checkGroupId}/${stepIndex}` }
|
||||
);
|
||||
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -78,6 +78,7 @@ export function renderApp(
|
|||
ReactDOM.render(<SyntheticsApp {...props} />, appMountParameters.element);
|
||||
|
||||
return () => {
|
||||
startPlugins.data.search.session.clear();
|
||||
ReactDOM.unmountComponentAtNode(appMountParameters.element);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -29,6 +29,8 @@ export const mockState: SyntheticsAppState = {
|
|||
integrationsPopoverOpen: null,
|
||||
searchText: '',
|
||||
monitorId: '',
|
||||
refreshInterval: 60,
|
||||
refreshPaused: true,
|
||||
},
|
||||
serviceLocations: {
|
||||
throttling: DEFAULT_THROTTLING,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 })}`;
|
||||
};
|
|
@ -208,6 +208,7 @@ export const renderApp = ({
|
|||
element
|
||||
);
|
||||
return () => {
|
||||
corePlugins.data.search.session.clear();
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue