mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Unified observability] Fix refresh button in the overview page (#126927)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c1704d9c9d
commit
a13e99c67c
29 changed files with 354 additions and 337 deletions
|
@ -34,7 +34,13 @@ describe('renderApp', () => {
|
|||
data: {
|
||||
query: {
|
||||
timefilter: {
|
||||
timefilter: { setTime: jest.fn(), getTime: jest.fn().mockImplementation(() => ({})) },
|
||||
timefilter: {
|
||||
setTime: jest.fn(),
|
||||
getTime: jest.fn().mockReturnValue({}),
|
||||
getTimeDefaults: jest.fn().mockReturnValue({}),
|
||||
getRefreshInterval: jest.fn().mockReturnValue({}),
|
||||
getRefreshIntervalDefaults: jest.fn().mockReturnValue({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
} from '../../../../../src/plugins/kibana_react/public';
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import type { LazyObservabilityPageTemplateProps } from '../components/shared/page_template/lazy_page_template';
|
||||
import { DatePickerContextProvider } from '../context/date_picker_context';
|
||||
import { HasDataContextProvider } from '../context/has_data_context';
|
||||
import { PluginContext } from '../context/plugin_context';
|
||||
import { useRouteParams } from '../hooks/use_route_params';
|
||||
|
@ -90,9 +91,11 @@ export const renderApp = ({
|
|||
<EuiThemeProvider darkMode={isDarkMode}>
|
||||
<i18nCore.Context>
|
||||
<RedirectAppLinks application={core.application} className={APP_WRAPPER_CLASS}>
|
||||
<HasDataContextProvider>
|
||||
<App />
|
||||
</HasDataContextProvider>
|
||||
<DatePickerContextProvider>
|
||||
<HasDataContextProvider>
|
||||
<App />
|
||||
</HasDataContextProvider>
|
||||
</DatePickerContextProvider>
|
||||
</RedirectAppLinks>
|
||||
</i18nCore.Context>
|
||||
</EuiThemeProvider>
|
||||
|
|
|
@ -21,7 +21,7 @@ import moment from 'moment';
|
|||
import React, { useContext } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { ThemeContext } from 'styled-components';
|
||||
import { useTimeRange } from '../../../../hooks/use_time_range';
|
||||
import { useDatePickerContext } from '../../../../hooks/use_date_picker_context';
|
||||
import { SectionContainer } from '../';
|
||||
import { getDataHandler } from '../../../../data_handler';
|
||||
import { useChartTheme } from '../../../../hooks/use_chart_theme';
|
||||
|
@ -58,11 +58,12 @@ export function APMSection({ bucketSize }: Props) {
|
|||
const chartTheme = useChartTheme();
|
||||
const history = useHistory();
|
||||
const { forceUpdate, hasDataMap } = useHasData();
|
||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange();
|
||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, lastUpdated } =
|
||||
useDatePickerContext();
|
||||
|
||||
const { data, status } = useFetcher(
|
||||
() => {
|
||||
if (bucketSize) {
|
||||
if (bucketSize && absoluteStart && absoluteEnd) {
|
||||
return getDataHandler('apm')?.fetchData({
|
||||
absoluteTime: { start: absoluteStart, end: absoluteEnd },
|
||||
relativeTime: { start: relativeStart, end: relativeEnd },
|
||||
|
@ -70,9 +71,9 @@ export function APMSection({ bucketSize }: Props) {
|
|||
});
|
||||
}
|
||||
},
|
||||
// Absolute times shouldn't be used here, since it would refetch on every render
|
||||
// `forceUpdate` and `lastUpdated` should trigger a reload
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[bucketSize, relativeStart, relativeEnd, forceUpdate]
|
||||
[bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate, lastUpdated]
|
||||
);
|
||||
|
||||
if (!hasDataMap.apm?.hasData) {
|
||||
|
|
|
@ -26,7 +26,7 @@ import { getDataHandler } from '../../../../data_handler';
|
|||
import { useChartTheme } from '../../../../hooks/use_chart_theme';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { useHasData } from '../../../../hooks/use_has_data';
|
||||
import { useTimeRange } from '../../../../hooks/use_time_range';
|
||||
import { useDatePickerContext } from '../../../../hooks/use_date_picker_context';
|
||||
import { LogsFetchDataResponse } from '../../../../typings';
|
||||
import { formatStatValue } from '../../../../utils/format_stat_value';
|
||||
import { ChartContainer } from '../../chart_container';
|
||||
|
@ -57,11 +57,12 @@ export function LogsSection({ bucketSize }: Props) {
|
|||
const history = useHistory();
|
||||
const chartTheme = useChartTheme();
|
||||
const { forceUpdate, hasDataMap } = useHasData();
|
||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange();
|
||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, lastUpdated } =
|
||||
useDatePickerContext();
|
||||
|
||||
const { data, status } = useFetcher(
|
||||
() => {
|
||||
if (bucketSize) {
|
||||
if (bucketSize && absoluteStart && absoluteEnd) {
|
||||
return getDataHandler('infra_logs')?.fetchData({
|
||||
absoluteTime: { start: absoluteStart, end: absoluteEnd },
|
||||
relativeTime: { start: relativeStart, end: relativeEnd },
|
||||
|
@ -69,9 +70,10 @@ export function LogsSection({ bucketSize }: Props) {
|
|||
});
|
||||
}
|
||||
},
|
||||
// Absolute times shouldn't be used here, since it would refetch on every render
|
||||
|
||||
// `forceUpdate` and `lastUpdated` trigger a reload
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[bucketSize, relativeStart, relativeEnd, forceUpdate]
|
||||
[bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate, lastUpdated]
|
||||
);
|
||||
|
||||
if (!hasDataMap.infra_logs?.hasData) {
|
||||
|
|
|
@ -25,7 +25,7 @@ import { SectionContainer } from '../';
|
|||
import { getDataHandler } from '../../../../data_handler';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { useHasData } from '../../../../hooks/use_has_data';
|
||||
import { useTimeRange } from '../../../../hooks/use_time_range';
|
||||
import { useDatePickerContext } from '../../../../hooks/use_date_picker_context';
|
||||
import { HostLink } from './host_link';
|
||||
import { formatDuration } from './lib/format_duration';
|
||||
import { MetricWithSparkline } from './metric_with_sparkline';
|
||||
|
@ -51,25 +51,31 @@ const bytesPerSecondFormatter = (value: NumberOrNull) =>
|
|||
|
||||
export function MetricsSection({ bucketSize }: Props) {
|
||||
const { forceUpdate, hasDataMap } = useHasData();
|
||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange();
|
||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, lastUpdated } =
|
||||
useDatePickerContext();
|
||||
const [sortDirection, setSortDirection] = useState<Direction>('asc');
|
||||
const [sortField, setSortField] = useState<keyof MetricsFetchDataSeries>('uptime');
|
||||
const [sortedData, setSortedData] = useState<MetricsFetchDataResponse | null>(null);
|
||||
|
||||
const { data, status } = useFetcher(
|
||||
() => {
|
||||
if (bucketSize) {
|
||||
return getDataHandler('infra_metrics')?.fetchData({
|
||||
absoluteTime: { start: absoluteStart, end: absoluteEnd },
|
||||
relativeTime: { start: relativeStart, end: relativeEnd },
|
||||
...bucketSize,
|
||||
});
|
||||
}
|
||||
},
|
||||
// Absolute times shouldn't be used here, since it would refetch on every render
|
||||
const { data, status } = useFetcher(() => {
|
||||
if (bucketSize && absoluteStart && absoluteEnd) {
|
||||
return getDataHandler('infra_metrics')?.fetchData({
|
||||
absoluteTime: { start: absoluteStart, end: absoluteEnd },
|
||||
relativeTime: { start: relativeStart, end: relativeEnd },
|
||||
...bucketSize,
|
||||
});
|
||||
}
|
||||
// `forceUpdate` and `lastUpdated` should trigger a reload
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[bucketSize, relativeStart, relativeEnd, forceUpdate]
|
||||
);
|
||||
}, [
|
||||
bucketSize,
|
||||
relativeStart,
|
||||
relativeEnd,
|
||||
absoluteStart,
|
||||
absoluteEnd,
|
||||
forceUpdate,
|
||||
lastUpdated,
|
||||
]);
|
||||
|
||||
const handleTableChange = useCallback(
|
||||
({ sort }: Criteria<MetricsFetchDataSeries>) => {
|
||||
|
@ -125,7 +131,7 @@ export function MetricsSection({ bucketSize }: Props) {
|
|||
<HostLink
|
||||
id={record.id}
|
||||
name={value}
|
||||
timerange={{ from: absoluteStart, to: absoluteEnd }}
|
||||
timerange={{ from: absoluteStart!, to: absoluteEnd! }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
|
|
@ -27,7 +27,7 @@ import { getDataHandler } from '../../../../data_handler';
|
|||
import { useChartTheme } from '../../../../hooks/use_chart_theme';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { useHasData } from '../../../../hooks/use_has_data';
|
||||
import { useTimeRange } from '../../../../hooks/use_time_range';
|
||||
import { useDatePickerContext } from '../../../../hooks/use_date_picker_context';
|
||||
import { Series } from '../../../../typings';
|
||||
import { ChartContainer } from '../../chart_container';
|
||||
import { StyledStat } from '../../styled_stat';
|
||||
|
@ -43,11 +43,12 @@ export function UptimeSection({ bucketSize }: Props) {
|
|||
const chartTheme = useChartTheme();
|
||||
const history = useHistory();
|
||||
const { forceUpdate, hasDataMap } = useHasData();
|
||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange();
|
||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, lastUpdated } =
|
||||
useDatePickerContext();
|
||||
|
||||
const { data, status } = useFetcher(
|
||||
() => {
|
||||
if (bucketSize) {
|
||||
if (bucketSize && absoluteStart && absoluteEnd) {
|
||||
return getDataHandler('synthetics')?.fetchData({
|
||||
absoluteTime: { start: absoluteStart, end: absoluteEnd },
|
||||
relativeTime: { start: relativeStart, end: relativeEnd },
|
||||
|
@ -55,9 +56,9 @@ export function UptimeSection({ bucketSize }: Props) {
|
|||
});
|
||||
}
|
||||
},
|
||||
// Absolute times shouldn't be used here, since it would refetch on every render
|
||||
// `forceUpdate` and `lastUpdated` should trigger a reload
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[bucketSize, relativeStart, relativeEnd, forceUpdate]
|
||||
[bucketSize, relativeStart, relativeEnd, absoluteStart, absoluteEnd, forceUpdate, lastUpdated]
|
||||
);
|
||||
|
||||
if (!hasDataMap.synthetics?.hasData) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { SectionContainer } from '../';
|
|||
import { getDataHandler } from '../../../../data_handler';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { useHasData } from '../../../../hooks/use_has_data';
|
||||
import { useTimeRange } from '../../../../hooks/use_time_range';
|
||||
import { useDatePickerContext } from '../../../../hooks/use_date_picker_context';
|
||||
import CoreVitals from '../../../shared/core_web_vitals';
|
||||
import { BucketSize } from '../../../../pages/overview';
|
||||
|
||||
|
@ -21,13 +21,14 @@ interface Props {
|
|||
|
||||
export function UXSection({ bucketSize }: Props) {
|
||||
const { forceUpdate, hasDataMap } = useHasData();
|
||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange();
|
||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, lastUpdated } =
|
||||
useDatePickerContext();
|
||||
const uxHasDataResponse = hasDataMap.ux;
|
||||
const serviceName = uxHasDataResponse?.serviceName as string;
|
||||
|
||||
const { data, status } = useFetcher(
|
||||
() => {
|
||||
if (serviceName && bucketSize) {
|
||||
if (serviceName && bucketSize && absoluteStart && absoluteEnd) {
|
||||
return getDataHandler('ux')?.fetchData({
|
||||
absoluteTime: { start: absoluteStart, end: absoluteEnd },
|
||||
relativeTime: { start: relativeStart, end: relativeEnd },
|
||||
|
@ -36,9 +37,18 @@ export function UXSection({ bucketSize }: Props) {
|
|||
});
|
||||
}
|
||||
},
|
||||
// Absolute times shouldn't be used here, since it would refetch on every render
|
||||
// `forceUpdate` and `lastUpdated` should trigger a reload
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[bucketSize, relativeStart, relativeEnd, forceUpdate, serviceName]
|
||||
[
|
||||
bucketSize,
|
||||
relativeStart,
|
||||
relativeEnd,
|
||||
absoluteStart,
|
||||
absoluteEnd,
|
||||
forceUpdate,
|
||||
serviceName,
|
||||
lastUpdated,
|
||||
]
|
||||
);
|
||||
|
||||
if (!uxHasDataResponse?.hasData) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import qs from 'query-string';
|
|||
import { DatePicker } from './';
|
||||
import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { of } from 'rxjs';
|
||||
import { DatePickerContextProvider } from '../../../context/date_picker_context';
|
||||
|
||||
let history: MemoryHistory;
|
||||
|
||||
|
@ -69,7 +70,13 @@ function mountDatePicker(initialParams: {
|
|||
data: {
|
||||
query: {
|
||||
timefilter: {
|
||||
timefilter: { setTime: setTimeSpy, getTime: getTimeSpy },
|
||||
timefilter: {
|
||||
setTime: setTimeSpy,
|
||||
getTime: getTimeSpy,
|
||||
getTimeDefaults: jest.fn().mockReturnValue({}),
|
||||
getRefreshIntervalDefaults: jest.fn().mockReturnValue({}),
|
||||
getRefreshInterval: jest.fn().mockReturnValue({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -79,7 +86,9 @@ function mountDatePicker(initialParams: {
|
|||
},
|
||||
}}
|
||||
>
|
||||
<DatePickerWrapper />
|
||||
<DatePickerContextProvider>
|
||||
<DatePickerWrapper />
|
||||
</DatePickerContextProvider>
|
||||
</KibanaContextProvider>
|
||||
</Router>
|
||||
);
|
||||
|
@ -106,7 +115,8 @@ describe('DatePicker', () => {
|
|||
rangeTo: 'now',
|
||||
});
|
||||
|
||||
expect(mockHistoryReplace).toHaveBeenCalledTimes(0);
|
||||
// It updates the URL when it doesn't contain the range.
|
||||
expect(mockHistoryPush).toHaveBeenCalledTimes(1);
|
||||
|
||||
wrapper.find(EuiSuperDatePicker).props().onTimeChange({
|
||||
start: 'now-90m',
|
||||
|
@ -114,7 +124,7 @@ describe('DatePicker', () => {
|
|||
isInvalid: false,
|
||||
isQuickSelection: true,
|
||||
});
|
||||
expect(mockHistoryPush).toHaveBeenCalledTimes(1);
|
||||
expect(mockHistoryPush).toHaveBeenCalledTimes(2);
|
||||
expect(mockHistoryPush).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
search: 'rangeFrom=now-90m&rangeTo=now-60m',
|
||||
|
@ -152,17 +162,6 @@ describe('DatePicker', () => {
|
|||
});
|
||||
|
||||
describe('if both `rangeTo` and `rangeFrom` is set', () => {
|
||||
it('calls setTime ', async () => {
|
||||
const { setTimeSpy } = mountDatePicker({
|
||||
rangeTo: 'now-20m',
|
||||
rangeFrom: 'now-22m',
|
||||
});
|
||||
expect(setTimeSpy).toHaveBeenCalledWith({
|
||||
to: 'now-20m',
|
||||
from: 'now-22m',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not update the url', () => {
|
||||
expect(mockHistoryReplace).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
|
|
@ -6,13 +6,10 @@
|
|||
*/
|
||||
|
||||
import { EuiSuperDatePicker } from '@elastic/eui';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import React, { useCallback } from 'react';
|
||||
import { UI_SETTINGS, useKibanaUISettings } from '../../../hooks/use_kibana_ui_settings';
|
||||
import { fromQuery, toQuery } from '../../../utils/url';
|
||||
import { TimePickerQuickRange } from './typings';
|
||||
import { ObservabilityPublicPluginsStart } from '../../../plugin';
|
||||
import { useDatePickerContext } from '../../../hooks/use_date_picker_context';
|
||||
|
||||
export interface DatePickerProps {
|
||||
rangeFrom?: string;
|
||||
|
@ -29,19 +26,7 @@ export function DatePicker({
|
|||
refreshInterval,
|
||||
onTimeRangeRefresh,
|
||||
}: DatePickerProps) {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const { data } = useKibana<ObservabilityPublicPluginsStart>().services;
|
||||
|
||||
useEffect(() => {
|
||||
// set time if both to and from are given in the url
|
||||
if (rangeFrom && rangeTo) {
|
||||
data.query.timefilter.timefilter.setTime({
|
||||
from: rangeFrom,
|
||||
to: rangeTo,
|
||||
});
|
||||
}
|
||||
}, [data, rangeFrom, rangeTo]);
|
||||
const { updateTimeRange, updateRefreshInterval } = useDatePickerContext();
|
||||
|
||||
const timePickerQuickRanges = useKibanaUISettings<TimePickerQuickRange[]>(
|
||||
UI_SETTINGS.TIMEPICKER_QUICK_RANGES
|
||||
|
@ -53,21 +38,6 @@ export function DatePicker({
|
|||
label: display,
|
||||
}));
|
||||
|
||||
function updateUrl(nextQuery: {
|
||||
rangeFrom?: string;
|
||||
rangeTo?: string;
|
||||
refreshPaused?: boolean;
|
||||
refreshInterval?: number;
|
||||
}) {
|
||||
history.push({
|
||||
...location,
|
||||
search: fromQuery({
|
||||
...toQuery(location.search),
|
||||
...nextQuery,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function onRefreshChange({
|
||||
isPaused,
|
||||
refreshInterval: interval,
|
||||
|
@ -75,23 +45,29 @@ export function DatePicker({
|
|||
isPaused: boolean;
|
||||
refreshInterval: number;
|
||||
}) {
|
||||
updateUrl({ refreshPaused: isPaused, refreshInterval: interval });
|
||||
updateRefreshInterval({ isPaused, interval });
|
||||
}
|
||||
|
||||
function onTimeChange({ start, end }: { start: string; end: string }) {
|
||||
updateUrl({ rangeFrom: start, rangeTo: end });
|
||||
}
|
||||
const onRefresh = useCallback(
|
||||
(newRange: { start: string; end: string }) => {
|
||||
if (onTimeRangeRefresh) {
|
||||
onTimeRangeRefresh(newRange);
|
||||
}
|
||||
updateTimeRange(newRange);
|
||||
},
|
||||
[onTimeRangeRefresh, updateTimeRange]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiSuperDatePicker
|
||||
start={rangeFrom}
|
||||
end={rangeTo}
|
||||
onTimeChange={onTimeChange}
|
||||
onTimeChange={onRefresh}
|
||||
onRefresh={onRefresh}
|
||||
isPaused={refreshPaused}
|
||||
refreshInterval={refreshInterval}
|
||||
onRefreshChange={onRefreshChange}
|
||||
commonlyUsedRanges={commonlyUsedRanges}
|
||||
onRefresh={onTimeRangeRefresh}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { createContext, useState, useMemo, useCallback } from 'react';
|
||||
import useMount from 'react-use/lib/useMount';
|
||||
import { useLocation, useHistory } from 'react-router-dom';
|
||||
import { parse } from 'query-string';
|
||||
import { fromQuery, ObservabilityPublicPluginsStart, toQuery } from '..';
|
||||
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { getAbsoluteTime } from '../utils/date';
|
||||
|
||||
export interface DatePickerContextValue {
|
||||
relativeStart: string;
|
||||
relativeEnd: string;
|
||||
absoluteStart?: number;
|
||||
absoluteEnd?: number;
|
||||
refreshInterval: number;
|
||||
refreshPaused: boolean;
|
||||
updateTimeRange: (params: { start: string; end: string }) => void;
|
||||
updateRefreshInterval: (params: { interval: number; isPaused: boolean }) => void;
|
||||
lastUpdated: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* This context contains the time range (both relative and absolute) and the
|
||||
* autorefresh status of the overview page date picker.
|
||||
* It also updates the URL when any of the values change
|
||||
*/
|
||||
export const DatePickerContext = createContext({} as DatePickerContextValue);
|
||||
|
||||
export function DatePickerContextProvider({ children }: { children: React.ReactElement }) {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
const updateUrl = useCallback(
|
||||
(nextQuery: {
|
||||
rangeFrom?: string;
|
||||
rangeTo?: string;
|
||||
refreshPaused?: boolean;
|
||||
refreshInterval?: number;
|
||||
}) => {
|
||||
history.push({
|
||||
...location,
|
||||
search: fromQuery({
|
||||
...toQuery(location.search),
|
||||
...nextQuery,
|
||||
}),
|
||||
});
|
||||
},
|
||||
[history, location]
|
||||
);
|
||||
|
||||
const [lastUpdated, setLastUpdated] = useState(Date.now());
|
||||
|
||||
const { data } = useKibana<ObservabilityPublicPluginsStart>().services;
|
||||
|
||||
const defaultTimeRange = data.query.timefilter.timefilter.getTimeDefaults();
|
||||
const sharedTimeRange = data.query.timefilter.timefilter.getTime();
|
||||
const defaultRefreshInterval = data.query.timefilter.timefilter.getRefreshIntervalDefaults();
|
||||
const sharedRefreshInterval = data.query.timefilter.timefilter.getRefreshInterval();
|
||||
|
||||
const {
|
||||
rangeFrom = sharedTimeRange.from ?? defaultTimeRange.from,
|
||||
rangeTo = sharedTimeRange.to ?? defaultTimeRange.to,
|
||||
refreshInterval = sharedRefreshInterval.value || defaultRefreshInterval.value || 10000, // we want to override a default of 0
|
||||
refreshPaused = sharedRefreshInterval.pause ?? defaultRefreshInterval.pause,
|
||||
} = parse(location.search, {
|
||||
sort: false,
|
||||
});
|
||||
|
||||
const relativeStart = rangeFrom as string;
|
||||
const relativeEnd = rangeTo as string;
|
||||
|
||||
const absoluteStart = useMemo(
|
||||
() => getAbsoluteTime(relativeStart),
|
||||
// `lastUpdated` works as a cache buster
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[relativeStart, lastUpdated]
|
||||
);
|
||||
|
||||
const absoluteEnd = useMemo(
|
||||
() => getAbsoluteTime(relativeEnd, { roundUp: true }),
|
||||
// `lastUpdated` works as a cache buster
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[relativeEnd, lastUpdated]
|
||||
);
|
||||
|
||||
const updateTimeRange = useCallback(
|
||||
({ start, end }: { start: string; end: string }) => {
|
||||
data.query.timefilter.timefilter.setTime({ from: start, to: end });
|
||||
updateUrl({ rangeFrom: start, rangeTo: end });
|
||||
setLastUpdated(Date.now());
|
||||
},
|
||||
[data.query.timefilter.timefilter, updateUrl]
|
||||
);
|
||||
|
||||
const updateRefreshInterval = useCallback(
|
||||
({ interval, isPaused }) => {
|
||||
updateUrl({ refreshInterval: interval, refreshPaused: isPaused });
|
||||
data.query.timefilter.timefilter.setRefreshInterval({ value: interval, pause: isPaused });
|
||||
setLastUpdated(Date.now());
|
||||
},
|
||||
[data.query.timefilter.timefilter, updateUrl]
|
||||
);
|
||||
|
||||
useMount(() => {
|
||||
updateUrl({ rangeFrom: relativeStart, rangeTo: relativeEnd });
|
||||
});
|
||||
|
||||
return (
|
||||
<DatePickerContext.Provider
|
||||
value={{
|
||||
relativeStart,
|
||||
relativeEnd,
|
||||
absoluteStart,
|
||||
absoluteEnd,
|
||||
refreshInterval: parseRefreshInterval(refreshInterval),
|
||||
refreshPaused: parseRefreshPaused(refreshPaused),
|
||||
updateTimeRange,
|
||||
updateRefreshInterval,
|
||||
lastUpdated,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DatePickerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function parseRefreshInterval(value: string | string[] | number | null): number {
|
||||
switch (typeof value) {
|
||||
case 'number':
|
||||
return value;
|
||||
case 'string':
|
||||
return parseInt(value, 10) || 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function parseRefreshPaused(value: string | string[] | boolean | null): boolean {
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
case 'false':
|
||||
return false;
|
||||
case 'true':
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -10,8 +10,6 @@ import { CoreStart } from 'kibana/public';
|
|||
import React from 'react';
|
||||
import { registerDataHandler, unregisterDataHandler } from '../data_handler';
|
||||
import { useHasData } from '../hooks/use_has_data';
|
||||
import * as routeParams from '../hooks/use_route_params';
|
||||
import * as timeRange from '../hooks/use_time_range';
|
||||
import { HasData, ObservabilityFetchDataPlugins } from '../typings/fetch_overview_data';
|
||||
import { HasDataContextProvider } from './has_data_context';
|
||||
import * as pluginContext from '../hooks/use_plugin_context';
|
||||
|
@ -21,9 +19,6 @@ import { createMemoryHistory } from 'history';
|
|||
import { ApmIndicesConfig } from '../../common/typings';
|
||||
import { act } from '@testing-library/react';
|
||||
|
||||
const relativeStart = '2020-10-08T06:00:00.000Z';
|
||||
const relativeEnd = '2020-10-08T07:00:00.000Z';
|
||||
|
||||
const sampleAPMIndices = { transaction: 'apm-*' } as ApmIndicesConfig;
|
||||
|
||||
function wrapper({ children }: { children: React.ReactElement }) {
|
||||
|
@ -57,19 +52,6 @@ function registerApps<T extends ObservabilityFetchDataPlugins>(
|
|||
|
||||
describe('HasDataContextProvider', () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(routeParams, 'useRouteParams').mockImplementation(() => ({
|
||||
query: {
|
||||
from: relativeStart,
|
||||
to: relativeEnd,
|
||||
},
|
||||
path: {},
|
||||
}));
|
||||
jest.spyOn(timeRange, 'useTimeRange').mockImplementation(() => ({
|
||||
relativeStart,
|
||||
relativeEnd,
|
||||
absoluteStart: new Date(relativeStart).valueOf(),
|
||||
absoluteEnd: new Date(relativeEnd).valueOf(),
|
||||
}));
|
||||
jest.spyOn(pluginContext, 'usePluginContext').mockReturnValue({
|
||||
core: { http: { get: jest.fn() } } as unknown as CoreStart,
|
||||
} as PluginContextValue);
|
||||
|
|
|
@ -12,7 +12,7 @@ import { asyncForEach } from '@kbn/std';
|
|||
import { getDataHandler } from '../data_handler';
|
||||
import { FETCH_STATUS } from '../hooks/use_fetcher';
|
||||
import { usePluginContext } from '../hooks/use_plugin_context';
|
||||
import { useTimeRange } from '../hooks/use_time_range';
|
||||
import { useDatePickerContext } from '../hooks/use_date_picker_context';
|
||||
import { getObservabilityAlerts } from '../services/get_observability_alerts';
|
||||
import { ObservabilityFetchDataPlugins } from '../typings/fetch_overview_data';
|
||||
import { ApmIndicesConfig } from '../../common/typings';
|
||||
|
@ -44,7 +44,7 @@ const apps: DataContextApps[] = ['apm', 'synthetics', 'infra_logs', 'infra_metri
|
|||
export function HasDataContextProvider({ children }: { children: React.ReactNode }) {
|
||||
const { core } = usePluginContext();
|
||||
const [forceUpdate, setForceUpdate] = useState('');
|
||||
const { absoluteStart, absoluteEnd } = useTimeRange();
|
||||
const { absoluteStart, absoluteEnd } = useDatePickerContext();
|
||||
|
||||
const [hasDataMap, setHasDataMap] = useState<HasDataContextValue['hasDataMap']>({});
|
||||
|
||||
|
@ -76,7 +76,7 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode
|
|||
};
|
||||
switch (app) {
|
||||
case 'ux':
|
||||
const params = { absoluteTime: { start: absoluteStart, end: absoluteEnd } };
|
||||
const params = { absoluteTime: { start: absoluteStart!, end: absoluteEnd! } };
|
||||
const resultUx = await getDataHandler(app)?.hasData(params);
|
||||
updateState({
|
||||
hasData: resultUx?.hasData,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useContext } from 'react';
|
||||
import { DatePickerContext } from '../context/date_picker_context';
|
||||
|
||||
export function useDatePickerContext() {
|
||||
return useContext(DatePickerContext);
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useTimeRange } from './use_time_range';
|
||||
import * as pluginContext from './use_plugin_context';
|
||||
import { AppMountParameters, CoreStart } from 'kibana/public';
|
||||
import { ObservabilityPublicPluginsStart } from '../plugin';
|
||||
import * as kibanaUISettings from './use_kibana_ui_settings';
|
||||
import { createObservabilityRuleTypeRegistryMock } from '../rules/observability_rule_type_registry_mock';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useLocation: () => ({
|
||||
pathname: '/observability/overview/',
|
||||
search: '',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('useTimeRange', () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({
|
||||
core: {} as CoreStart,
|
||||
appMountParameters: {} as AppMountParameters,
|
||||
config: {
|
||||
unsafe: {
|
||||
alertingExperience: { enabled: true },
|
||||
cases: { enabled: true },
|
||||
overviewNext: { enabled: false },
|
||||
rules: { enabled: false },
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
data: {
|
||||
query: {
|
||||
timefilter: {
|
||||
timefilter: {
|
||||
getTime: jest.fn().mockImplementation(() => ({
|
||||
from: '2020-10-08T06:00:00.000Z',
|
||||
to: '2020-10-08T07:00:00.000Z',
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ObservabilityPublicPluginsStart,
|
||||
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
||||
ObservabilityPageTemplate: () => null,
|
||||
}));
|
||||
jest.spyOn(kibanaUISettings, 'useKibanaUISettings').mockImplementation(() => ({
|
||||
from: '2020-10-08T05:00:00.000Z',
|
||||
to: '2020-10-08T06:00:00.000Z',
|
||||
}));
|
||||
});
|
||||
|
||||
describe('when range from and to are not provided', () => {
|
||||
describe('when data plugin has time set', () => {
|
||||
it('returns ranges and absolute times from data plugin', () => {
|
||||
const relativeStart = '2020-10-08T06:00:00.000Z';
|
||||
const relativeEnd = '2020-10-08T07:00:00.000Z';
|
||||
const timeRange = useTimeRange();
|
||||
expect(timeRange).toEqual({
|
||||
relativeStart,
|
||||
relativeEnd,
|
||||
absoluteStart: new Date(relativeStart).valueOf(),
|
||||
absoluteEnd: new Date(relativeEnd).valueOf(),
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("when data plugin doesn't have time set", () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({
|
||||
core: {} as CoreStart,
|
||||
appMountParameters: {} as AppMountParameters,
|
||||
config: {
|
||||
unsafe: {
|
||||
alertingExperience: { enabled: true },
|
||||
cases: { enabled: true },
|
||||
overviewNext: { enabled: false },
|
||||
rules: { enabled: false },
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
data: {
|
||||
query: {
|
||||
timefilter: {
|
||||
timefilter: {
|
||||
getTime: jest.fn().mockImplementation(() => ({
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ObservabilityPublicPluginsStart,
|
||||
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
|
||||
ObservabilityPageTemplate: () => null,
|
||||
}));
|
||||
});
|
||||
it('returns ranges and absolute times from kibana default settings', () => {
|
||||
const relativeStart = '2020-10-08T05:00:00.000Z';
|
||||
const relativeEnd = '2020-10-08T06:00:00.000Z';
|
||||
const timeRange = useTimeRange();
|
||||
expect(timeRange).toEqual({
|
||||
relativeStart,
|
||||
relativeEnd,
|
||||
absoluteStart: new Date(relativeStart).valueOf(),
|
||||
absoluteEnd: new Date(relativeEnd).valueOf(),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { parse } from 'query-string';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { TimePickerTimeDefaults } from '../components/shared/date_picker/typings';
|
||||
import { getAbsoluteTime } from '../utils/date';
|
||||
import { UI_SETTINGS, useKibanaUISettings } from './use_kibana_ui_settings';
|
||||
import { usePluginContext } from './use_plugin_context';
|
||||
|
||||
const getParsedParams = (search: string) => {
|
||||
return parse(search.slice(1), { sort: false });
|
||||
};
|
||||
|
||||
export function useTimeRange() {
|
||||
const { plugins } = usePluginContext();
|
||||
|
||||
const timePickerTimeDefaults = useKibanaUISettings<TimePickerTimeDefaults>(
|
||||
UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS
|
||||
);
|
||||
|
||||
const timePickerSharedState = plugins.data.query.timefilter.timefilter.getTime();
|
||||
|
||||
const { rangeFrom, rangeTo } = getParsedParams(useLocation().search);
|
||||
|
||||
const relativeStart = (rangeFrom ??
|
||||
timePickerSharedState.from ??
|
||||
timePickerTimeDefaults.from) as string;
|
||||
const relativeEnd = (rangeTo ?? timePickerSharedState.to ?? timePickerTimeDefaults.to) as string;
|
||||
|
||||
return {
|
||||
relativeStart,
|
||||
relativeEnd,
|
||||
absoluteStart: getAbsoluteTime(relativeStart)!,
|
||||
absoluteEnd: getAbsoluteTime(relativeEnd, { roundUp: true })!,
|
||||
};
|
||||
}
|
|
@ -52,6 +52,7 @@ export const plugin: PluginInitializer<
|
|||
export * from './components/shared/action_menu/';
|
||||
|
||||
export type { UXMetrics } from './components/shared/core_web_vitals/';
|
||||
export { DatePickerContextProvider } from './context/date_picker_context';
|
||||
export {
|
||||
getCoreVitalsComponent,
|
||||
HeaderMenuPortal,
|
||||
|
|
|
@ -20,7 +20,6 @@ import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
|
|||
import { useFetcher } from '../../hooks/use_fetcher';
|
||||
import { useHasData } from '../../hooks/use_has_data';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { useTimeRange } from '../../hooks/use_time_range';
|
||||
import { useAlertIndexNames } from '../../hooks/use_alert_index_names';
|
||||
import { RouteParams } from '../../routes';
|
||||
import { getNewsFeed } from '../../services/get_news_feed';
|
||||
|
@ -32,6 +31,7 @@ import { AlertsTableTGrid } from '../alerts/containers/alerts_table_t_grid/alert
|
|||
import { SectionContainer } from '../../components/app/section';
|
||||
import { ObservabilityAppServices } from '../../application/types';
|
||||
import { useGetUserCasesPermissions } from '../../hooks/use_get_user_cases_permissions';
|
||||
import { useDatePickerContext } from '../../hooks/use_date_picker_context';
|
||||
interface Props {
|
||||
routeParams: RouteParams<'/overview'>;
|
||||
}
|
||||
|
@ -57,29 +57,22 @@ export function OverviewPage({ routeParams }: Props) {
|
|||
|
||||
const { core, ObservabilityPageTemplate } = usePluginContext();
|
||||
|
||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange();
|
||||
|
||||
const relativeTime = { start: relativeStart, end: relativeEnd };
|
||||
const absoluteTime = { start: absoluteStart, end: absoluteEnd };
|
||||
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd, refreshInterval, refreshPaused } =
|
||||
useDatePickerContext();
|
||||
|
||||
const { data: newsFeed } = useFetcher(() => getNewsFeed({ core }), [core]);
|
||||
|
||||
const { hasAnyData, isAllRequestsComplete } = useHasData();
|
||||
const refetch = useRef<() => void>();
|
||||
|
||||
const bucketSize = calculateBucketSize({
|
||||
start: absoluteTime.start,
|
||||
end: absoluteTime.end,
|
||||
});
|
||||
|
||||
const bucketSizeValue = useMemo(() => {
|
||||
if (bucketSize?.bucketSize) {
|
||||
return {
|
||||
bucketSize: bucketSize.bucketSize,
|
||||
intervalString: bucketSize.intervalString,
|
||||
};
|
||||
}
|
||||
}, [bucketSize?.bucketSize, bucketSize?.intervalString]);
|
||||
const bucketSize = useMemo(
|
||||
() =>
|
||||
calculateBucketSize({
|
||||
start: absoluteStart,
|
||||
end: absoluteEnd,
|
||||
}),
|
||||
[absoluteStart, absoluteEnd]
|
||||
);
|
||||
|
||||
const setRefetch = useCallback((ref) => {
|
||||
refetch.current = ref;
|
||||
|
@ -105,8 +98,6 @@ export function OverviewPage({ routeParams }: Props) {
|
|||
docsLink: core.docLinks.links.observability.guide,
|
||||
});
|
||||
|
||||
const { refreshInterval = 10000, refreshPaused = true } = routeParams.query;
|
||||
|
||||
return (
|
||||
<ObservabilityPageTemplate
|
||||
noDataConfig={noDataConfig}
|
||||
|
@ -116,8 +107,8 @@ export function OverviewPage({ routeParams }: Props) {
|
|||
pageTitle: overviewPageTitle,
|
||||
rightSideItems: [
|
||||
<DatePicker
|
||||
rangeFrom={relativeTime.start}
|
||||
rangeTo={relativeTime.end}
|
||||
rangeFrom={relativeStart}
|
||||
rangeTo={relativeEnd}
|
||||
refreshInterval={refreshInterval}
|
||||
refreshPaused={refreshPaused}
|
||||
onTimeRangeRefresh={onTimeRangeRefresh}
|
||||
|
@ -146,8 +137,8 @@ export function OverviewPage({ routeParams }: Props) {
|
|||
>
|
||||
<AlertsTableTGrid
|
||||
setRefetch={setRefetch}
|
||||
rangeFrom={relativeTime.start}
|
||||
rangeTo={relativeTime.end}
|
||||
rangeFrom={relativeStart}
|
||||
rangeTo={relativeEnd}
|
||||
indexNames={indexNames}
|
||||
/>
|
||||
</CasesContext>
|
||||
|
@ -155,7 +146,7 @@ export function OverviewPage({ routeParams }: Props) {
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{/* Data sections */}
|
||||
{hasAnyData && <DataSections bucketSize={bucketSizeValue} />}
|
||||
{hasAnyData && <DataSections bucketSize={bucketSize} />}
|
||||
<EmptySections />
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer size="s" />
|
||||
|
|
|
@ -12,7 +12,7 @@ import { DatePicker } from '../../components/shared/date_picker';
|
|||
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
|
||||
import { useHasData } from '../../hooks/use_has_data';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { useTimeRange } from '../../hooks/use_time_range';
|
||||
import { useDatePickerContext } from '../../hooks/use_date_picker_context';
|
||||
import { RouteParams } from '../../routes';
|
||||
import { getNoDataConfig } from '../../utils/no_data_config';
|
||||
import { LoadingObservability } from './loading_observability';
|
||||
|
@ -36,7 +36,7 @@ export function OverviewPage({ routeParams }: Props) {
|
|||
|
||||
const { core, ObservabilityPageTemplate } = usePluginContext();
|
||||
|
||||
const { relativeStart, relativeEnd } = useTimeRange();
|
||||
const { relativeStart, relativeEnd } = useDatePickerContext();
|
||||
|
||||
const relativeTime = { start: relativeStart, end: relativeEnd };
|
||||
|
||||
|
|
|
@ -16,17 +16,10 @@ import moment from 'moment';
|
|||
import '../../../../../lib/__mocks__/use_composite_image.mock';
|
||||
import { mockRef } from '../../../../../lib/__mocks__/screenshot_ref.mock';
|
||||
|
||||
jest.mock('../../../../../../../observability/public');
|
||||
|
||||
mockReduxHooks();
|
||||
|
||||
jest.mock('../../../../../../../observability/public', () => {
|
||||
const originalModule = jest.requireActual('../../../../../../../observability/public');
|
||||
|
||||
return {
|
||||
...originalModule,
|
||||
useFetcher: jest.fn().mockReturnValue({ data: null, status: 'pending' }),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Ping Timestamp component', () => {
|
||||
let checkGroup: string;
|
||||
let timestamp: string;
|
||||
|
|
|
@ -12,20 +12,24 @@ import * as observabilityPublic from '../../../../observability/public';
|
|||
import '../../lib/__mocks__/use_composite_image.mock';
|
||||
import { mockRef } from '../../lib/__mocks__/screenshot_ref.mock';
|
||||
|
||||
jest.mock('../../../../observability/public', () => {
|
||||
const originalModule = jest.requireActual('../../../../observability/public');
|
||||
|
||||
return {
|
||||
...originalModule,
|
||||
useFetcher: jest.fn().mockReturnValue({ data: null, status: 'success' }),
|
||||
};
|
||||
});
|
||||
jest.mock('../../../../observability/public');
|
||||
|
||||
jest.mock('react-use/lib/useIntersection', () => () => ({
|
||||
isIntersecting: true,
|
||||
}));
|
||||
|
||||
describe('StepScreenshotDisplayProps', () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({
|
||||
data: null,
|
||||
status: observabilityPublic.FETCH_STATUS.SUCCESS,
|
||||
refetch: () => {},
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
(observabilityPublic.useFetcher as any).mockClear();
|
||||
});
|
||||
it('displays screenshot thumbnail when present', () => {
|
||||
const { getByAltText } = render(
|
||||
<StepScreenshotDisplay
|
||||
|
|
|
@ -58,7 +58,19 @@ const mockCorePlugins = {
|
|||
),
|
||||
},
|
||||
},
|
||||
data: {},
|
||||
data: {
|
||||
query: {
|
||||
timefilter: {
|
||||
timefilter: {
|
||||
setTime: jest.fn(),
|
||||
getTime: jest.fn().mockReturnValue({}),
|
||||
getTimeDefaults: jest.fn().mockReturnValue({}),
|
||||
getRefreshIntervalDefaults: jest.fn().mockReturnValue({}),
|
||||
getRefreshInterval: jest.fn().mockReturnValue({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const coreStart = coreMock.createStart({ basePath: '/basepath' });
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin';
|
|||
import { UXActionMenu } from '../components/app/rum_dashboard/action_menu';
|
||||
|
||||
import {
|
||||
DatePickerContextProvider,
|
||||
InspectorContextProvider,
|
||||
useBreadcrumbs,
|
||||
} from '../../../observability/public';
|
||||
|
@ -133,14 +134,16 @@ export function UXAppRoot({
|
|||
>
|
||||
<i18nCore.Context>
|
||||
<RouterProvider history={history} router={uxRouter}>
|
||||
<InspectorContextProvider>
|
||||
<UrlParamsProvider>
|
||||
<EuiErrorBoundary>
|
||||
<UxApp />
|
||||
</EuiErrorBoundary>
|
||||
<UXActionMenu appMountParameters={appMountParameters} />
|
||||
</UrlParamsProvider>
|
||||
</InspectorContextProvider>
|
||||
<DatePickerContextProvider>
|
||||
<InspectorContextProvider>
|
||||
<UrlParamsProvider>
|
||||
<EuiErrorBoundary>
|
||||
<UxApp />
|
||||
</EuiErrorBoundary>
|
||||
<UXActionMenu appMountParameters={appMountParameters} />
|
||||
</UrlParamsProvider>
|
||||
</InspectorContextProvider>
|
||||
</DatePickerContextProvider>
|
||||
</RouterProvider>
|
||||
</i18nCore.Context>
|
||||
</KibanaContextProvider>
|
||||
|
|
|
@ -9,7 +9,7 @@ import { useMemo } from 'react';
|
|||
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
|
||||
export function useUxQuery() {
|
||||
const { urlParams, uxUiFilters } = useLegacyUrlParams();
|
||||
const { rangeId, urlParams, uxUiFilters } = useLegacyUrlParams();
|
||||
|
||||
const { start, end, searchTerm, percentile } = urlParams;
|
||||
|
||||
|
@ -27,7 +27,10 @@ export function useUxQuery() {
|
|||
}
|
||||
|
||||
return null;
|
||||
}, [start, end, searchTerm, percentile, uxUiFilters]);
|
||||
|
||||
// `rangeId` acts as a cache buster for stable date ranges like `Today`
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [start, end, searchTerm, percentile, uxUiFilters, rangeId]);
|
||||
|
||||
return queryParams;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ interface JSErrorItem {
|
|||
}
|
||||
|
||||
export function JSErrors() {
|
||||
const { urlParams, uxUiFilters } = useLegacyUrlParams();
|
||||
const { rangeId, urlParams, uxUiFilters } = useLegacyUrlParams();
|
||||
|
||||
const { start, end, serviceName, searchTerm } = urlParams;
|
||||
|
||||
|
@ -56,7 +56,9 @@ export function JSErrors() {
|
|||
}
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
[start, end, serviceName, uxUiFilters, pagination, searchTerm]
|
||||
// `rangeId` acts as a cache buster for stable ranges like "Today"
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[start, end, serviceName, uxUiFilters, pagination, searchTerm, rangeId]
|
||||
);
|
||||
|
||||
const {
|
||||
|
|
|
@ -32,7 +32,7 @@ export interface PercentileRange {
|
|||
export function PageLoadDistribution() {
|
||||
const { http } = useKibanaServices();
|
||||
|
||||
const { urlParams, uxUiFilters } = useLegacyUrlParams();
|
||||
const { rangeId, urlParams, uxUiFilters } = useLegacyUrlParams();
|
||||
|
||||
const { start, end, rangeFrom, rangeTo, searchTerm } = urlParams;
|
||||
|
||||
|
@ -67,6 +67,8 @@ export function PageLoadDistribution() {
|
|||
}
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
// `rangeId` acts as a cache buster for stable ranges like "Today"
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
end,
|
||||
start,
|
||||
|
@ -75,6 +77,7 @@ export function PageLoadDistribution() {
|
|||
percentileRange.max,
|
||||
searchTerm,
|
||||
serviceName,
|
||||
rangeId,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ import { BreakdownItem } from '../../../../../typings/ui_filters';
|
|||
export function PageViewsTrend() {
|
||||
const { http } = useKibanaServices();
|
||||
|
||||
const { urlParams, uxUiFilters } = useLegacyUrlParams();
|
||||
const { rangeId, urlParams, uxUiFilters } = useLegacyUrlParams();
|
||||
const { serviceName } = uxUiFilters;
|
||||
|
||||
const { start, end, searchTerm, rangeTo, rangeFrom } = urlParams;
|
||||
|
@ -54,7 +54,9 @@ export function PageViewsTrend() {
|
|||
}
|
||||
return Promise.resolve(undefined);
|
||||
},
|
||||
[start, end, serviceName, uxUiFilters, searchTerm, breakdown]
|
||||
// `rangeId` acts as a cache buster for stable ranges like "Today"
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[start, end, serviceName, uxUiFilters, searchTerm, breakdown, rangeId]
|
||||
);
|
||||
|
||||
const exploratoryViewLink = createExploratoryViewUrl(
|
||||
|
|
|
@ -13,6 +13,7 @@ import { RUM_AGENT_NAMES } from '../../../../../common/agent_name';
|
|||
|
||||
export function WebApplicationSelect() {
|
||||
const {
|
||||
rangeId,
|
||||
urlParams: { start, end },
|
||||
} = useLegacyUrlParams();
|
||||
|
||||
|
@ -30,7 +31,9 @@ export function WebApplicationSelect() {
|
|||
});
|
||||
}
|
||||
},
|
||||
[start, end]
|
||||
// `rangeId` works as a cache buster for ranges that never change, like `Today`
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[start, end, rangeId]
|
||||
);
|
||||
|
||||
const rumServiceNames = data?.rumServices ?? [];
|
||||
|
|
|
@ -13,7 +13,7 @@ import { useFetcher } from '../../../../hooks/use_fetcher';
|
|||
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
|
||||
export function VisitorBreakdown() {
|
||||
const { urlParams, uxUiFilters } = useLegacyUrlParams();
|
||||
const { rangeId, urlParams, uxUiFilters } = useLegacyUrlParams();
|
||||
|
||||
const { start, end, searchTerm } = urlParams;
|
||||
|
||||
|
@ -35,7 +35,9 @@ export function VisitorBreakdown() {
|
|||
}
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
[end, start, uxUiFilters, searchTerm]
|
||||
// `rangeId` acts as a cache buster for stable ranges like "Today"
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[end, start, uxUiFilters, searchTerm, rangeId]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -45,7 +45,7 @@ const EmbeddedPanel = styled.div`
|
|||
`;
|
||||
|
||||
export function EmbeddedMapComponent() {
|
||||
const { urlParams } = useLegacyUrlParams();
|
||||
const { rangeId, urlParams } = useLegacyUrlParams();
|
||||
|
||||
const { start, end, serviceName } = urlParams;
|
||||
|
||||
|
@ -124,7 +124,7 @@ export function EmbeddedMapComponent() {
|
|||
embeddable.reload();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [start, end]);
|
||||
}, [start, end, rangeId]);
|
||||
|
||||
useEffect(() => {
|
||||
async function setupEmbeddable() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue