[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)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Ash","email":"1849116+ashokaditya@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-10-24T13:48:33Z","message":"[DataUsage][Serverless]
Handle usage metrics errors
(#197056)","sha":"1267bd7129912690d469ae6d359c8242a679dfb8","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:version","v8.17.0"],"title":"[DataUsage][Serverless]
Handle usage metrics
errors","number":197056,"url":"https://github.com/elastic/kibana/pull/197056","mergeCommit":{"message":"[DataUsage][Serverless]
Handle usage metrics errors
(#197056)","sha":"1267bd7129912690d469ae6d359c8242a679dfb8"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/197056","number":197056,"mergeCommit":{"message":"[DataUsage][Serverless]
Handle usage metrics errors
(#197056)","sha":"1267bd7129912690d469ae6d359c8242a679dfb8"}},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Ash <1849116+ashokaditya@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2024-10-25 02:29:43 +11:00 committed by GitHub
parent 75933e164b
commit 8e5c5fa72b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 94 additions and 62 deletions

View file

@ -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 (
<EuiFlexGroup alignItems="flexStart" direction="column">
<FlexItemWithCss>

View file

@ -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<GetDataUsageDataStreamsResponse, IHttpFetchError>;
}): UseQueryResult<GetDataUsageDataStreamsResponse, IHttpFetchError> => {
const http = useKibanaContextForPlugin().services.http;
const {
services: { notifications },
} = useKibanaContextForPlugin();
return useQuery<GetDataUsageDataStreamsResponse, IHttpFetchError>({
queryKey: ['get-data-usage-data-streams'],
...options,
keepPreviousData: true,
queryFn: async () => {
const dataStreamsResponse = await http.get<GetDataUsageDataStreamsResponse>(
DATA_USAGE_DATA_STREAMS_API_ROUTE,
{
const dataStreamsResponse = await http
.get<GetDataUsageDataStreamsResponse>(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,
});
},
});
};

View file

@ -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<UsageMetricsResponseSchemaBody, IHttpFetchError<ErrorType>> = {}
): UseQueryResult<UsageMetricsResponseSchemaBody, IHttpFetchError<ErrorType>> => {
const http = useKibanaContextForPlugin().services.http;
const {
services: { notifications },
} = useKibanaContextForPlugin();
return useQuery<UsageMetricsResponseSchemaBody, IHttpFetchError<ErrorType>>({
queryKey: ['get-data-usage-metrics', body],
...options,
keepPreviousData: true,
queryFn: async ({ signal }) => {
return http.post<UsageMetricsResponseSchemaBody>(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<ErrorType>) => {
notifications.toasts.addDanger({
title: i18n.translate('xpack.dataUsage.getMetrics.addFailure.toast.title', {
defaultMessage: 'Error getting usage metrics',
}),
text: error.message,
});
return http
.post<UsageMetricsResponseSchemaBody>(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;
});
},
});
};

View file

@ -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 = <E extends Error>(
});
}
if (error instanceof AutoOpsError) {
return res.customError({
statusCode: 503,
body: error,
});
}
if (error instanceof NotFoundError) {
return res.notFound({ body: error });
}

View file

@ -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;
}

View file

@ -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 {}

View file

@ -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;
}
}
}