diff --git a/x-pack/plugins/infra/public/alerting/common/criterion_preview_chart/criterion_preview_chart.tsx b/x-pack/plugins/infra/public/alerting/common/criterion_preview_chart/criterion_preview_chart.tsx index 67b6545691f78..bd79036974f51 100644 --- a/x-pack/plugins/infra/public/alerting/common/criterion_preview_chart/criterion_preview_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/common/criterion_preview_chart/criterion_preview_chart.tsx @@ -10,7 +10,7 @@ import { niceTimeFormatter, TooltipValue } from '@elastic/charts'; import { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; -import { EuiText } from '@elastic/eui'; +import { EuiLoadingChart, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { sum, min as getMin, max as getMax } from 'lodash'; import { formatNumber } from '../../../../common/formatters/number'; @@ -119,7 +119,7 @@ export const LoadingState = () => { return ( - + ); diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx index ef30035f220d4..2028f3b72e67f 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx @@ -9,6 +9,7 @@ import { coreMock as mockCoreMock } from '@kbn/core/public/mocks'; import React from 'react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { buildMetricThresholdRule } from '../mocks/metric_threshold_rule'; import AlertDetailsAppSection from './alert_details_app_section'; @@ -26,10 +27,13 @@ jest.mock('../../../containers/metrics_source/use_source_via_http', () => ({ })); describe('AlertDetailsAppSection', () => { + const queryClient = new QueryClient(); const renderComponent = () => { return render( - + + + ); }; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx index d9db49192d5c4..be22d90cc2729 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx @@ -34,7 +34,7 @@ const mockResponse = { }; jest.mock('../hooks/use_metrics_explorer_chart_data', () => ({ - useMetricsExplorerChartData: () => ({ loading: false, data: mockResponse }), + useMetricsExplorerChartData: () => ({ loading: false, data: { pages: [mockResponse] } }), })); describe('ExpressionChart', () => { diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx index 0ce6a5ac9d13d..de9ea025ae750 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo, useCallback } from 'react'; +import React from 'react'; import { Axis, Chart, niceTimeFormatter, Position, Settings } from '@elastic/charts'; import { EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -54,7 +54,9 @@ export const ExpressionChart: React.FC = ({ groupBy, chartType = MetricsExplorerChartType.bar, }) => { - const { loading, data } = useMetricsExplorerChartData( + const { uiSettings } = useKibanaContextForPlugin().services; + + const { isLoading, data } = useMetricsExplorerChartData( expression, derivedIndexPattern, source, @@ -62,8 +64,24 @@ export const ExpressionChart: React.FC = ({ groupBy ); - const { uiSettings } = useKibanaContextForPlugin().services; + if (isLoading) { + return ; + } + if (!data) { + return ; + } + + const isDarkMode = uiSettings?.get('theme:darkMode') || false; + const firstSeries = first(first(data.pages)!.series); + // Creating a custom series where the ID is changed to 0 + // so that we can get a proper domain + if (!firstSeries || !firstSeries.rows || firstSeries.rows.length === 0) { + return ; + } + + const firstTimestamp = first(firstSeries.rows)!.timestamp; + const lastTimestamp = last(firstSeries.rows)!.timestamp; const metric: MetricsExplorerOptionsMetric = { field: expression.metric, aggregation: expression.aggType as MetricsExplorerAggregation, @@ -73,41 +91,16 @@ export const ExpressionChart: React.FC = ({ if (metric.aggregation === 'custom') { metric.label = expression.label || CUSTOM_EQUATION; } - const isDarkMode = uiSettings?.get('theme:darkMode') || false; - const dateFormatter = useMemo(() => { - const firstSeries = first(data?.series); - const firstTimestamp = first(firstSeries?.rows)?.timestamp; - const lastTimestamp = last(firstSeries?.rows)?.timestamp; - - if (firstTimestamp == null || lastTimestamp == null) { - return (value: number) => `${value}`; - } - return niceTimeFormatter([firstTimestamp, lastTimestamp]); - }, [data?.series]); - - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - const yAxisFormater = useCallback(createFormatterForMetric(metric), [expression]); - - if (loading) { - return ; - } - - if (!data) { - return ; - } + const dateFormatter = + firstTimestamp == null || lastTimestamp == null + ? (value: number) => `${value}` + : niceTimeFormatter([firstTimestamp, lastTimestamp]); const criticalThresholds = expression.threshold.slice().sort(); const warningThresholds = expression.warningThreshold?.slice().sort() ?? []; const thresholds = [...criticalThresholds, ...warningThresholds].sort(); - // Creating a custom series where the ID is changed to 0 - // so that we can get a proper domian - const firstSeries = first(data.series); - if (!firstSeries || !firstSeries.rows || firstSeries.rows.length === 0) { - return ; - } - const series = { ...firstSeries, rows: firstSeries.rows.map((row) => { @@ -119,8 +112,6 @@ export const ExpressionChart: React.FC = ({ }), }; - const firstTimestamp = first(firstSeries.rows)!.timestamp; - const lastTimestamp = last(firstSeries.rows)!.timestamp; const dataDomain = calculateDomain(series, [metric], false); const domain = { max: Math.max(dataDomain.max, last(thresholds) || dataDomain.max) * 1.1, // add 10% headroom. @@ -173,7 +164,12 @@ export const ExpressionChart: React.FC = ({ showOverlappingTicks={true} tickFormat={dateFormatter} /> - + diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts index 6fcbd6df75918..e3904cbb473c3 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts @@ -5,12 +5,16 @@ * 2.0. */ +import DateMath from '@kbn/datemath'; import { DataViewBase } from '@kbn/es-query'; import { useMemo } from 'react'; import { MetricExpressionCustomMetric } from '../../../../common/alerting/metrics'; import { MetricsSourceConfiguration } from '../../../../common/metrics_sources'; import { MetricExpression } from '../types'; -import { MetricsExplorerOptions } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; +import { + MetricsExplorerOptions, + MetricsExplorerTimestampsRT, +} from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; import { useMetricsExplorerData } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data'; import { MetricExplorerCustomMetricAggregations } from '../../../../common/http_api/metrics_explorer'; @@ -52,23 +56,19 @@ export const useMetricsExplorerChartData = ( groupBy, ] ); - const timerange = useMemo( - () => ({ + const timestamps: MetricsExplorerTimestampsRT = useMemo(() => { + const from = `now-${(timeSize || 1) * 20}${timeUnit}`; + const to = 'now'; + const fromTimestamp = DateMath.parse(from)!.valueOf(); + const toTimestamp = DateMath.parse(to, { roundUp: true })!.valueOf(); + return { interval: `>=${timeSize || 1}${timeUnit}`, - from: `now-${(timeSize || 1) * 20}${timeUnit}`, - to: 'now', - }), - [timeSize, timeUnit] - ); + fromTimestamp, + toTimestamp, + }; + }, [timeSize, timeUnit]); - return useMetricsExplorerData( - options, - source?.configuration, - derivedIndexPattern, - timerange, - null, - null - ); + return useMetricsExplorerData(options, source?.configuration, derivedIndexPattern, timestamps); }; const mapMetricThresholdMetricToMetricsExplorerMetric = (metric: MetricExpressionCustomMetric) => { diff --git a/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx b/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx index b192bf54e4829..757cf3d100fce 100644 --- a/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx +++ b/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx @@ -30,7 +30,7 @@ export const WithMetricsExplorerOptionsUrlState = () => { options, chartOptions, setChartOptions, - currentTimerange, + timeRange, setOptions: setRawOptions, setTimeRange, } = useMetricsExplorerOptionsContainerContext(); @@ -43,9 +43,9 @@ export const WithMetricsExplorerOptionsUrlState = () => { () => ({ options, chartOptions, - timerange: currentTimerange, + timerange: timeRange, }), - [options, chartOptions, currentTimerange] + [options, chartOptions, timeRange] ); const handleChange = (newUrlState: MetricsExplorerUrlState | undefined) => { diff --git a/x-pack/plugins/infra/public/hooks/use_kibana_time_zone_setting.ts b/x-pack/plugins/infra/public/hooks/use_kibana_time_zone_setting.ts index 411b6013154cd..6ea4ecee25ca2 100644 --- a/x-pack/plugins/infra/public/hooks/use_kibana_time_zone_setting.ts +++ b/x-pack/plugins/infra/public/hooks/use_kibana_time_zone_setting.ts @@ -5,6 +5,7 @@ * 2.0. */ +import moment from 'moment-timezone'; import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/public'; @@ -12,7 +13,7 @@ export function useKibanaTimeZoneSetting() { const [kibanaTimeZone] = useUiSetting$(UI_SETTINGS.DATEFORMAT_TZ); if (!kibanaTimeZone || kibanaTimeZone === 'Browser') { - return 'local'; + return moment.tz.guess(); } return kibanaTimeZone; diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index f42ff445c3a25..3a2ba96cb6ad9 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -10,6 +10,8 @@ import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import { RouteComponentProps, Switch } from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { EuiErrorBoundary, EuiHeaderLinks, EuiHeaderLink } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; @@ -52,6 +54,7 @@ const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLab export const InfrastructurePage = ({ match }: RouteComponentProps) => { const uiCapabilities = useKibana().services.application?.capabilities; const { setHeaderActionMenu, theme$ } = useContext(HeaderActionMenuContext); + const queryClient = new QueryClient(); const settingsTabTitle = i18n.translate('xpack.infra.metrics.settingsTabTitle', { defaultMessage: 'Settings', @@ -73,60 +76,65 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { - - - - {setHeaderActionMenu && theme$ && ( - - - - {settingsTabTitle} - - - - - {ADD_DATA_LABEL} - - - - )} - - - ( - - {({ configuration, createDerivedIndexPattern }) => ( - - - {configuration ? ( - - ) : ( - - )} - - )} - - )} + + + + - - - - } /> - - + + {setHeaderActionMenu && theme$ && ( + + + + {settingsTabTitle} + + + + + {ADD_DATA_LABEL} + + + + )} + + + ( + + {({ configuration, createDerivedIndexPattern }) => ( + + + {configuration ? ( + + ) : ( + + )} + + )} + + )} + /> + + + + } /> + + + diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/charts.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/charts.tsx index b85fd65c538c1..6a6728af98629 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/charts.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/charts.tsx @@ -9,6 +9,7 @@ import { EuiButton, EuiFlexGrid, EuiFlexItem, EuiText, EuiHorizontalRule } from import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; +import { first, last, sumBy } from 'lodash'; import { MetricsSourceConfigurationProperties } from '../../../../../common/metrics_sources'; import { MetricsExplorerResponse } from '../../../../../common/http_api/metrics_explorer'; import { @@ -20,22 +21,22 @@ import { InfraLoadingPanel } from '../../../../components/loading'; import { NoData } from '../../../../components/empty_states/no_data'; import { MetricsExplorerChart } from './chart'; -type StringOrNull = string | null; - interface Props { - loading: boolean; + isLoading: boolean; options: MetricsExplorerOptions; chartOptions: MetricsExplorerChartOptions; - onLoadMore: (afterKey: StringOrNull | Record) => void; + onLoadMore: () => void; onRefetch: () => void; onFilter: (filter: string) => void; onTimeChange: (start: string, end: string) => void; - data: MetricsExplorerResponse | null; + data?: { + pages: MetricsExplorerResponse[]; + }; source: MetricsSourceConfigurationProperties | undefined; timeRange: MetricsExplorerTimeOptions; } export const MetricsExplorerCharts = ({ - loading, + isLoading, data, onLoadMore, options, @@ -47,7 +48,7 @@ export const MetricsExplorerCharts = ({ timeRange, onTimeChange, }: Props) => { - if (loading) { + if (isLoading) { return ( - - {data.series.map((series) => ( - - 1 ? 200 : 400} - series={series} - source={source} - timeRange={timeRange} - onTimeChange={onTimeChange} - /> - - ))} + + {data.pages.map((page) => + page.series.map((series) => ( + + 1 ? 200 : 400} + series={series} + source={source} + timeRange={timeRange} + onTimeChange={onTimeChange} + /> + + )) + )} - {data.series.length > 1 ? ( + {firstPage.series.length > 1 ? (
@@ -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": "小时",