diff --git a/x-pack/plugins/ml/common/util/errors.test.ts b/x-pack/plugins/ml/common/util/errors.test.ts new file mode 100644 index 0000000000000..00af27248ccce --- /dev/null +++ b/x-pack/plugins/ml/common/util/errors.test.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + BoomResponse, + extractErrorMessage, + MLCustomHttpResponseOptions, + MLResponseError, +} from './errors'; +import { ResponseError } from 'kibana/server'; + +describe('ML - error message utils', () => { + describe('extractErrorMessage', () => { + test('returns just the error message', () => { + const testMsg = 'Saved object [index-pattern/blahblahblah] not found'; + + const bodyWithNestedErrorMsg: MLCustomHttpResponseOptions = { + body: { + message: { + msg: testMsg, + }, + }, + statusCode: 404, + }; + expect(extractErrorMessage(bodyWithNestedErrorMsg)).toBe(testMsg); + + const bodyWithStringMsg: MLCustomHttpResponseOptions = { + body: { + msg: testMsg, + }, + statusCode: 404, + }; + expect(extractErrorMessage(bodyWithStringMsg)).toBe(testMsg); + + const bodyWithStringMessage: MLCustomHttpResponseOptions = { + body: { + message: testMsg, + }, + statusCode: 404, + }; + expect(extractErrorMessage(bodyWithStringMessage)).toBe(testMsg); + + const bodyWithString: MLCustomHttpResponseOptions = { + body: testMsg, + statusCode: 404, + }; + expect(extractErrorMessage(bodyWithString)).toBe(testMsg); + + const bodyWithError: MLCustomHttpResponseOptions = { + body: new Error(testMsg), + statusCode: 404, + }; + expect(extractErrorMessage(bodyWithError)).toBe(testMsg); + + const bodyWithBoomError: MLCustomHttpResponseOptions = { + statusCode: 404, + body: { + data: [], + isBoom: true, + isServer: false, + output: { + statusCode: 404, + payload: { + statusCode: 404, + error: testMsg, + message: testMsg, + }, + headers: {}, + }, + }, + }; + expect(extractErrorMessage(bodyWithBoomError)).toBe(testMsg); + }); + }); +}); diff --git a/x-pack/plugins/ml/common/util/errors.ts b/x-pack/plugins/ml/common/util/errors.ts index 6f620f74a4531..e165e15d7c64e 100644 --- a/x-pack/plugins/ml/common/util/errors.ts +++ b/x-pack/plugins/ml/common/util/errors.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CustomHttpResponseOptions, ResponseError } from 'kibana/server'; +import { ResponseError, ResponseHeaders } from 'kibana/server'; import { isErrorResponse } from '../types/errors'; export function getErrorMessage(error: any) { @@ -19,34 +19,76 @@ export function getErrorMessage(error: any) { return JSON.stringify(error); } +// Adding temporary types until Kibana ResponseError is updated + +export interface BoomResponse { + data: any; + isBoom: boolean; + isServer: boolean; + output: { + statusCode: number; + payload: { + statusCode: number; + error: string; + message: string; + }; + headers: {}; + }; +} +export type MLResponseError = + | { + message: { + msg: string; + }; + } + | { msg: string }; + +export interface MLCustomHttpResponseOptions< + T extends ResponseError | MLResponseError | BoomResponse +> { + /** HTTP message to send to the client */ + body?: T; + /** HTTP Headers with additional information about response */ + headers?: ResponseHeaders; + statusCode: number; +} + export const extractErrorMessage = ( - error: CustomHttpResponseOptions | undefined | string -): string | undefined => { + error: + | MLCustomHttpResponseOptions + | undefined + | string +): string => { + // extract only the error message within the response error coming from Kibana, Elasticsearch, and our own ML messages + if (typeof error === 'string') { return error; } + if (error?.body === undefined) return ''; - if (error?.body) { - if (typeof error.body === 'string') { - return error.body; - } - if (typeof error.body === 'object' && 'message' in error.body) { - if (typeof error.body.message === 'string') { - return error.body.message; - } - // @ts-ignore - if (typeof (error.body.message?.msg === 'string')) { - // @ts-ignore - return error.body.message?.msg; - } + if (typeof error.body === 'string') { + return error.body; + } + if ( + typeof error.body === 'object' && + 'output' in error.body && + error.body.output.payload.message + ) { + return error.body.output.payload.message; + } + + if (typeof error.body === 'object' && 'msg' in error.body && typeof error.body.msg === 'string') { + return error.body.msg; + } + + if (typeof error.body === 'object' && 'message' in error.body) { + if (typeof error.body.message === 'string') { + return error.body.message; } - if (typeof error.body === 'object' && 'msg' in error.body) { - // @ts-ignore - if (typeof error.body.msg === 'string') { - // @ts-ignore - return error.body.msg; - } + if (!(error.body.message instanceof Error) && typeof (error.body.message.msg === 'string')) { + return error.body.message.msg; } } - return undefined; + // If all else fail return an empty message instead of JSON.stringify + return ''; }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx index 2d433f6b18484..38ef00914e8fb 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx @@ -18,6 +18,7 @@ import { } from '@elastic/eui'; import { IIndexPattern } from 'src/plugins/data/common'; import { FormattedMessage } from '@kbn/i18n/react'; +import { extractErrorMessage } from '../../../../../../../common/util/errors'; import { deleteAnalytics, deleteAnalyticsAndDestIndex, @@ -29,7 +30,6 @@ import { } from '../../../../../capabilities/check_capabilities'; import { useMlKibana } from '../../../../../contexts/kibana'; import { isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common'; -import { extractErrorMessage } from '../../../../../util/error_utils'; interface DeleteActionProps { item: DataFrameAnalyticsListRow; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts index 26cefff0a3f59..ebd3fa8982604 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; +import { extractErrorMessage } from '../../../../../../../common/util/errors'; import { getToastNotifications } from '../../../../../util/dependency_cache'; import { ml } from '../../../../../services/ml_api_service'; import { refreshAnalyticsList$, REFRESH_ANALYTICS_LIST_STATE } from '../../../../common'; @@ -11,7 +12,6 @@ import { isDataFrameAnalyticsFailed, DataFrameAnalyticsListRow, } from '../../components/analytics_list/common'; -import { extractErrorMessage } from '../../../../../util/error_utils'; export const deleteAnalytics = async (d: DataFrameAnalyticsListRow) => { const toastNotifications = getToastNotifications(); diff --git a/x-pack/plugins/ml/public/application/util/error_utils.test.ts b/x-pack/plugins/ml/public/application/util/error_utils.test.ts deleted file mode 100644 index a37cae42be518..0000000000000 --- a/x-pack/plugins/ml/public/application/util/error_utils.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { extractErrorMessage, MLCustomHttpResponseOptions, MLResponseError } from './error_utils'; -import expect from '@kbn/expect/expect.js'; -import { ResponseError } from 'kibana/server'; - -describe('ML - error message utils', () => { - describe('extractErrorMessage', () => { - test('returns just the error message', () => { - const bodyWithNestedErrorMsg: MLCustomHttpResponseOptions = { - body: { - message: { - msg: 'Not Found', - }, - }, - statusCode: 404, - }; - expect(extractErrorMessage(bodyWithNestedErrorMsg)).to.be('Not Found'); - - const bodyWithStringMsg: MLCustomHttpResponseOptions = { - body: { - msg: 'Not Found', - }, - statusCode: 404, - }; - expect(extractErrorMessage(bodyWithStringMsg)).to.be('Not Found'); - - const bodyWithStringMessage: MLCustomHttpResponseOptions = { - body: { - message: 'Not Found', - }, - statusCode: 404, - }; - expect(extractErrorMessage(bodyWithStringMessage)).to.be('Not Found'); - - const bodyWithString: MLCustomHttpResponseOptions = { - body: 'Not Found', - statusCode: 404, - }; - expect(extractErrorMessage(bodyWithString)).to.be('Not Found'); - - const bodyWithError: MLCustomHttpResponseOptions = { - body: new Error('Not Found'), - statusCode: 404, - }; - expect(extractErrorMessage(bodyWithError)).to.be('Not Found'); - }); - }); -}); diff --git a/x-pack/plugins/ml/public/application/util/error_utils.ts b/x-pack/plugins/ml/public/application/util/error_utils.ts deleted file mode 100644 index c7de4472ca9dc..0000000000000 --- a/x-pack/plugins/ml/public/application/util/error_utils.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ResponseError, ResponseHeaders } from 'kibana/server'; - -// Adding temporary types until Kibana ResponseError is updated -export type MLResponseError = - | { - message: { - msg: string; - }; - } - | { msg: string }; - -export interface MLCustomHttpResponseOptions { - /** HTTP message to send to the client */ - body?: T; - /** HTTP Headers with additional information about response */ - headers?: ResponseHeaders; - statusCode: number; -} - -export const extractErrorMessage = ( - error: - | MLCustomHttpResponseOptions - | MLCustomHttpResponseOptions - | undefined - | string -): string | undefined => { - // extract only the error message within the response error coming from Kibana, Elasticsearch, and our own ML messages - - if (typeof error === 'string') { - return error; - } - if (error?.body === undefined) return; - - if (typeof error.body === 'string') { - return error.body; - } - if (typeof error.body === 'object' && 'msg' in error.body && typeof error.body.msg === 'string') { - return error.body.msg; - } - - if (typeof error.body === 'object' && 'message' in error.body) { - if (typeof error.body.message === 'string') { - return error.body.message; - } - if (!(error.body.message instanceof Error) && typeof (error.body.message.msg === 'string')) { - return error.body.message.msg; - } - } -};