@@ -108,8 +113,8 @@ export const MetricsExplorerCharts = ({
id="xpack.infra.metricsExplorer.footerPaginationMessage"
defaultMessage='Displaying {length} of {total} charts grouped by "{groupBy}".'
values={{
- length: data.series.length,
- total: data.pageInfo.total,
+ length: sumBy(data.pages, 'series.length'),
+ total: firstPage.pageInfo.total,
groupBy: Array.isArray(options.groupBy)
? options.groupBy.join(and)
: options.groupBy,
@@ -117,13 +122,13 @@ export const MetricsExplorerCharts = ({
/>
- {data.pageInfo.afterKey ? (
+ {hasMore ? (
onLoadMore(data.pageInfo.afterKey || null)}
+ onClick={onLoadMore}
>
({
useSyncKibanaTimeFilterTime: () => [() => {}],
}));
+jest.mock('../../../../alerting/use_alert_prefill', () => ({
+ useAlertPrefillContext: () => ({
+ metricThresholdPrefill: {
+ setPrefillOptions: jest.fn(),
+ },
+ }),
+}));
+
const renderUseMetricsExplorerStateHook = () =>
renderHook((props) => useMetricsExplorerState(props.source, props.derivedIndexPattern), {
initialProps: { source, derivedIndexPattern },
@@ -65,7 +73,7 @@ Object.defineProperty(window, 'localStorage', {
describe('useMetricsExplorerState', () => {
beforeEach(() => {
mockedUseMetricsExplorerData.mockReturnValue({
- loading: false,
+ isLoading: false,
error: null,
data: null,
});
@@ -75,25 +83,27 @@ describe('useMetricsExplorerState', () => {
it('should just work', async () => {
mockedUseMetricsExplorerData.mockReturnValue({
- loading: true,
+ isLoading: false,
error: null,
- data: resp,
+ data: {
+ pages: [resp],
+ },
});
const { result } = renderUseMetricsExplorerStateHook();
- expect(result.current.data).toEqual(resp);
+ expect(result.current.data!.pages[0]).toEqual(resp);
expect(result.current.error).toBe(null);
- expect(result.current.loading).toBe(true);
+ expect(result.current.isLoading).toBe(false);
});
describe('handleRefresh', () => {
it('should trigger an addition request when handleRefresh is called', async () => {
const { result } = renderUseMetricsExplorerStateHook();
- expect(result.current.refreshSignal).toBe(0);
+ expect(result.all.length).toBe(2);
+ const numberOfHookCalls = result.all.length;
act(() => {
- result.current.handleRefresh();
+ result.current.refresh();
});
- expect(result.current.afterKey).toBe(null);
- expect(result.current.refreshSignal).toBe(1);
+ expect(result.all.length).toBe(numberOfHookCalls + 1);
});
});
@@ -129,7 +139,7 @@ describe('useMetricsExplorerState', () => {
act(() => {
handleTimeChange('now-10m', 'now');
});
- expect(result.current.currentTimerange).toEqual({
+ expect(result.current.timeRange).toEqual({
from: 'now-10m',
to: 'now',
interval: '>=10s',
@@ -190,30 +200,34 @@ describe('useMetricsExplorerState', () => {
it('should load more based on the afterKey', async () => {
const { result, rerender } = renderUseMetricsExplorerStateHook();
expect(result.current.data).toBe(null);
- expect(result.current.loading).toBe(false);
+ expect(result.current.isLoading).toBe(false);
mockedUseMetricsExplorerData.mockReturnValue({
- loading: false,
+ isLoading: false,
error: null,
- data: resp,
+ data: {
+ pages: [resp],
+ },
});
await rerender();
- const { series, pageInfo } = result.current.data!;
+ const { series } = result.current.data!.pages[0];
expect(series).toBeDefined();
expect(series.length).toBe(3);
+ const fetchNextPage = jest.fn();
mockedUseMetricsExplorerData.mockReturnValue({
- loading: false,
+ isLoading: false,
error: null,
data: {
pageInfo: { total: 10, afterKey: 'host-06' },
series: [createSeries('host-04'), createSeries('host-05'), createSeries('host-06')],
} as any,
+ fetchNextPage,
});
await rerender();
const { handleLoadMore } = result.current;
act(() => {
- handleLoadMore(pageInfo.afterKey!);
+ handleLoadMore();
});
- expect(result.current.afterKey).toBe(pageInfo.afterKey);
+ expect(fetchNextPage).toBeCalledTimes(1);
});
});
});
diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts
index 4842125a6ff0c..a9a4611094174 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts
@@ -5,7 +5,8 @@
* 2.0.
*/
-import { useState, useCallback } from 'react';
+import DateMath from '@kbn/datemath';
+import { useCallback, useEffect } from 'react';
import { DataViewBase } from '@kbn/es-query';
import { MetricsSourceConfigurationProperties } from '../../../../../common/metrics_sources';
import {
@@ -30,46 +31,54 @@ export interface MetricExplorerViewState {
export const useMetricsExplorerState = (
source: MetricsSourceConfigurationProperties,
derivedIndexPattern: DataViewBase,
- shouldLoadImmediately = true
+ enabled = true
) => {
- const [refreshSignal, setRefreshSignal] = useState(0);
- const [afterKey, setAfterKey] = useState>(null);
const {
defaultViewState,
options,
- currentTimerange,
+ timeRange,
chartOptions,
setChartOptions,
setTimeRange,
setOptions,
+ timestamps,
+ setTimestamps,
} = useMetricsExplorerOptionsContainerContext();
- const { loading, error, data, loadData } = useMetricsExplorerData(
+ const refreshTimestamps = useCallback(() => {
+ const fromTimestamp = DateMath.parse(timeRange.from)!.valueOf();
+ const toTimestamp = DateMath.parse(timeRange.to, { roundUp: true })!.valueOf();
+
+ setTimestamps({
+ interval: timeRange.interval,
+ fromTimestamp,
+ toTimestamp,
+ });
+ }, [setTimestamps, timeRange]);
+
+ const { data, error, fetchNextPage, isLoading } = useMetricsExplorerData(
options,
source,
derivedIndexPattern,
- currentTimerange,
- afterKey,
- refreshSignal,
- shouldLoadImmediately
+ timestamps,
+ enabled
);
- const handleRefresh = useCallback(() => {
- setAfterKey(null);
- setRefreshSignal(refreshSignal + 1);
- }, [refreshSignal]);
+ useEffect(() => {
+ refreshTimestamps();
+ // options, setOptions are added to dependencies since we need to refresh the timestamps
+ // every time options change
+ }, [options, setOptions, refreshTimestamps]);
const handleTimeChange = useCallback(
(start: string, end: string) => {
- setAfterKey(null);
- setTimeRange({ ...currentTimerange, from: start, to: end });
+ setTimeRange({ interval: timeRange.interval, from: start, to: end });
},
- [currentTimerange, setTimeRange]
+ [setTimeRange, timeRange.interval]
);
const handleGroupByChange = useCallback(
(groupBy: string | null | string[]) => {
- setAfterKey(null);
setOptions({
...options,
groupBy: groupBy || void 0,
@@ -80,7 +89,6 @@ export const useMetricsExplorerState = (
const handleFilterQuerySubmit = useCallback(
(query: string) => {
- setAfterKey(null);
setOptions({
...options,
filterQuery: query,
@@ -91,7 +99,6 @@ export const useMetricsExplorerState = (
const handleMetricsChange = useCallback(
(metrics: MetricsExplorerMetric[]) => {
- setAfterKey(null);
setOptions({
...options,
metrics,
@@ -102,7 +109,6 @@ export const useMetricsExplorerState = (
const handleAggregationChange = useCallback(
(aggregation: MetricsExplorerAggregation) => {
- setAfterKey(null);
const metrics =
aggregation === 'count'
? [{ aggregation }]
@@ -137,24 +143,21 @@ export const useMetricsExplorerState = (
);
return {
- loading,
- error,
- data,
- currentTimerange,
- options,
chartOptions,
- setChartOptions,
+ timeRange,
+ data,
+ defaultViewState,
+ error,
+ isLoading,
handleAggregationChange,
handleMetricsChange,
handleFilterQuerySubmit,
handleGroupByChange,
handleTimeChange,
- handleRefresh,
- handleLoadMore: setAfterKey,
- defaultViewState,
+ handleLoadMore: fetchNextPage,
onViewStateChange,
- loadData,
- refreshSignal,
- afterKey,
+ options,
+ setChartOptions,
+ refresh: refreshTimestamps,
};
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx
index 1813b2a81c101..1bb6577f77be5 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx
@@ -6,6 +6,7 @@
*/
import React from 'react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useMetricsExplorerData } from './use_metrics_explorer_data';
import { renderHook } from '@testing-library/react-hooks';
@@ -15,53 +16,60 @@ import {
options,
source,
derivedIndexPattern,
- timeRange,
+ timestamps,
resp,
createSeries,
} from '../../../../utils/fixtures/metrics_explorer';
-import { MetricsExplorerOptions, MetricsExplorerTimeOptions } from './use_metrics_explorer_options';
+import {
+ MetricsExplorerOptions,
+ MetricsExplorerTimestampsRT,
+} from './use_metrics_explorer_options';
import { DataViewBase } from '@kbn/es-query';
-import { HttpHandler } from '@kbn/core/public';
import { MetricsSourceConfigurationProperties } from '../../../../../common/metrics_sources';
const mockedFetch = jest.fn();
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ cacheTime: 0,
+ },
+ },
+});
+
const renderUseMetricsExplorerDataHook = () => {
const wrapper: React.FC = ({ children }) => {
const services = {
http: {
- fetch: mockedFetch,
+ post: mockedFetch,
},
};
- return {children};
+ return (
+
+ {children}
+
+ );
};
return renderHook(
(props: {
options: MetricsExplorerOptions;
source: MetricsSourceConfigurationProperties | undefined;
derivedIndexPattern: DataViewBase;
- timeRange: MetricsExplorerTimeOptions;
- afterKey: string | null | Record;
- signal: any;
- fetch?: HttpHandler;
- shouldLoadImmediately?: boolean;
+ timestamps: MetricsExplorerTimestampsRT;
}) =>
useMetricsExplorerData(
props.options,
props.source,
props.derivedIndexPattern,
- props.timeRange,
- props.afterKey,
- props.signal
+ props.timestamps
),
{
initialProps: {
options,
source,
derivedIndexPattern,
- timeRange,
- afterKey: null as string | null | Record,
- signal: 1,
+ timestamps,
},
wrapper,
}
@@ -75,85 +83,77 @@ jest.mock('../../../../utils/kuery', () => {
});
describe('useMetricsExplorerData Hook', () => {
+ afterEach(() => {
+ queryClient.clear();
+ });
+
it('should just work', async () => {
- mockedFetch.mockResolvedValue(resp as any);
+ mockedFetch.mockResolvedValue(resp);
const { result, waitForNextUpdate } = renderUseMetricsExplorerDataHook();
- expect(result.current.data).toBe(null);
- expect(result.current.loading).toBe(true);
+
+ expect(result.current.data).toBeUndefined();
+ expect(result.current.isLoading).toBe(true);
+
await waitForNextUpdate();
- expect(result.current.data).toEqual(resp);
- expect(result.current.loading).toBe(false);
- const { series } = result.current.data!;
+
+ expect(result.current.data!.pages[0]).toEqual(resp);
+ expect(result.current.isLoading).toBe(false);
+ const { series } = result.current.data!.pages[0];
expect(series).toBeDefined();
expect(series.length).toBe(3);
});
it('should paginate', async () => {
- mockedFetch.mockResolvedValue(resp as any);
- const { result, waitForNextUpdate, rerender } = renderUseMetricsExplorerDataHook();
- expect(result.current.data).toBe(null);
- expect(result.current.loading).toBe(true);
+ mockedFetch.mockResolvedValue(resp);
+ const { result, waitForNextUpdate } = renderUseMetricsExplorerDataHook();
+ expect(result.current.data).toBeUndefined();
+ expect(result.current.isLoading).toBe(true);
await waitForNextUpdate();
- expect(result.current.data).toEqual(resp);
- expect(result.current.loading).toBe(false);
- const { series, pageInfo } = result.current.data!;
+ expect(result.current.data!.pages[0]).toEqual(resp);
+ expect(result.current.isLoading).toBe(false);
+ const { series } = result.current.data!.pages[0];
expect(series).toBeDefined();
expect(series.length).toBe(3);
mockedFetch.mockResolvedValue({
pageInfo: { total: 10, afterKey: 'host-06' },
series: [createSeries('host-04'), createSeries('host-05'), createSeries('host-06')],
} as any);
- rerender({
- options,
- source,
- derivedIndexPattern,
- timeRange,
- afterKey: pageInfo.afterKey!,
- signal: 1,
- });
- expect(result.current.loading).toBe(true);
+ result.current.fetchNextPage();
await waitForNextUpdate();
- expect(result.current.loading).toBe(false);
- const { series: nextSeries } = result.current.data!;
+ expect(result.current.isLoading).toBe(false);
+ const { series: nextSeries } = result.current.data!.pages[1];
expect(nextSeries).toBeDefined();
- expect(nextSeries.length).toBe(6);
+ expect(nextSeries.length).toBe(3);
});
it('should reset error upon recovery', async () => {
const error = new Error('Network Error');
mockedFetch.mockRejectedValue(error);
- const { result, waitForNextUpdate, rerender } = renderUseMetricsExplorerDataHook();
- expect(result.current.data).toBe(null);
+ const { result, waitForNextUpdate } = renderUseMetricsExplorerDataHook();
+ expect(result.current.data).toBeUndefined();
expect(result.current.error).toEqual(null);
- expect(result.current.loading).toBe(true);
+ expect(result.current.isLoading).toBe(true);
await waitForNextUpdate();
- expect(result.current.data).toEqual(null);
+ expect(result.current.data).toBeUndefined();
expect(result.current.error).toEqual(error);
- expect(result.current.loading).toBe(false);
+ expect(result.current.isLoading).toBe(false);
mockedFetch.mockResolvedValue(resp as any);
- rerender({
- options,
- source,
- derivedIndexPattern,
- timeRange,
- afterKey: null,
- signal: 2,
- });
+ result.current.refetch();
await waitForNextUpdate();
- expect(result.current.data).toEqual(resp);
- expect(result.current.loading).toBe(false);
+ expect(result.current.data!.pages[0]).toEqual(resp);
+ expect(result.current.isLoading).toBe(false);
expect(result.current.error).toBe(null);
});
it('should not paginate on option change', async () => {
mockedFetch.mockResolvedValue(resp as any);
const { result, waitForNextUpdate, rerender } = renderUseMetricsExplorerDataHook();
- expect(result.current.data).toBe(null);
- expect(result.current.loading).toBe(true);
+ expect(result.current.data).toBeUndefined();
+ expect(result.current.isLoading).toBe(true);
await waitForNextUpdate();
- expect(result.current.data).toEqual(resp);
- expect(result.current.loading).toBe(false);
- const { series, pageInfo } = result.current.data!;
+ expect(result.current.data!.pages[0]).toEqual(resp);
+ expect(result.current.isLoading).toBe(false);
+ const { series } = result.current.data!.pages[0];
expect(series).toBeDefined();
expect(series.length).toBe(3);
mockedFetch.mockResolvedValue(resp as any);
@@ -165,25 +165,23 @@ describe('useMetricsExplorerData Hook', () => {
},
source,
derivedIndexPattern,
- timeRange,
- afterKey: pageInfo.afterKey!,
- signal: 1,
+ timestamps,
});
- expect(result.current.loading).toBe(true);
+ expect(result.current.isLoading).toBe(true);
await waitForNextUpdate();
- expect(result.current.data).toEqual(resp);
- expect(result.current.loading).toBe(false);
+ expect(result.current.data!.pages[0]).toEqual(resp);
+ expect(result.current.isLoading).toBe(false);
});
it('should not paginate on time change', async () => {
mockedFetch.mockResolvedValue(resp as any);
const { result, waitForNextUpdate, rerender } = renderUseMetricsExplorerDataHook();
- expect(result.current.data).toBe(null);
- expect(result.current.loading).toBe(true);
+ expect(result.current.data).toBeUndefined();
+ expect(result.current.isLoading).toBe(true);
await waitForNextUpdate();
- expect(result.current.data).toEqual(resp);
- expect(result.current.loading).toBe(false);
- const { series, pageInfo } = result.current.data!;
+ expect(result.current.data!.pages[0]).toEqual(resp);
+ expect(result.current.isLoading).toBe(false);
+ const { series } = result.current.data!.pages[0];
expect(series).toBeDefined();
expect(series.length).toBe(3);
mockedFetch.mockResolvedValue(resp as any);
@@ -191,13 +189,11 @@ describe('useMetricsExplorerData Hook', () => {
options,
source,
derivedIndexPattern,
- timeRange: { from: 'now-1m', to: 'now', interval: '>=1m' },
- afterKey: pageInfo.afterKey!,
- signal: 1,
+ timestamps: { fromTimestamp: 1678378092225, toTimestamp: 1678381693477, interval: '>=10s' },
});
- expect(result.current.loading).toBe(true);
+ expect(result.current.isLoading).toBe(true);
await waitForNextUpdate();
- expect(result.current.data).toEqual(resp);
- expect(result.current.loading).toBe(false);
+ expect(result.current.data!.pages[0]).toEqual(resp);
+ expect(result.current.isLoading).toBe(false);
});
});
diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts
index 6f385ba3d5193..a110ae1939840 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts
@@ -5,10 +5,8 @@
* 2.0.
*/
-import DateMath from '@kbn/datemath';
-import { useEffect, useState } from 'react';
import { DataViewBase } from '@kbn/es-query';
-import { isEqual } from 'lodash';
+import { useInfiniteQuery } from '@tanstack/react-query';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { MetricsSourceConfigurationProperties } from '../../../../../common/metrics_sources';
@@ -17,109 +15,73 @@ import {
metricsExplorerResponseRT,
} from '../../../../../common/http_api/metrics_explorer';
import { convertKueryToElasticSearchQuery } from '../../../../utils/kuery';
-import { MetricsExplorerOptions, MetricsExplorerTimeOptions } from './use_metrics_explorer_options';
+import {
+ MetricsExplorerOptions,
+ MetricsExplorerTimestampsRT,
+} from './use_metrics_explorer_options';
import { decodeOrThrow } from '../../../../../common/runtime_types';
-import { useTrackedPromise } from '../../../../utils/use_tracked_promise';
-
-function isSameOptions(current: MetricsExplorerOptions, next: MetricsExplorerOptions) {
- return isEqual(current, next);
-}
export function useMetricsExplorerData(
options: MetricsExplorerOptions,
source: MetricsSourceConfigurationProperties | undefined,
derivedIndexPattern: DataViewBase,
- timerange: MetricsExplorerTimeOptions,
- afterKey: string | null | Record,
- signal: any,
- shouldLoadImmediately = true
+ { fromTimestamp, toTimestamp, interval }: MetricsExplorerTimestampsRT,
+ enabled = true
) {
- const kibana = useKibana();
- const fetchFn = kibana.services.http?.fetch;
- const [error, setError] = useState(null);
- const [loading, setLoading] = useState(true);
- const [data, setData] = useState(null);
- const [lastOptions, setLastOptions] = useState(null);
- const [lastTimerange, setLastTimerange] = useState(null);
+ const { http } = useKibana().services;
+
+ const { isLoading, data, error, refetch, fetchNextPage } = useInfiniteQuery<
+ MetricsExplorerResponse,
+ Error
+ >({
+ queryKey: ['metricExplorer', options, fromTimestamp, toTimestamp],
+ queryFn: async ({ signal, pageParam = { afterKey: null } }) => {
+ if (!fromTimestamp || !toTimestamp) {
+ throw new Error('Unable to parse timerange');
+ }
+ if (!http) {
+ throw new Error('HTTP service is unavailable');
+ }
+ if (!source) {
+ throw new Error('Source is unavailable');
+ }
- const from = DateMath.parse(timerange.from);
- const to = DateMath.parse(timerange.to, { roundUp: true });
- const [, makeRequest] = useTrackedPromise(
- {
- cancelPreviousOn: 'creation',
- createPromise: () => {
- setLoading(true);
- if (!from || !to) {
- return Promise.reject(new Error('Unable to parse timerange'));
- }
- if (!fetchFn) {
- return Promise.reject(new Error('HTTP service is unavailable'));
- }
- if (!source) {
- return Promise.reject(new Error('Source is unavailable'));
- }
+ const { afterKey } = pageParam;
+ const response = await http.post('/api/infra/metrics_explorer', {
+ method: 'POST',
+ body: JSON.stringify({
+ forceInterval: options.forceInterval,
+ dropLastBucket: options.dropLastBucket != null ? options.dropLastBucket : true,
+ metrics: options.aggregation === 'count' ? [{ aggregation: 'count' }] : options.metrics,
+ groupBy: options.groupBy,
+ afterKey,
+ limit: options.limit,
+ indexPattern: source.metricAlias,
+ filterQuery:
+ (options.filterQuery &&
+ convertKueryToElasticSearchQuery(options.filterQuery, derivedIndexPattern)) ||
+ void 0,
+ timerange: {
+ interval,
+ from: fromTimestamp,
+ to: toTimestamp,
+ },
+ }),
+ signal,
+ });
- return fetchFn('/api/infra/metrics_explorer', {
- method: 'POST',
- body: JSON.stringify({
- forceInterval: options.forceInterval,
- dropLastBucket: options.dropLastBucket != null ? options.dropLastBucket : true,
- metrics: options.aggregation === 'count' ? [{ aggregation: 'count' }] : options.metrics,
- groupBy: options.groupBy,
- afterKey,
- limit: options.limit,
- indexPattern: source.metricAlias,
- filterQuery:
- (options.filterQuery &&
- convertKueryToElasticSearchQuery(options.filterQuery, derivedIndexPattern)) ||
- void 0,
- timerange: {
- ...timerange,
- from: from.valueOf(),
- to: to.valueOf(),
- },
- }),
- });
- },
- onResolve: (resp: unknown) => {
- setLoading(false);
- const response = decodeOrThrow(metricsExplorerResponseRT)(resp);
- if (response) {
- if (
- data &&
- lastOptions &&
- data.pageInfo.afterKey !== response.pageInfo.afterKey &&
- isSameOptions(lastOptions, options) &&
- isEqual(timerange, lastTimerange) &&
- afterKey
- ) {
- const { series } = data;
- setData({
- ...response,
- series: [...series, ...response.series],
- });
- } else {
- setData(response);
- }
- setLastOptions(options);
- setLastTimerange(timerange);
- setError(null);
- }
- },
- onReject: (e: unknown) => {
- setError(e as Error);
- setData(null);
- setLoading(false);
- },
+ return decodeOrThrow(metricsExplorerResponseRT)(response);
},
- [source, timerange, options, signal, afterKey]
- );
+ getNextPageParam: (lastPage) => lastPage.pageInfo,
+ enabled: enabled && !!fromTimestamp && !!toTimestamp && !!http && !!source,
+ refetchOnWindowFocus: false,
+ });
- useEffect(() => {
- if (!shouldLoadImmediately) {
- return;
- }
- makeRequest();
- }, [makeRequest, shouldLoadImmediately]);
- return { error, loading, data, loadData: makeRequest };
+ return {
+ data,
+ error,
+ fetchNextPage,
+ isLoading,
+ refetch,
+ };
}
diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.test.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.test.tsx
index 37200f75d109c..795728fa8d8ef 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.test.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.test.tsx
@@ -65,7 +65,7 @@ describe('useMetricExplorerOptions', () => {
it('should just work', () => {
const { result } = renderUseMetricsExplorerOptionsHook();
expect(result.current.options).toEqual(DEFAULT_OPTIONS);
- expect(result.current.currentTimerange).toEqual(DEFAULT_TIMERANGE);
+ expect(result.current.timeRange).toEqual(DEFAULT_TIMERANGE);
expect(result.current.isAutoReloading).toEqual(false);
expect(STORE.MetricsExplorerOptions).toEqual(JSON.stringify(DEFAULT_OPTIONS));
});
@@ -94,7 +94,7 @@ describe('useMetricExplorerOptions', () => {
result.current.setTimeRange(newTimeRange);
});
rerender();
- expect(result.current.currentTimerange).toEqual(newTimeRange);
+ expect(result.current.timeRange).toEqual(newTimeRange);
});
it('should load from store when available', () => {
diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts
index 02cab766c57e9..fa0d4189e2949 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts
@@ -5,9 +5,11 @@
* 2.0.
*/
+import DateMath from '@kbn/datemath';
import * as t from 'io-ts';
import { values } from 'lodash';
import createContainer from 'constate';
+import type { TimeRange } from '@kbn/es-query';
import { useState, useEffect, useMemo, Dispatch, SetStateAction } from 'react';
import { useAlertPrefillContext } from '../../../../alerting/use_alert_prefill';
import { Color } from '../../../../../common/color_palette';
@@ -77,6 +79,13 @@ export const metricExplorerOptionsRT = t.intersection([
export type MetricsExplorerOptions = t.TypeOf;
+export const metricsExplorerTimestampsRT = t.type({
+ fromTimestamp: t.number,
+ toTimestamp: t.number,
+ interval: t.string,
+});
+export type MetricsExplorerTimestampsRT = t.TypeOf;
+
export const metricsExplorerTimeOptionsRT = t.type({
from: t.string,
to: t.string,
@@ -149,25 +158,37 @@ function useStateWithLocalStorage(
return [state, setState];
}
+const getDefaultTimeRange = ({ from, to }: TimeRange) => {
+ const fromTimestamp = DateMath.parse(from)!.valueOf();
+ const toTimestamp = DateMath.parse(to, { roundUp: true })!.valueOf();
+ return {
+ fromTimestamp,
+ toTimestamp,
+ interval: DEFAULT_TIMERANGE.interval,
+ };
+};
+
export const useMetricsExplorerOptions = () => {
const TIME_DEFAULTS = { from: 'now-1h', to: 'now' };
const [getTime] = useKibanaTimefilterTime(TIME_DEFAULTS);
const { from, to } = getTime();
- const defaultTimeRange = {
- from,
- to,
- interval: DEFAULT_TIMERANGE.interval,
- };
const [options, setOptions] = useStateWithLocalStorage(
'MetricsExplorerOptions',
DEFAULT_OPTIONS
);
- const [currentTimerange, setTimeRange] = useState(defaultTimeRange);
+ const [timeRange, setTimeRange] = useState({
+ from,
+ to,
+ interval: DEFAULT_TIMERANGE.interval,
+ });
+ const [timestamps, setTimestamps] = useState(
+ getDefaultTimeRange({ from, to })
+ );
useSyncKibanaTimeFilterTime(TIME_DEFAULTS, {
- from: currentTimerange.from,
- to: currentTimerange.to,
+ from: timeRange.from,
+ to: timeRange.to,
});
const [chartOptions, setChartOptions] = useStateWithLocalStorage(
@@ -194,17 +215,19 @@ export const useMetricsExplorerOptions = () => {
defaultViewState: {
options: DEFAULT_OPTIONS,
chartOptions: DEFAULT_CHART_OPTIONS,
- currentTimerange: defaultTimeRange,
+ currentTimerange: timeRange,
},
options,
chartOptions,
setChartOptions,
- currentTimerange,
+ timeRange,
isAutoReloading,
setOptions,
setTimeRange,
startAutoReload: () => setAutoReloading(true),
stopAutoReload: () => setAutoReloading(false),
+ timestamps,
+ setTimestamps,
};
};
diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx
index f928017256695..d62d6ea1e82b1 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx
@@ -7,7 +7,7 @@
import { EuiErrorBoundary } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
import { useTrackPageview } from '@kbn/observability-plugin/public';
import { MetricsSourceConfigurationProperties } from '../../../../common/metrics_sources';
import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs';
@@ -27,11 +27,12 @@ interface MetricsExplorerPageProps {
}
export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExplorerPageProps) => {
+ const [enabled, setEnabled] = useState(false);
const {
- loading,
+ isLoading,
error,
data,
- currentTimerange,
+ timeRange,
options,
chartOptions,
setChartOptions,
@@ -40,11 +41,10 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl
handleFilterQuerySubmit,
handleGroupByChange,
handleTimeChange,
- handleRefresh,
handleLoadMore,
onViewStateChange,
- loadData,
- } = useMetricsExplorerState(source, derivedIndexPattern, false);
+ refresh,
+ } = useMetricsExplorerState(source, derivedIndexPattern, enabled);
const { currentView, shouldLoadDefault } = useSavedViewContext();
useTrackPageview({ app: 'infra_metrics', path: 'metrics_explorer' });
@@ -59,11 +59,10 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl
useEffect(() => {
if (currentView != null || !shouldLoadDefault) {
- // load metrics explorer data after default view loaded, unless we're not loading a view
- loadData();
+ // load metrics explorer data after default view loaded, unless we're not isLoading a view
+ setEnabled(true);
}
- /* eslint-disable-next-line react-hooks/exhaustive-deps */
- }, [loadData, shouldLoadDefault]);
+ }, [currentView, shouldLoadDefault]);
useMetricsBreadcrumbs([
{
@@ -82,7 +81,7 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl
viewState={{
options,
chartOptions,
- currentTimerange,
+ currentTimerange: timeRange,
}}
/>,
],
@@ -90,10 +89,10 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl
>
) : (
)}
diff --git a/x-pack/plugins/infra/public/utils/fixtures/metrics_explorer.ts b/x-pack/plugins/infra/public/utils/fixtures/metrics_explorer.ts
index 4e2254635ab05..58ccc468a06f4 100644
--- a/x-pack/plugins/infra/public/utils/fixtures/metrics_explorer.ts
+++ b/x-pack/plugins/infra/public/utils/fixtures/metrics_explorer.ts
@@ -15,6 +15,7 @@ import {
MetricsExplorerChartType,
MetricsExplorerYAxisMode,
MetricsExplorerChartOptions,
+ MetricsExplorerTimestampsRT,
} from '../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options';
export const options: MetricsExplorerOptions = {
@@ -55,6 +56,12 @@ export const timeRange: MetricsExplorerTimeOptions = {
interval: '>=10s',
};
+export const timestamps: MetricsExplorerTimestampsRT = {
+ fromTimestamp: 1678376367166,
+ toTimestamp: 1678379973620,
+ interval: '>=10s',
+};
+
export const createSeries = (id: string): MetricsExplorerSeries => ({
id,
columns: [
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index f94bf75ebd05a..c13377b026aab 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -17435,7 +17435,6 @@
"xpack.infra.alerting.metricsDropdownMenu": "Indicateurs",
"xpack.infra.alerting.metricsDropdownTitle": "Règles d'indicateurs",
"xpack.infra.alerts.charts.errorMessage": "Oups, un problème est survenu",
- "xpack.infra.alerts.charts.loadingMessage": "Chargement",
"xpack.infra.alerts.charts.noDataMessage": "Aucune donnée graphique disponible",
"xpack.infra.alerts.timeLabels.days": "jours",
"xpack.infra.alerts.timeLabels.hours": "heures",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 96e423426fc17..b7a7a626158fe 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -17434,7 +17434,6 @@
"xpack.infra.alerting.metricsDropdownMenu": "メトリック",
"xpack.infra.alerting.metricsDropdownTitle": "メトリックルール",
"xpack.infra.alerts.charts.errorMessage": "問題が発生しました",
- "xpack.infra.alerts.charts.loadingMessage": "読み込み中",
"xpack.infra.alerts.charts.noDataMessage": "グラフデータがありません",
"xpack.infra.alerts.timeLabels.days": "日",
"xpack.infra.alerts.timeLabels.hours": "時間",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index d6405ae70463f..bc56d71d3e1f7 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -17435,7 +17435,6 @@
"xpack.infra.alerting.metricsDropdownMenu": "指标",
"xpack.infra.alerting.metricsDropdownTitle": "指标规则",
"xpack.infra.alerts.charts.errorMessage": "哇哦,出问题了",
- "xpack.infra.alerts.charts.loadingMessage": "正在加载",
"xpack.infra.alerts.charts.noDataMessage": "没有可用图表数据",
"xpack.infra.alerts.timeLabels.days": "天",
"xpack.infra.alerts.timeLabels.hours": "小时",