From 8e5c5fa72b8adbf3d4bc7015c872d22dcda39155 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 25 Oct 2024 02:29:43 +1100 Subject: [PATCH] [8.x] [DataUsage][Serverless] Handle usage metrics errors (#197056) (#197647) # Backport This will backport the following commits from `main` to `8.x`: - [[DataUsage][Serverless] Handle usage metrics errors (#197056)](https://github.com/elastic/kibana/pull/197056) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Ash <1849116+ashokaditya@users.noreply.github.com> --- .../app/components/data_usage_metrics.tsx | 28 +++++++++++++-- .../public/hooks/use_get_data_streams.ts | 24 ++++--------- .../public/hooks/use_get_usage_metrics.ts | 36 ++++++++----------- .../data_usage/server/routes/error_handler.ts | 8 +++++ .../data_usage/server/services/autoops_api.ts | 26 +++++++------- .../data_usage/server/services/errors.ts | 10 ++++++ .../data_usage/server/services/index.ts | 24 +++++++++---- 7 files changed, 94 insertions(+), 62 deletions(-) create mode 100644 x-pack/plugins/data_usage/server/services/errors.ts diff --git a/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.tsx b/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.tsx index 48b6566df9e66..929ebf7a02490 100644 --- a/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.tsx +++ b/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.tsx @@ -8,6 +8,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { css } from '@emotion/react'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingElastic } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { Charts } from './charts'; import { useBreadcrumbs } from '../../utils/use_breadcrumbs'; import { useKibanaContextForPlugin } from '../../utils/use_kibana'; @@ -29,7 +30,7 @@ const FlexItemWithCss = ({ children }: { children: React.ReactNode }) => ( export const DataUsageMetrics = () => { const { - services: { chrome, appParams }, + services: { chrome, appParams, notifications }, } = useKibanaContextForPlugin(); useBreadcrumbs([{ text: PLUGIN_NAME }], appParams, chrome); @@ -43,10 +44,15 @@ export const DataUsageMetrics = () => { setUrlDateRangeFilter, } = useDataUsageMetricsUrlParams(); - const { data: dataStreams, isFetching: isFetchingDataStreams } = useGetDataUsageDataStreams({ + const { + error: errorFetchingDataStreams, + data: dataStreams, + isFetching: isFetchingDataStreams, + } = useGetDataUsageDataStreams({ selectedDataStreams: dataStreamsFromUrl, options: { enabled: true, + retry: false, }, }); @@ -93,6 +99,7 @@ export const DataUsageMetrics = () => { const { dateRangePickerState, onRefreshChange, onTimeChange } = useDateRangePicker(); const { + error: errorFetchingDataUsageMetrics, data, isFetching, isFetched, @@ -157,6 +164,23 @@ export const DataUsageMetrics = () => { onChangeMetricTypesFilter, ]); + if (errorFetchingDataUsageMetrics) { + notifications.toasts.addDanger({ + title: i18n.translate('xpack.dataUsage.getMetrics.addFailure.toast.title', { + defaultMessage: 'Error getting usage metrics', + }), + text: errorFetchingDataUsageMetrics.message, + }); + } + if (errorFetchingDataStreams) { + notifications.toasts.addDanger({ + title: i18n.translate('xpack.dataUsage.getDataStreams.addFailure.toast.title', { + defaultMessage: 'Error getting data streams', + }), + text: errorFetchingDataStreams.message, + }); + } + return ( diff --git a/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.ts b/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.ts index 35f53c49e2c28..598acca3c1faf 100644 --- a/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.ts +++ b/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.ts @@ -6,7 +6,6 @@ */ import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; -import { i18n } from '@kbn/i18n'; import { useQuery } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; import { DATA_USAGE_DATA_STREAMS_API_ROUTE } from '../../common'; @@ -33,22 +32,19 @@ export const useGetDataUsageDataStreams = ({ options?: UseQueryOptions; }): UseQueryResult => { const http = useKibanaContextForPlugin().services.http; - const { - services: { notifications }, - } = useKibanaContextForPlugin(); return useQuery({ queryKey: ['get-data-usage-data-streams'], ...options, keepPreviousData: true, queryFn: async () => { - const dataStreamsResponse = await http.get( - DATA_USAGE_DATA_STREAMS_API_ROUTE, - { + const dataStreamsResponse = await http + .get(DATA_USAGE_DATA_STREAMS_API_ROUTE, { version: '1', - // query: {}, - } - ); + }) + .catch((error) => { + throw error.body; + }); const augmentedDataStreamsBasedOnSelectedItems = dataStreamsResponse.reduce<{ selected: GetDataUsageDataStreamsResponse; @@ -87,13 +83,5 @@ export const useGetDataUsageDataStreams = ({ : PAGING_PARAMS.default ); }, - onError: (error: IHttpFetchError) => { - notifications.toasts.addDanger({ - title: i18n.translate('xpack.dataUsage.getDataStreams.addFailure.toast.title', { - defaultMessage: 'Error getting data streams', - }), - text: error.message, - }); - }, }); }; diff --git a/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts index bbd0f5d8aa02f..7e7406d72b9c0 100644 --- a/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts +++ b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts @@ -6,7 +6,6 @@ */ import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; -import { i18n } from '@kbn/i18n'; import { useQuery } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; import { UsageMetricsRequestBody, UsageMetricsResponseSchemaBody } from '../../common/rest_types'; @@ -23,33 +22,26 @@ export const useGetDataUsageMetrics = ( options: UseQueryOptions> = {} ): UseQueryResult> => { const http = useKibanaContextForPlugin().services.http; - const { - services: { notifications }, - } = useKibanaContextForPlugin(); return useQuery>({ queryKey: ['get-data-usage-metrics', body], ...options, keepPreviousData: true, queryFn: async ({ signal }) => { - return http.post(DATA_USAGE_METRICS_API_ROUTE, { - signal, - version: '1', - body: JSON.stringify({ - from: body.from, - to: body.to, - metricTypes: body.metricTypes, - dataStreams: body.dataStreams, - }), - }); - }, - onError: (error: IHttpFetchError) => { - notifications.toasts.addDanger({ - title: i18n.translate('xpack.dataUsage.getMetrics.addFailure.toast.title', { - defaultMessage: 'Error getting usage metrics', - }), - text: error.message, - }); + return http + .post(DATA_USAGE_METRICS_API_ROUTE, { + signal, + version: '1', + body: JSON.stringify({ + from: body.from, + to: body.to, + metricTypes: body.metricTypes, + dataStreams: body.dataStreams, + }), + }) + .catch((error) => { + throw error.body; + }); }, }); }; diff --git a/x-pack/plugins/data_usage/server/routes/error_handler.ts b/x-pack/plugins/data_usage/server/routes/error_handler.ts index 122df5e72b130..b889d12674db5 100644 --- a/x-pack/plugins/data_usage/server/routes/error_handler.ts +++ b/x-pack/plugins/data_usage/server/routes/error_handler.ts @@ -8,6 +8,7 @@ import type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server'; import { CustomHttpRequestError } from '../utils/custom_http_request_error'; import { BaseError } from '../common/errors'; +import { AutoOpsError } from '../services/errors'; export class NotFoundError extends BaseError {} @@ -31,6 +32,13 @@ export const errorHandler = ( }); } + if (error instanceof AutoOpsError) { + return res.customError({ + statusCode: 503, + body: error, + }); + } + if (error instanceof NotFoundError) { return res.notFound({ body: error }); } diff --git a/x-pack/plugins/data_usage/server/services/autoops_api.ts b/x-pack/plugins/data_usage/server/services/autoops_api.ts index ece0ec86116f2..e5ffe24c6167a 100644 --- a/x-pack/plugins/data_usage/server/services/autoops_api.ts +++ b/x-pack/plugins/data_usage/server/services/autoops_api.ts @@ -18,7 +18,11 @@ import { } from '../../common/rest_types'; import { AppContextService } from './app_context'; import { AutoOpsConfig } from '../types'; +import { AutoOpsError } from './errors'; +const AGENT_CREATION_FAILED_ERROR = 'AutoOps API could not create the autoops agent'; +const AUTO_OPS_AGENT_CREATION_PREFIX = '[AutoOps API] Creating autoops agent failed'; +const AUTO_OPS_MISSING_CONFIG_ERROR = 'Missing autoops configuration'; export class AutoOpsAPIService { constructor(private appContextService: AppContextService) {} public async autoOpsUsageMetricsAPI(requestBody: UsageMetricsRequestBody) { @@ -34,8 +38,8 @@ export class AutoOpsAPIService { const autoopsConfig = this.appContextService.getConfig()?.autoops; if (!autoopsConfig) { - logger.error('[AutoOps API] Missing autoops configuration', errorMetadata); - throw new Error('missing autoops configuration'); + logger.error(`[AutoOps API] ${AUTO_OPS_MISSING_CONFIG_ERROR}`, errorMetadata); + throw new AutoOpsError(AUTO_OPS_MISSING_CONFIG_ERROR); } logger.debug( @@ -86,7 +90,7 @@ export class AutoOpsAPIService { (error: Error | AxiosError) => { if (!axios.isAxiosError(error)) { logger.error( - `[AutoOps API] Creating autoops failed with an error ${error} ${requestConfigDebugStatus}`, + `${AUTO_OPS_AGENT_CREATION_PREFIX} with an error ${error} ${requestConfigDebugStatus}`, errorMetadataWithRequestConfig ); throw new Error(withRequestIdMessage(error.message)); @@ -97,7 +101,7 @@ export class AutoOpsAPIService { if (error.response) { // The request was made and the server responded with a status code and error data logger.error( - `[AutoOps API] Creating autoops failed because the AutoOps API responding with a status code that falls out of the range of 2xx: ${JSON.stringify( + `${AUTO_OPS_AGENT_CREATION_PREFIX} because the AutoOps API responded with a status code that falls out of the range of 2xx: ${JSON.stringify( error.response.status )}} ${JSON.stringify(error.response.data)}} ${requestConfigDebugStatus}`, { @@ -111,30 +115,26 @@ export class AutoOpsAPIService { }, } ); - throw new Error( - withRequestIdMessage(`the AutoOps API could not create the autoops agent`) - ); + throw new AutoOpsError(withRequestIdMessage(AGENT_CREATION_FAILED_ERROR)); } else if (error.request) { // The request was made but no response was received logger.error( - `[AutoOps API] Creating autoops agent failed while sending the request to the AutoOps API: ${errorLogCodeCause} ${requestConfigDebugStatus}`, + `${AUTO_OPS_AGENT_CREATION_PREFIX} while sending the request to the AutoOps API: ${errorLogCodeCause} ${requestConfigDebugStatus}`, errorMetadataWithRequestConfig ); throw new Error(withRequestIdMessage(`no response received from the AutoOps API`)); } else { // Something happened in setting up the request that triggered an Error logger.error( - `[AutoOps API] Creating autoops agent failed to be created ${errorLogCodeCause} ${requestConfigDebugStatus}`, + `${AUTO_OPS_AGENT_CREATION_PREFIX} to be created ${errorLogCodeCause} ${requestConfigDebugStatus}`, errorMetadataWithRequestConfig ); - throw new Error( - withRequestIdMessage('the AutoOps API could not create the autoops agent') - ); + throw new AutoOpsError(withRequestIdMessage(AGENT_CREATION_FAILED_ERROR)); } } ); - logger.debug(`[AutoOps API] Created an autoops agent ${response}`); + logger.debug(`[AutoOps API] Successfully created an autoops agent ${response}`); return response; } diff --git a/x-pack/plugins/data_usage/server/services/errors.ts b/x-pack/plugins/data_usage/server/services/errors.ts new file mode 100644 index 0000000000000..0574e2a3c75fb --- /dev/null +++ b/x-pack/plugins/data_usage/server/services/errors.ts @@ -0,0 +1,10 @@ +/* + * 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 { BaseError } from '../common/errors'; + +export class AutoOpsError extends BaseError {} diff --git a/x-pack/plugins/data_usage/server/services/index.ts b/x-pack/plugins/data_usage/server/services/index.ts index 4026891180a78..9ccd08861a26c 100644 --- a/x-pack/plugins/data_usage/server/services/index.ts +++ b/x-pack/plugins/data_usage/server/services/index.ts @@ -4,10 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { ValidationError } from '@kbn/config-schema'; import { AppContextService } from './app_context'; import { AutoOpsAPIService } from './autoops_api'; import type { DataUsageContext } from '../types'; import { MetricTypes } from '../../common/rest_types'; +import { AutoOpsError } from './errors'; export class DataUsageService { private appContextService: AppContextService; @@ -32,12 +34,20 @@ export class DataUsageService { metricTypes: MetricTypes[]; dataStreams: string[]; }) { - const response = await this.autoOpsAPIService.autoOpsUsageMetricsAPI({ - from, - to, - metricTypes, - dataStreams, - }); - return response.data; + try { + const response = await this.autoOpsAPIService.autoOpsUsageMetricsAPI({ + from, + to, + metricTypes, + dataStreams, + }); + return response.data; + } catch (error) { + if (error instanceof ValidationError) { + throw new AutoOpsError(error.message); + } + + throw error; + } } }