diff --git a/x-pack/plugins/ml/common/types/data_frame_analytics.ts b/x-pack/plugins/ml/common/types/data_frame_analytics.ts index 60d2ca63dda59..96d6c81a3d309 100644 --- a/x-pack/plugins/ml/common/types/data_frame_analytics.ts +++ b/x-pack/plugins/ml/common/types/data_frame_analytics.ts @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CustomHttpResponseOptions, ResponseError } from 'kibana/server'; +import Boom from 'boom'; +import { EsErrorBody } from '../util/errors'; export interface DeleteDataFrameAnalyticsWithIndexStatus { success: boolean; - error?: CustomHttpResponseOptions; + error?: EsErrorBody | Boom; } export type IndexName = string; diff --git a/x-pack/plugins/ml/common/util/errors.test.ts b/x-pack/plugins/ml/common/util/errors.test.ts deleted file mode 100644 index 0b99799e3b6ec..0000000000000 --- a/x-pack/plugins/ml/common/util/errors.test.ts +++ /dev/null @@ -1,80 +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 { - 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, - response: `{"error":{"reason":"${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 deleted file mode 100644 index a5f89db96cfd7..0000000000000 --- a/x-pack/plugins/ml/common/util/errors.ts +++ /dev/null @@ -1,177 +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'; -import { isErrorResponse } from '../types/errors'; - -export function getErrorMessage(error: any) { - if (isErrorResponse(error)) { - return `${error.body.error}: ${error.body.message}`; - } - - if (typeof error === 'object' && typeof error.message === 'string') { - return error.message; - } - - 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; statusCode: number; response: 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 interface MLErrorObject { - message: string; - fullErrorMessage?: string; // For use in a 'See full error' popover. - statusCode?: number; -} - -export const extractErrorProperties = ( - error: - | MLCustomHttpResponseOptions - | string - | undefined -): MLErrorObject => { - // extract properties of the error object from within the response error - // coming from Kibana, Elasticsearch, and our own ML messages - let message = ''; - let fullErrorMessage; - let statusCode; - - if (typeof error === 'string') { - return { - message: error, - }; - } - if (error?.body === undefined) { - return { - message: '', - }; - } - - if (typeof error.body === 'string') { - return { - message: error.body, - }; - } - if ( - typeof error.body === 'object' && - 'output' in error.body && - error.body.output.payload.message - ) { - return { - message: error.body.output.payload.message, - }; - } - - if ( - typeof error.body === 'object' && - 'response' in error.body && - typeof error.body.response === 'string' - ) { - const errorResponse = JSON.parse(error.body.response); - if ('error' in errorResponse && typeof errorResponse === 'object') { - const errorResponseError = errorResponse.error; - if ('reason' in errorResponseError) { - message = errorResponseError.reason; - } - if ('caused_by' in errorResponseError) { - const causedByMessage = JSON.stringify(errorResponseError.caused_by); - // Only add a fullErrorMessage if different to the message. - if (causedByMessage !== message) { - fullErrorMessage = causedByMessage; - } - } - return { - message, - fullErrorMessage, - statusCode: error.statusCode, - }; - } - } - - if (typeof error.body === 'object' && 'msg' in error.body && typeof error.body.msg === 'string') { - return { - message: error.body.msg, - }; - } - - if (typeof error.body === 'object' && 'message' in error.body) { - if ( - 'attributes' in error.body && - typeof error.body.attributes === 'object' && - error.body.attributes.body?.status !== undefined - ) { - statusCode = error.body.attributes.body.status; - - if (typeof error.body.attributes.body.error?.reason === 'string') { - return { - message: error.body.attributes.body.error.reason, - statusCode, - }; - } - } - - if (typeof error.body.message === 'string') { - return { - message: error.body.message, - statusCode, - }; - } - if (!(error.body.message instanceof Error) && typeof (error.body.message.msg === 'string')) { - return { - message: error.body.message.msg, - statusCode, - }; - } - } - - // If all else fail return an empty message instead of JSON.stringify - return { - message: '', - }; -}; - -export const extractErrorMessage = ( - error: - | MLCustomHttpResponseOptions - | undefined - | string -): string => { - // extract only the error message within the response error coming from Kibana, Elasticsearch, and our own ML messages - const errorObj = extractErrorProperties(error); - return errorObj.message; -}; diff --git a/x-pack/plugins/ml/common/util/errors/errors.test.ts b/x-pack/plugins/ml/common/util/errors/errors.test.ts new file mode 100644 index 0000000000000..ddaf0b04dd12b --- /dev/null +++ b/x-pack/plugins/ml/common/util/errors/errors.test.ts @@ -0,0 +1,99 @@ +/* + * 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 Boom from 'boom'; + +import { extractErrorMessage, MLHttpFetchError, MLResponseError, EsErrorBody } from './index'; + +describe('ML - error message utils', () => { + describe('extractErrorMessage', () => { + test('returns just the error message', () => { + const testMsg = 'Saved object [index-pattern/indexpattern] not found'; + + // bad error, return empty string + const badError = {} as any; + expect(extractErrorMessage(badError)).toBe(''); + + // raw es error + const esErrorMsg: EsErrorBody = { + error: { + root_cause: [ + { + type: 'type', + reason: 'reason', + }, + ], + type: 'type', + reason: testMsg, + }, + status: 404, + }; + expect(extractErrorMessage(esErrorMsg)).toBe(testMsg); + + // error is basic string + const stringMessage = testMsg; + expect(extractErrorMessage(stringMessage)).toBe(testMsg); + + // kibana error without attributes + const bodyWithoutAttributes: MLHttpFetchError = { + name: 'name', + req: {} as Request, + request: {} as Request, + message: 'Something else', + body: { + statusCode: 404, + error: 'error', + message: testMsg, + }, + }; + expect(extractErrorMessage(bodyWithoutAttributes)).toBe(testMsg); + + // kibana error with attributes + const bodyWithAttributes: MLHttpFetchError = { + name: 'name', + req: {} as Request, + request: {} as Request, + message: 'Something else', + body: { + statusCode: 404, + error: 'error', + message: 'Something else', + attributes: { + body: { + status: 404, + error: { + reason: testMsg, + type: 'type', + root_cause: [{ type: 'type', reason: 'reason' }], + }, + }, + }, + }, + }; + expect(extractErrorMessage(bodyWithAttributes)).toBe(testMsg); + + // boom error + const boomError: Boom = { + message: '', + reformat: () => '', + name: '', + data: [], + isBoom: true, + isServer: false, + output: { + statusCode: 404, + payload: { + statusCode: 404, + error: testMsg, + message: testMsg, + }, + headers: {}, + }, + }; + expect(extractErrorMessage(boomError)).toBe(testMsg); + }); + }); +}); diff --git a/x-pack/plugins/ml/common/util/errors/index.ts b/x-pack/plugins/ml/common/util/errors/index.ts new file mode 100644 index 0000000000000..d6d33b86e6fa9 --- /dev/null +++ b/x-pack/plugins/ml/common/util/errors/index.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export { MLRequestFailure } from './request_error'; +export { extractErrorMessage, extractErrorProperties } from './process_errors'; +export { + ErrorType, + EsErrorBody, + EsErrorRootCause, + MLErrorObject, + MLHttpFetchError, + MLResponseError, + isBoomError, + isErrorString, + isEsErrorBody, + isMLResponseError, +} from './types'; diff --git a/x-pack/plugins/ml/common/util/errors/process_errors.ts b/x-pack/plugins/ml/common/util/errors/process_errors.ts new file mode 100644 index 0000000000000..4addfa414ef56 --- /dev/null +++ b/x-pack/plugins/ml/common/util/errors/process_errors.ts @@ -0,0 +1,83 @@ +/* + * 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 { + ErrorType, + MLErrorObject, + isBoomError, + isErrorString, + isEsErrorBody, + isMLResponseError, +} from './types'; + +export const extractErrorProperties = (error: ErrorType): MLErrorObject => { + // extract properties of the error object from within the response error + // coming from Kibana, Elasticsearch, and our own ML messages + + // some responses contain raw es errors as part of a bulk response + // e.g. if some jobs fail the action in a bulk request + if (isEsErrorBody(error)) { + return { + message: error.error.reason, + statusCode: error.status, + fullError: error, + }; + } + + if (isErrorString(error)) { + return { + message: error, + }; + } + + if (isBoomError(error)) { + return { + message: error.output.payload.message, + statusCode: error.output.payload.statusCode, + }; + } + + if (error?.body === undefined) { + return { + message: '', + }; + } + + if (typeof error.body === 'string') { + return { + message: error.body, + }; + } + + if (isMLResponseError(error)) { + if ( + typeof error.body.attributes === 'object' && + typeof error.body.attributes.body?.error?.reason === 'string' + ) { + return { + message: error.body.attributes.body.error.reason, + statusCode: error.body.statusCode, + fullError: error.body.attributes.body, + }; + } else { + return { + message: error.body.message, + statusCode: error.body.statusCode, + }; + } + } + + // If all else fail return an empty message instead of JSON.stringify + return { + message: '', + }; +}; + +export const extractErrorMessage = (error: ErrorType): string => { + // extract only the error message within the response error coming from Kibana, Elasticsearch, and our own ML messages + const errorObj = extractErrorProperties(error); + return errorObj.message; +}; diff --git a/x-pack/plugins/ml/common/util/errors/request_error.ts b/x-pack/plugins/ml/common/util/errors/request_error.ts new file mode 100644 index 0000000000000..faa8a8e2dc0d4 --- /dev/null +++ b/x-pack/plugins/ml/common/util/errors/request_error.ts @@ -0,0 +1,26 @@ +/* + * 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 { MLErrorObject, ErrorType } from './types'; + +export class MLRequestFailure extends Error { + constructor(error: MLErrorObject, resp: ErrorType) { + super(error.message); + Object.setPrototypeOf(this, new.target.prototype); + + if (typeof resp !== 'string' && typeof resp !== 'undefined') { + if ('body' in resp) { + this.stack = JSON.stringify(resp.body, null, 2); + } else { + try { + this.stack = JSON.stringify(resp, null, 2); + } catch (e) { + // fail silently + } + } + } + } +} diff --git a/x-pack/plugins/ml/common/util/errors/types.ts b/x-pack/plugins/ml/common/util/errors/types.ts new file mode 100644 index 0000000000000..3d9fc82be8d85 --- /dev/null +++ b/x-pack/plugins/ml/common/util/errors/types.ts @@ -0,0 +1,60 @@ +/* + * 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 { HttpFetchError } from 'kibana/public'; +import Boom from 'boom'; + +export interface EsErrorRootCause { + type: string; + reason: string; +} + +export interface EsErrorBody { + error: { + root_cause?: EsErrorRootCause[]; + caused_by?: EsErrorRootCause; + type: string; + reason: string; + }; + status: number; +} + +export interface MLResponseError { + statusCode: number; + error: string; + message: string; + attributes?: { + body: EsErrorBody; + }; +} + +export interface MLErrorObject { + message: string; + statusCode?: number; + fullError?: EsErrorBody; +} + +export interface MLHttpFetchError extends HttpFetchError { + body: T; +} + +export type ErrorType = MLHttpFetchError | EsErrorBody | Boom | string | undefined; + +export function isEsErrorBody(error: any): error is EsErrorBody { + return error && error.error?.reason !== undefined; +} + +export function isErrorString(error: any): error is string { + return typeof error === 'string'; +} + +export function isMLResponseError(error: any): error is MLResponseError { + return typeof error.body === 'object' && 'message' in error.body; +} + +export function isBoomError(error: any): error is Boom { + return error.isBoom === true; +} diff --git a/x-pack/plugins/ml/public/application/components/messagebar/messagebar_service.d.ts b/x-pack/plugins/ml/public/application/components/messagebar/messagebar_service.d.ts deleted file mode 100644 index 29a537a7ca8d8..0000000000000 --- a/x-pack/plugins/ml/public/application/components/messagebar/messagebar_service.d.ts +++ /dev/null @@ -1,13 +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. - */ - -declare interface MlMessageBarService { - notify: { - error(text: any, resp?: any): void; - }; -} - -export const mlMessageBarService: MlMessageBarService; diff --git a/x-pack/plugins/ml/public/application/components/messagebar/messagebar_service.js b/x-pack/plugins/ml/public/application/components/messagebar/messagebar_service.js deleted file mode 100644 index 1dc7140bd2687..0000000000000 --- a/x-pack/plugins/ml/public/application/components/messagebar/messagebar_service.js +++ /dev/null @@ -1,33 +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 { getToastNotifications } from '../../util/dependency_cache'; -import { MLRequestFailure } from '../../util/ml_error'; -import { i18n } from '@kbn/i18n'; - -function errorNotify(text, resp) { - let err = null; - if (typeof text === 'object' && text.response !== undefined) { - resp = text.response; - } else if (typeof text === 'object' && text.message !== undefined) { - err = new Error(text.message); - } else { - err = new Error(text); - } - - const toastNotifications = getToastNotifications(); - toastNotifications.addError(new MLRequestFailure(err, resp), { - title: i18n.translate('xpack.ml.messagebarService.errorTitle', { - defaultMessage: 'An error has occurred', - }), - }); -} - -export const mlMessageBarService = { - notify: { - error: errorNotify, - }, -}; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.js b/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.js index eed57aaf1b491..5b0224c8e9f0a 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.js @@ -10,6 +10,8 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, @@ -51,8 +53,7 @@ import { getPartitioningFieldNames } from '../../../../common/util/job_utils'; import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { mlJobService } from '../../services/job_service'; import { ml } from '../../services/ml_api_service'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { extractErrorMessage } from '../../../../common/util/errors'; class RuleEditorFlyoutUI extends Component { static propTypes = { @@ -431,8 +432,8 @@ class RuleEditorFlyoutUI extends Component { values: { jobId }, } ); - if (error.message) { - errorMessage += ` : ${error.message}`; + if (error.error) { + errorMessage += ` : ${extractErrorMessage(error.error)}`; } toasts.addDanger(errorMessage); }); diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/utils.js b/x-pack/plugins/ml/public/application/components/rule_editor/utils.js index a697eed00fd66..6a8e20647cdbe 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/utils.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/utils.js @@ -146,22 +146,17 @@ export function updateJobRules(job, detectorIndex, rules) { } return new Promise((resolve, reject) => { - mlJobService - .updateJob(jobId, jobData) - .then((resp) => { - if (resp.success) { - // Refresh the job data in the job service before resolving. - mlJobService - .refreshJob(jobId) - .then(() => { - resolve({ success: true }); - }) - .catch((refreshResp) => { - reject(refreshResp); - }); - } else { - reject(resp); - } + ml.updateJob({ jobId: jobId, job: jobData }) + .then(() => { + // Refresh the job data in the job service before resolving. + mlJobService + .refreshJob(jobId) + .then(() => { + resolve({ success: true }); + }) + .catch((refreshResp) => { + reject(refreshResp); + }); }) .catch((resp) => { reject(resp); diff --git a/x-pack/plugins/ml/public/application/components/validate_job/__snapshots__/validate_job_view.test.js.snap b/x-pack/plugins/ml/public/application/components/validate_job/__snapshots__/validate_job_view.test.js.snap index cbbf84b9e51ec..f6a161cc01999 100644 --- a/x-pack/plugins/ml/public/application/components/validate_job/__snapshots__/validate_job_view.test.js.snap +++ b/x-pack/plugins/ml/public/application/components/validate_job/__snapshots__/validate_job_view.test.js.snap @@ -8,7 +8,7 @@ exports[`ValidateJob renders button and modal with a message 1`] = ` iconSide="right" iconType="questionInCircle" isDisabled={false} - isLoading={false} + isLoading={true} onClick={[Function]} size="s" > @@ -18,62 +18,6 @@ exports[`ValidateJob renders button and modal with a message 1`] = ` values={Object {}} /> - - } - > - - - - - - - - , - } - } - /> - - `; @@ -108,7 +52,7 @@ exports[`ValidateJob renders the button and modal with a success message 1`] = ` iconSide="right" iconType="questionInCircle" isDisabled={false} - isLoading={false} + isLoading={true} onClick={[Function]} size="s" > @@ -118,52 +62,6 @@ exports[`ValidateJob renders the button and modal with a success message 1`] = ` values={Object {}} /> - - } - > - - - - - - - - , - } - } - /> - - `; diff --git a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.d.ts b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.d.ts index 43e0a5f3eac78..35e4e189b4326 100644 --- a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.d.ts +++ b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.d.ts @@ -8,7 +8,7 @@ import { FC } from 'react'; declare const ValidateJob: FC<{ getJobConfig: any; getDuration: any; - mlJobService: any; + ml: any; embedded?: boolean; setIsValid?: (valid: boolean) => void; idFilterList?: string[]; diff --git a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js index dde6925631d3e..0c079bc11cffc 100644 --- a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js +++ b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js @@ -32,6 +32,8 @@ import { getDocLinks } from '../../util/dependency_cache'; import { VALIDATION_STATUS } from '../../../../common/constants/validation'; import { getMostSevereMessageStatus } from '../../../../common/util/validation_utils'; +import { toastNotificationServiceProvider } from '../../services/toast_notification_service'; +import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; const defaultIconType = 'questionInCircle'; const getDefaultState = () => ({ @@ -182,7 +184,7 @@ Modal.propType = { title: PropTypes.string, }; -export class ValidateJob extends Component { +export class ValidateJobUI extends Component { constructor(props) { super(props); this.state = getDefaultState(); @@ -209,25 +211,40 @@ export class ValidateJob extends Component { if (typeof job === 'object') { let shouldShowLoadingIndicator = true; - this.props.mlJobService.validateJob({ duration, fields, job }).then((data) => { - shouldShowLoadingIndicator = false; - this.setState({ - ...this.state, - ui: { - ...this.state.ui, - iconType: statusToEuiIconType(getMostSevereMessageStatus(data.messages)), - isLoading: false, - isModalVisible: true, - }, - data, - title: job.job_id, - }); - if (typeof this.props.setIsValid === 'function') { - this.props.setIsValid( - data.messages.some((m) => m.status === VALIDATION_STATUS.ERROR) === false + this.props.ml + .validateJob({ duration, fields, job }) + .then((messages) => { + shouldShowLoadingIndicator = false; + this.setState({ + ...this.state, + ui: { + ...this.state.ui, + iconType: statusToEuiIconType(getMostSevereMessageStatus(messages)), + isLoading: false, + isModalVisible: true, + }, + data: { + messages, + success: true, + }, + title: job.job_id, + }); + if (typeof this.props.setIsValid === 'function') { + this.props.setIsValid( + messages.some((m) => m.status === VALIDATION_STATUS.ERROR) === false + ); + } + }) + .catch((error) => { + const { toasts } = this.props.kibana.services.notifications; + const toastNotificationService = toastNotificationServiceProvider(toasts); + toastNotificationService.displayErrorToast( + error, + i18n.translate('xpack.ml.jobService.validateJobErrorTitle', { + defaultMessage: 'Job Validation Error', + }) ); - } - }); + }); // wait for 250ms before triggering the loading indicator // to avoid flickering when there's a loading time below @@ -335,15 +352,17 @@ export class ValidateJob extends Component { ); } } -ValidateJob.propTypes = { +ValidateJobUI.propTypes = { fields: PropTypes.object, fill: PropTypes.bool, getDuration: PropTypes.func, getJobConfig: PropTypes.func.isRequired, isCurrentJobConfig: PropTypes.bool, isDisabled: PropTypes.bool, - mlJobService: PropTypes.object.isRequired, + ml: PropTypes.object.isRequired, embedded: PropTypes.bool, setIsValid: PropTypes.func, idFilterList: PropTypes.array, }; + +export const ValidateJob = withKibana(ValidateJobUI); diff --git a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js index cc8a5abb4e9ab..280dbd76d5487 100644 --- a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js +++ b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js @@ -16,6 +16,12 @@ jest.mock('../../util/dependency_cache', () => ({ }), })); +jest.mock('../../../../../../../src/plugins/kibana_react/public', () => ({ + withKibana: (comp) => { + return comp; + }, +})); + const job = { job_id: 'test-id', }; @@ -25,11 +31,16 @@ const getJobConfig = () => job; function prepareTest(messages) { const p = Promise.resolve(messages); - const mlJobService = { - validateJob: () => p, + const ml = { + validateJob: () => Promise.resolve(messages), + }; + const kibana = { + services: { + notifications: { toasts: { addDanger: jest.fn() } }, + }, }; - const component = ; + const component = ; const wrapper = shallowWithIntl(component); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index 97098ea9e75c6..60681fb6e7bbe 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -10,7 +10,7 @@ import { distinctUntilChanged, filter } from 'rxjs/operators'; import { cloneDeep } from 'lodash'; import { ml } from '../../services/ml_api_service'; import { Dictionary } from '../../../../common/types/common'; -import { getErrorMessage } from '../../../../common/util/errors'; +import { extractErrorMessage } from '../../../../common/util/errors'; import { SavedSearchQuery } from '../../contexts/ml'; import { AnalysisConfig, @@ -486,7 +486,7 @@ export const loadEvalData = async ({ results.eval = evalResult; return results; } catch (e) { - results.error = getErrorMessage(e); + results.error = extractErrorMessage(e); return results; } }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts index c162cb2754c10..53c0f02fd9a80 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getErrorMessage } from '../../../../common/util/errors'; +import { extractErrorMessage } from '../../../../common/util/errors'; import { EsSorting, SearchResponse7, UseDataGridReturnType } from '../../components/data_grid'; import { ml } from '../../services/ml_api_service'; @@ -62,7 +62,7 @@ export const getIndexData = async ( setTableItems(docs); setStatus(INDEX_STATUS.LOADED); } catch (e) { - setErrorMessage(getErrorMessage(e)); + setErrorMessage(extractErrorMessage(e)); setStatus(INDEX_STATUS.ERROR); } } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts index fde1b26106508..b0e73edff7476 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts @@ -8,7 +8,7 @@ import { useEffect, useState } from 'react'; import { IndexPattern } from '../../../../../../../src/plugins/data/public'; -import { getErrorMessage } from '../../../../common/util/errors'; +import { extractErrorMessage } from '../../../../common/util/errors'; import { getIndexPatternIdFromName } from '../../util/index_utils'; import { ml } from '../../services/ml_api_service'; @@ -83,12 +83,12 @@ export const useResultsViewConfig = (jobId: string) => { setIsLoadingJobConfig(false); } } catch (e) { - setJobCapsServiceErrorMessage(getErrorMessage(e)); + setJobCapsServiceErrorMessage(extractErrorMessage(e)); setIsLoadingJobConfig(false); } } } catch (e) { - setJobConfigErrorMessage(getErrorMessage(e)); + setJobConfigErrorMessage(extractErrorMessage(e)); setIsLoadingJobConfig(false); } })(); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts index eab5165a42137..ea958c8c4a3a3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts @@ -25,7 +25,7 @@ import { SearchResponse7, UseIndexDataReturnType, } from '../../../../components/data_grid'; -import { getErrorMessage } from '../../../../../../common/util/errors'; +import { extractErrorMessage } from '../../../../../../common/util/errors'; import { INDEX_STATUS } from '../../../common/analytics'; import { ml } from '../../../../services/ml_api_service'; @@ -94,7 +94,7 @@ export const useIndexData = ( setTableItems(docs); setStatus(INDEX_STATUS.LOADED); } catch (e) { - setErrorMessage(getErrorMessage(e)); + setErrorMessage(extractErrorMessage(e)); setStatus(INDEX_STATUS.ERROR); } }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx index ac1c710e1d106..f833cf4708cec 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx @@ -11,7 +11,6 @@ import { MlContext } from '../../../../../contexts/ml'; import { kibanaContextValueMock } from '../../../../../contexts/ml/__mocks__/kibana_context_value'; import { useCreateAnalyticsForm } from './use_create_analytics_form'; -import { getErrorMessage } from '../../../../../../../common/util/errors'; const getMountedHook = () => mountHook( @@ -21,28 +20,6 @@ const getMountedHook = () => ) ); -describe('getErrorMessage()', () => { - test('verify error message response formats', () => { - const customError1 = { - body: { statusCode: 403, error: 'Forbidden', message: 'the-error-message' }, - }; - const errorMessage1 = getErrorMessage(customError1); - expect(errorMessage1).toBe('Forbidden: the-error-message'); - - const customError2 = new Error('the-error-message'); - const errorMessage2 = getErrorMessage(customError2); - expect(errorMessage2).toBe('the-error-message'); - - const customError3 = { customErrorMessage: 'the-error-message' }; - const errorMessage3 = getErrorMessage(customError3); - expect(errorMessage3).toBe('{"customErrorMessage":"the-error-message"}'); - - const customError4 = { message: 'the-error-message' }; - const errorMessage4 = getErrorMessage(customError4); - expect(errorMessage4).toBe('the-error-message'); - }); -}); - describe('useCreateAnalyticsForm()', () => { test('initialization', () => { const { getLastHookValue } = getMountedHook(); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index 9612b9213d120..161dde51df43e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -8,7 +8,7 @@ import { useReducer } from 'react'; import { i18n } from '@kbn/i18n'; -import { getErrorMessage } from '../../../../../../../common/util/errors'; +import { extractErrorMessage } from '../../../../../../../common/util/errors'; import { DeepReadonly } from '../../../../../../../common/types/common'; import { ml } from '../../../../../services/ml_api_service'; import { useMlContext } from '../../../../../contexts/ml'; @@ -115,7 +115,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { refresh(); } catch (e) { addRequestMessage({ - error: getErrorMessage(e), + error: extractErrorMessage(e), message: i18n.translate( 'xpack.ml.dataframe.analytics.create.errorCreatingDataFrameAnalyticsJob', { @@ -178,7 +178,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { }); } catch (e) { addRequestMessage({ - error: getErrorMessage(e), + error: extractErrorMessage(e), message: i18n.translate( 'xpack.ml.dataframe.analytics.create.createIndexPatternErrorMessage', { @@ -199,7 +199,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { ); } catch (e) { addRequestMessage({ - error: getErrorMessage(e), + error: extractErrorMessage(e), message: i18n.translate( 'xpack.ml.dataframe.analytics.create.errorGettingDataFrameAnalyticsList', { @@ -225,7 +225,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { }); } catch (e) { addRequestMessage({ - error: getErrorMessage(e), + error: extractErrorMessage(e), message: i18n.translate( 'xpack.ml.dataframe.analytics.create.errorGettingIndexPatternTitles', { @@ -260,7 +260,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { refresh(); } catch (e) { addRequestMessage({ - error: getErrorMessage(e), + error: extractErrorMessage(e), message: i18n.translate( 'xpack.ml.dataframe.analytics.create.errorStartingDataFrameAnalyticsJob', { 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 9de859742438e..a21be83732613 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 @@ -85,12 +85,11 @@ export const deleteAnalyticsAndDestIndex = async ( ); } if (status.destIndexDeleted?.error) { - const error = extractErrorMessage(status.destIndexDeleted.error); - toastNotificationService.displayDangerToast( + toastNotificationService.displayErrorToast( + status.destIndexDeleted.error, i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexErrorMessage', { - defaultMessage: - 'An error occurred deleting destination index {destinationIndex}: {error}', - values: { destinationIndex, error }, + defaultMessage: 'An error occurred deleting destination index {destinationIndex}', + values: { destinationIndex }, }) ); } diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.test.js index d705e47a5e906..58adf3d892f66 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_config_builder.test.js @@ -8,8 +8,6 @@ import mockAnomalyRecord from './__mocks__/mock_anomaly_record.json'; import mockDetectorsByJob from './__mocks__/mock_detectors_by_job.json'; import mockJobConfig from './__mocks__/mock_job_config.json'; -jest.mock('../../util/ml_error', () => class MLRequestFailure {}); - jest.mock('../../services/job_service', () => ({ mlJobService: { getJob() { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js index 9d0082ffcb568..bd781d32a6b06 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js @@ -7,6 +7,8 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { cloneDeep, isEqual, pick } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, EuiButtonEmpty, @@ -28,8 +30,6 @@ import { loadFullJob } from '../utils'; import { validateModelMemoryLimit, validateGroupNames, isValidCustomUrls } from '../validate_job'; import { toastNotificationServiceProvider } from '../../../../services/toast_notification_service'; import { withKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; import { collapseLiteralStrings } from '../../../../../../shared_imports'; import { DATAFEED_STATE } from '../../../../../../common/constants/states'; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js index 5030c48a4e367..adcc576c5e356 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js @@ -6,9 +6,9 @@ import { difference } from 'lodash'; import { getNewJobLimits } from '../../../../services/ml_server_info'; -import { mlJobService } from '../../../../services/job_service'; import { processCreatedBy } from '../../../../../../common/util/job_utils'; import { getSavedObjectsClient } from '../../../../util/dependency_cache'; +import { ml } from '../../../../services/ml_api_service'; export function saveJob(job, newJobData, finish) { return new Promise((resolve, reject) => { @@ -41,14 +41,9 @@ export function saveJob(job, newJobData, finish) { // if anything has changed, post the changes if (Object.keys(jobData).length) { - mlJobService - .updateJob(job.job_id, jobData) - .then((resp) => { - if (resp.success) { - saveDatafeedWrapper(); - } else { - reject(resp); - } + ml.updateJob({ jobId: job.job_id, job: jobData }) + .then(() => { + saveDatafeedWrapper(); }) .catch((error) => { reject(error); @@ -59,17 +54,17 @@ export function saveJob(job, newJobData, finish) { }); } -function saveDatafeed(datafeedData, job) { +function saveDatafeed(datafeedConfig, job) { return new Promise((resolve, reject) => { - if (Object.keys(datafeedData).length) { + if (Object.keys(datafeedConfig).length) { const datafeedId = job.datafeed_config.datafeed_id; - mlJobService.updateDatafeed(datafeedId, datafeedData).then((resp) => { - if (resp.success) { + ml.updateDatafeed({ datafeedId, datafeedConfig }) + .then(() => { resolve(); - } else { - reject(resp); - } - }); + }) + .catch((error) => { + reject(error); + }); } else { resolve(); } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js index f73dde69a3d4c..a379f49a83159 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js @@ -6,6 +6,8 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, @@ -25,9 +27,7 @@ import { ml } from '../../../../../services/ml_api_service'; import { checkPermission } from '../../../../../capabilities/check_capabilities'; import { GroupList } from './group_list'; import { NewGroupInput } from './new_group_input'; -import { mlMessageBarService } from '../../../../../components/messagebar'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { getToastNotificationService } from '../../../../../services/toast_notification_service'; function createSelectedGroups(jobs, groups) { const jobIds = jobs.map((j) => j.id); @@ -160,7 +160,7 @@ export class GroupSelector extends Component { // check success of each job update if (resp.hasOwnProperty(jobId)) { if (resp[jobId].success === false) { - mlMessageBarService.notify.error(resp[jobId].error); + getToastNotificationService().displayErrorToast(resp[jobId].error); success = false; } } @@ -175,7 +175,7 @@ export class GroupSelector extends Component { } }) .catch((error) => { - mlMessageBarService.notify.error(error); + getToastNotificationService().displayErrorToast(error); console.error(error); }); }; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js index 913727bda67df..21824aac18cdd 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -5,17 +5,19 @@ */ import { each } from 'lodash'; -import { mlMessageBarService } from '../../../components/messagebar'; +import { i18n } from '@kbn/i18n'; import rison from 'rison-node'; import { mlJobService } from '../../../services/job_service'; -import { toastNotificationServiceProvider } from '../../../services/toast_notification_service'; -import { ml } from '../../../services/ml_api_service'; +import { + getToastNotificationService, + toastNotificationServiceProvider, +} from '../../../services/toast_notification_service'; import { getToastNotifications } from '../../../util/dependency_cache'; +import { ml } from '../../../services/ml_api_service'; import { stringMatch } from '../../../util/string_utils'; import { JOB_STATE, DATAFEED_STATE } from '../../../../../common/constants/states'; import { parseInterval } from '../../../../../common/util/parse_interval'; -import { i18n } from '@kbn/i18n'; import { mlCalendarService } from '../../../services/calendar_service'; export function loadFullJob(jobId) { @@ -60,7 +62,6 @@ export function forceStartDatafeeds(jobs, start, end, finish = () => {}) { finish(); }) .catch((error) => { - mlMessageBarService.notify.error(error); const toastNotifications = getToastNotifications(); toastNotifications.addDanger( i18n.translate('xpack.ml.jobsList.startJobErrorMessage', { @@ -81,7 +82,6 @@ export function stopDatafeeds(jobs, finish = () => {}) { finish(); }) .catch((error) => { - mlMessageBarService.notify.error(error); const toastNotifications = getToastNotifications(); toastNotifications.addDanger( i18n.translate('xpack.ml.jobsList.stopJobErrorMessage', { @@ -219,9 +219,8 @@ export async function cloneJob(jobId) { window.location.href = '#/jobs/new_job'; } catch (error) { - mlMessageBarService.notify.error(error); - const toastNotifications = getToastNotifications(); - toastNotifications.addDanger( + getToastNotificationService().displayErrorToast( + error, i18n.translate('xpack.ml.jobsList.cloneJobErrorMessage', { defaultMessage: 'Could not clone {jobId}. Job could not be found', values: { jobId }, @@ -239,13 +238,11 @@ export function closeJobs(jobs, finish = () => {}) { finish(); }) .catch((error) => { - mlMessageBarService.notify.error(error); - const toastNotifications = getToastNotifications(); - toastNotifications.addDanger( + getToastNotificationService().displayErrorToast( + error, i18n.translate('xpack.ml.jobsList.closeJobErrorMessage', { defaultMessage: 'Jobs failed to close', - }), - error + }) ); finish(); }); @@ -260,13 +257,11 @@ export function deleteJobs(jobs, finish = () => {}) { finish(); }) .catch((error) => { - mlMessageBarService.notify.error(error); - const toastNotifications = getToastNotifications(); - toastNotifications.addDanger( + getToastNotificationService().displayErrorToast( + error, i18n.translate('xpack.ml.jobsList.deleteJobErrorMessage', { defaultMessage: 'Jobs failed to delete', - }), - error + }) ); finish(); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts index 0011c88d2b524..6671aaa83abe0 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts @@ -23,7 +23,7 @@ import { useEffect, useMemo } from 'react'; import { DEFAULT_MODEL_MEMORY_LIMIT } from '../../../../../../../common/constants/new_job'; import { ml } from '../../../../../services/ml_api_service'; import { JobValidator, VALIDATION_DELAY_MS } from '../../job_validator/job_validator'; -import { ErrorResponse } from '../../../../../../../common/types/errors'; +import { MLHttpFetchError, MLResponseError } from '../../../../../../../common/util/errors'; import { useMlKibana } from '../../../../../contexts/kibana'; import { JobCreator } from '../job_creator'; @@ -36,10 +36,10 @@ export const modelMemoryEstimatorProvider = ( jobValidator: JobValidator ) => { const modelMemoryCheck$ = new Subject(); - const error$ = new Subject(); + const error$ = new Subject>(); return { - get error$(): Observable { + get error$(): Observable> { return error$.asObservable(); }, get updates$(): Observable { @@ -64,7 +64,7 @@ export const modelMemoryEstimatorProvider = ( catchError((error) => { // eslint-disable-next-line no-console console.error('Model memory limit could not be calculated', error.body); - error$.next(error.body); + error$.next(error); // fallback to the default in case estimation failed return of(DEFAULT_MODEL_MEMORY_LIMIT); }) @@ -120,7 +120,8 @@ export const useModelMemoryEstimator = ( title: i18n.translate('xpack.ml.newJob.wizard.estimateModelMemoryError', { defaultMessage: 'Model memory limit could not be calculated', }), - text: error.message, + text: + error.body.attributes?.body.error.caused_by?.reason || error.body.message || undefined, }); }) ); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts index 0ec3b609b604f..a87ba4c29baa9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts @@ -15,7 +15,7 @@ import { } from '../../../../../common/job_creator'; import { ml, BucketSpanEstimatorData } from '../../../../../../../services/ml_api_service'; import { useMlContext } from '../../../../../../../contexts/ml'; -import { mlMessageBarService } from '../../../../../../../components/messagebar'; +import { getToastNotificationService } from '../../../../../../../services/toast_notification_service'; export enum ESTIMATE_STATUS { NOT_RUNNING, @@ -68,7 +68,7 @@ export function useEstimateBucketSpan() { const { name, error, message } = await ml.estimateBucketSpan(data); setStatus(ESTIMATE_STATUS.NOT_RUNNING); if (error === true) { - mlMessageBarService.notify.error(message); + getToastNotificationService().displayErrorToast(message); } else { jobCreator.bucketSpan = name; jobCreatorUpdate(); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx index cbbddb5bbc5b8..da2e5cc0e63d9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx @@ -6,7 +6,7 @@ import React, { FC, useContext, useEffect, useState } from 'react'; import { EuiHorizontalRule } from '@elastic/eui'; -import { mlMessageBarService } from '../../../../../../../components/messagebar'; +import { getToastNotificationService } from '../../../../../../../services/toast_notification_service'; import { JobCreatorContext } from '../../../job_creator_context'; import { CategorizationJobCreator } from '../../../../../common/job_creator'; @@ -94,7 +94,7 @@ export const CategorizationDetectors: FC = ({ setIsValid }) => { setFieldExamples(null); setValidationChecks([]); setOverallValidStatus(CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID); - mlMessageBarService.notify.error(error); + getToastNotificationService().displayErrorToast(error); } } else { setFieldExamples(null); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx index 684cb5b4e0dda..762d18a5367f1 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx @@ -15,7 +15,7 @@ import { AggFieldPair } from '../../../../../../../../../common/types/fields'; import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings'; import { MetricSelector } from './metric_selector'; import { ChartGrid } from './chart_grid'; -import { mlMessageBarService } from '../../../../../../../components/messagebar'; +import { getToastNotificationService } from '../../../../../../../services/toast_notification_service'; interface Props { setIsValid: (na: boolean) => void; @@ -109,7 +109,7 @@ export const MultiMetricDetectors: FC = ({ setIsValid }) => { .loadFieldExampleValues(splitField) .then(setFieldValues) .catch((error) => { - mlMessageBarService.notify.error(error); + getToastNotificationService().displayErrorToast(error); }); } else { setFieldValues([]); @@ -138,7 +138,7 @@ export const MultiMetricDetectors: FC = ({ setIsValid }) => { ); setLineChartsData(resp); } catch (error) { - mlMessageBarService.notify.error(error); + getToastNotificationService().displayErrorToast(error); setLineChartsData([]); } setLoadingData(false); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx index f39a316440e74..cc0fbf2fc0a04 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx @@ -12,7 +12,7 @@ import { Results, ModelItem, Anomaly } from '../../../../../common/results_loade import { LineChartData } from '../../../../../common/chart_loader'; import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings'; import { ChartGrid } from './chart_grid'; -import { mlMessageBarService } from '../../../../../../../components/messagebar'; +import { getToastNotificationService } from '../../../../../../../services/toast_notification_service'; export const MultiMetricDetectorsSummary: FC = () => { const { jobCreator: jc, chartLoader, resultsLoader, chartInterval } = useContext( @@ -43,7 +43,7 @@ export const MultiMetricDetectorsSummary: FC = () => { const tempFieldValues = await chartLoader.loadFieldExampleValues(jobCreator.splitField); setFieldValues(tempFieldValues); } catch (error) { - mlMessageBarService.notify.error(error); + getToastNotificationService().displayErrorToast(error); } } })(); @@ -75,7 +75,7 @@ export const MultiMetricDetectorsSummary: FC = () => { ); setLineChartsData(resp); } catch (error) { - mlMessageBarService.notify.error(error); + getToastNotificationService().displayErrorToast(error); setLineChartsData({}); } setLoadingData(false); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx index e5f5ba48900d9..46f91550f6e32 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx @@ -17,7 +17,7 @@ import { getChartSettings, defaultChartSettings } from '../../../charts/common/s import { MetricSelector } from './metric_selector'; import { SplitFieldSelector } from '../split_field'; import { ChartGrid } from './chart_grid'; -import { mlMessageBarService } from '../../../../../../../components/messagebar'; +import { getToastNotificationService } from '../../../../../../../services/toast_notification_service'; interface Props { setIsValid: (na: boolean) => void; @@ -159,7 +159,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { setLineChartsData(resp); } catch (error) { - mlMessageBarService.notify.error(error); + getToastNotificationService().displayErrorToast(error); setLineChartsData([]); } setLoadingData(false); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx index 06f7092e8ac06..c32cc6ecc445a 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx @@ -15,7 +15,7 @@ import { LineChartData } from '../../../../../common/chart_loader'; import { Field, AggFieldPair } from '../../../../../../../../../common/types/fields'; import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings'; import { ChartGrid } from './chart_grid'; -import { mlMessageBarService } from '../../../../../../../components/messagebar'; +import { getToastNotificationService } from '../../../../../../../services/toast_notification_service'; type DetectorFieldValues = Record; @@ -81,7 +81,7 @@ export const PopulationDetectorsSummary: FC = () => { setLineChartsData(resp); } catch (error) { - mlMessageBarService.notify.error(error); + getToastNotificationService().displayErrorToast(error); setLineChartsData({}); } setLoadingData(false); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx index f04b63f47789e..5844e59225ab5 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx @@ -13,7 +13,7 @@ import { newJobCapsService } from '../../../../../../../services/new_job_capabil import { AggFieldPair } from '../../../../../../../../../common/types/fields'; import { AnomalyChart, CHART_TYPE } from '../../../charts/anomaly_chart'; import { getChartSettings } from '../../../charts/common/settings'; -import { mlMessageBarService } from '../../../../../../../components/messagebar'; +import { getToastNotificationService } from '../../../../../../../services/toast_notification_service'; interface Props { setIsValid: (na: boolean) => void; @@ -93,7 +93,7 @@ export const SingleMetricDetectors: FC = ({ setIsValid }) => { setLineChartData(resp); } } catch (error) { - mlMessageBarService.notify.error(error); + getToastNotificationService().displayErrorToast(error); setLineChartData({}); } setLoadingData(false); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx index 85fb5890307ba..ae019ee1bbf84 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx @@ -11,7 +11,7 @@ import { Results, ModelItem, Anomaly } from '../../../../../common/results_loade import { LineChartData } from '../../../../../common/chart_loader'; import { AnomalyChart, CHART_TYPE } from '../../../charts/anomaly_chart'; import { getChartSettings } from '../../../charts/common/settings'; -import { mlMessageBarService } from '../../../../../../../components/messagebar'; +import { getToastNotificationService } from '../../../../../../../services/toast_notification_service'; const DTR_IDX = 0; @@ -63,7 +63,7 @@ export const SingleMetricDetectorsSummary: FC = () => { setLineChartData(resp); } } catch (error) { - mlMessageBarService.notify.error(error); + getToastNotificationService().displayErrorToast(error); setLineChartData({}); } setLoadingData(false); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/post_save_options/post_save_options.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/post_save_options/post_save_options.tsx index 2e7cc9c413a25..82a023cd1779b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/post_save_options/post_save_options.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/post_save_options/post_save_options.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { JobRunner } from '../../../../../common/job_runner'; import { useMlKibana } from '../../../../../../../contexts/kibana'; -import { getErrorMessage } from '../../../../../../../../../common/util/errors'; +import { extractErrorMessage } from '../../../../../../../../../common/util/errors'; // @ts-ignore import { CreateWatchFlyout } from '../../../../../../jobs_list/components/create_watch_flyout/index'; @@ -70,7 +70,7 @@ export const PostSaveOptions: FC = ({ jobRunner }) => { defaultMessage: `Error starting job`, } ), - text: getErrorMessage(error), + text: extractErrorMessage(error), }); } } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx index 24d7fb9fc2a40..3000ce8449138 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx @@ -22,13 +22,13 @@ import { JobCreatorContext } from '../job_creator_context'; import { JobRunner } from '../../../common/job_runner'; import { mlJobService } from '../../../../../services/job_service'; import { JsonEditorFlyout, EDITOR_MODE } from '../common/json_editor_flyout'; -import { getErrorMessage } from '../../../../../../../common/util/errors'; import { isSingleMetricJobCreator, isAdvancedJobCreator } from '../../../common/job_creator'; import { JobDetails } from './components/job_details'; import { DatafeedDetails } from './components/datafeed_details'; import { DetectorChart } from './components/detector_chart'; import { JobProgress } from './components/job_progress'; import { PostSaveOptions } from './components/post_save_options'; +import { toastNotificationServiceProvider } from '../../../../../services/toast_notification_service'; import { convertToAdvancedJob, resetJob, @@ -72,15 +72,7 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => const jr = await jobCreator.createAndStartJob(); setJobRunner(jr); } catch (error) { - // catch and display all job creation errors - const { toasts } = notifications; - toasts.addDanger({ - title: i18n.translate('xpack.ml.newJob.wizard.summaryStep.createJobError', { - defaultMessage: `Job creation error`, - }), - text: getErrorMessage(error), - }); - setCreatingJob(false); + handleJobCreationError(error); } } @@ -91,18 +83,21 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => await jobCreator.createDatafeed(); advancedStartDatafeed(jobCreator, navigateToPath); } catch (error) { - // catch and display all job creation errors - const { toasts } = notifications; - toasts.addDanger({ - title: i18n.translate('xpack.ml.newJob.wizard.summaryStep.createJobError', { - defaultMessage: `Job creation error`, - }), - text: getErrorMessage(error), - }); - setCreatingJob(false); + handleJobCreationError(error); } } + function handleJobCreationError(error: any) { + const { displayErrorToast } = toastNotificationServiceProvider(notifications.toasts); + displayErrorToast( + error, + i18n.translate('xpack.ml.newJob.wizard.summaryStep.createJobError', { + defaultMessage: `Job creation error`, + }) + ); + setCreatingJob(false); + } + function viewResults() { const url = mlJobService.createResultsUrl( [jobCreator.jobId], diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/validation_step/validation.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/validation_step/validation.tsx index 19b89ffec02ac..3bde32f40eeb5 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/validation_step/validation.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/validation_step/validation.tsx @@ -8,7 +8,7 @@ import React, { Fragment, FC, useContext, useState, useEffect } from 'react'; import { WizardNav } from '../wizard_nav'; import { WIZARD_STEPS, StepProps } from '../step_types'; import { JobCreatorContext } from '../job_creator_context'; -import { mlJobService } from '../../../../../services/job_service'; +import { ml } from '../../../../../services/ml_api_service'; import { ValidateJob } from '../../../../../components/validate_job'; import { JOB_TYPE } from '../../../../../../../common/constants/new_job'; @@ -66,7 +66,7 @@ export const ValidationStep: FC = ({ setCurrentStep, isCurrentStep }) { jobs = []; datafeedIds = {}; - ml.getJobs() .then((resp) => { jobs = resp.jobs; @@ -162,7 +159,6 @@ class JobService { } processBasicJobInfo(this, jobs); this.jobs = jobs; - createJobStats(this.jobs, this.jobStats); resolve({ jobs: this.jobs }); }); }) @@ -176,12 +172,7 @@ class JobService { function error(err) { console.log('jobService error getting list of jobs:', err); - msgs.notify.error( - i18n.translate('xpack.ml.jobService.jobsListCouldNotBeRetrievedErrorMessage', { - defaultMessage: 'Jobs list could not be retrieved', - }) - ); - msgs.notify.error('', err); + getToastNotificationService().displayErrorToast(err); reject({ jobs, err }); } }); @@ -248,7 +239,6 @@ class JobService { } } this.jobs = jobs; - createJobStats(this.jobs, this.jobStats); resolve({ jobs: this.jobs }); }); }) @@ -263,12 +253,7 @@ class JobService { function error(err) { console.log('JobService error getting list of jobs:', err); - msgs.notify.error( - i18n.translate('xpack.ml.jobService.jobsListCouldNotBeRetrievedErrorMessage', { - defaultMessage: 'Jobs list could not be retrieved', - }) - ); - msgs.notify.error('', err); + getToastNotificationService().displayErrorToast(err); reject({ jobs, err }); } }); @@ -280,9 +265,6 @@ class JobService { ml.getDatafeeds(sId) .then((resp) => { - // console.log('loadDatafeeds query response:', resp); - - // make deep copy of datafeeds const datafeeds = resp.datafeeds; // load datafeeds stats @@ -309,12 +291,7 @@ class JobService { function error(err) { console.log('loadDatafeeds error getting list of datafeeds:', err); - msgs.notify.error( - i18n.translate('xpack.ml.jobService.datafeedsListCouldNotBeRetrievedErrorMessage', { - defaultMessage: 'datafeeds list could not be retrieved', - }) - ); - msgs.notify.error('', err); + getToastNotificationService().displayErrorToast(err); reject({ jobs, err }); } }); @@ -415,62 +392,6 @@ class JobService { return tempJob; } - updateJob(jobId, job) { - // return the promise chain - return ml - .updateJob({ jobId, job }) - .then(() => { - return { success: true }; - }) - .catch((err) => { - // TODO - all the functions in here should just return the error and not - // display the toast, as currently both the component and this service display - // errors, so we end up with duplicate toasts. - const toastNotifications = getToastNotifications(); - const toastNotificationService = toastNotificationServiceProvider(toastNotifications); - toastNotificationService.displayErrorToast( - err, - i18n.translate('xpack.ml.jobService.updateJobErrorTitle', { - defaultMessage: 'Could not update job: {jobId}', - values: { jobId }, - }) - ); - - console.error('update job', err); - return { success: false, message: err }; - }); - } - - validateJob(obj) { - // return the promise chain - return ml - .validateJob(obj) - .then((messages) => { - return { success: true, messages }; - }) - .catch((err) => { - const toastNotifications = getToastNotifications(); - const toastNotificationService = toastNotificationServiceProvider(toastNotifications); - toastNotificationService.displayErrorToast( - err, - i18n.translate('xpack.ml.jobService.validateJobErrorTitle', { - defaultMessage: 'Job Validation Error', - }) - ); - - console.log('validate job', err); - return { - success: false, - messages: [ - { - status: 'error', - text: err.message, - }, - ], - }; - }); - } - // find a job based on the id getJob(jobId) { const job = find(jobs, (j) => { @@ -638,25 +559,6 @@ class JobService { }); } - updateDatafeed(datafeedId, datafeedConfig) { - return ml - .updateDatafeed({ datafeedId, datafeedConfig }) - .then((resp) => { - console.log('update datafeed', resp); - return { success: true }; - }) - .catch((err) => { - msgs.notify.error( - i18n.translate('xpack.ml.jobService.couldNotUpdateDatafeedErrorMessage', { - defaultMessage: 'Could not update datafeed: {datafeedId}', - values: { datafeedId }, - }) - ); - console.log('update datafeed', err); - return { success: false, message: err.message }; - }); - } - // start the datafeed for a given job // refresh the job state on start success startDatafeed(datafeedId, jobId, start, end) { @@ -677,49 +579,6 @@ class JobService { }) .catch((err) => { console.log('jobService error starting datafeed:', err); - msgs.notify.error( - i18n.translate('xpack.ml.jobService.couldNotStartDatafeedErrorMessage', { - defaultMessage: 'Could not start datafeed for {jobId}', - values: { jobId }, - }), - err - ); - reject(err); - }); - }); - } - - // stop the datafeed for a given job - // refresh the job state on stop success - stopDatafeed(datafeedId, jobId) { - return new Promise((resolve, reject) => { - ml.stopDatafeed({ - datafeedId, - }) - .then((resp) => { - resolve(resp); - }) - .catch((err) => { - console.log('jobService error stopping datafeed:', err); - const couldNotStopDatafeedErrorMessage = i18n.translate( - 'xpack.ml.jobService.couldNotStopDatafeedErrorMessage', - { - defaultMessage: 'Could not stop datafeed for {jobId}', - values: { jobId }, - } - ); - - if (err.statusCode === 500) { - msgs.notify.error(couldNotStopDatafeedErrorMessage); - msgs.notify.error( - i18n.translate('xpack.ml.jobService.requestMayHaveTimedOutErrorMessage', { - defaultMessage: - 'Request may have timed out and may still be running in the background.', - }) - ); - } else { - msgs.notify.error(couldNotStopDatafeedErrorMessage, err); - } reject(err); }); }); @@ -887,51 +746,6 @@ function processBasicJobInfo(localJobService, jobsList) { return processedJobsList; } -// Loop through the jobs list and create basic stats -// stats are displayed along the top of the Jobs Management page -function createJobStats(jobsList, jobStats) { - jobStats.activeNodes.value = 0; - jobStats.total.value = 0; - jobStats.open.value = 0; - jobStats.closed.value = 0; - jobStats.failed.value = 0; - jobStats.activeDatafeeds.value = 0; - - // object to keep track of nodes being used by jobs - const mlNodes = {}; - let failedJobs = 0; - - each(jobsList, (job) => { - if (job.state === 'opened') { - jobStats.open.value++; - } else if (job.state === 'closed') { - jobStats.closed.value++; - } else if (job.state === 'failed') { - failedJobs++; - } - - if (job.datafeed_config && job.datafeed_config.state === 'started') { - jobStats.activeDatafeeds.value++; - } - - if (job.node && job.node.name) { - mlNodes[job.node.name] = {}; - } - }); - - jobStats.total.value = jobsList.length; - - // // Only show failed jobs if it is non-zero - if (failedJobs) { - jobStats.failed.value = failedJobs; - jobStats.failed.show = true; - } else { - jobStats.failed.show = false; - } - - jobStats.activeNodes.value = Object.keys(mlNodes).length; -} - function createResultsUrlForJobs(jobsList, resultsPage, userTimeRange) { let from = undefined; let to = undefined; diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts index 9d7ce4f3df59b..0deda455df771 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts @@ -62,7 +62,7 @@ export interface BucketSpanEstimatorResponse { name: string; ms: number; error?: boolean; - message?: { msg: string } | string; + message?: string; } export interface GetTimeFieldRangeResponse { diff --git a/x-pack/plugins/ml/public/application/services/toast_notification_service.ts b/x-pack/plugins/ml/public/application/services/toast_notification_service.ts deleted file mode 100644 index 94381ae3f1e51..0000000000000 --- a/x-pack/plugins/ml/public/application/services/toast_notification_service.ts +++ /dev/null @@ -1,88 +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 { ToastInput, ToastOptions, ToastsStart } from 'kibana/public'; -import { ResponseError } from 'kibana/server'; -import { useMemo } from 'react'; -import { useNotifications } from '../contexts/kibana'; -import { - BoomResponse, - extractErrorProperties, - MLCustomHttpResponseOptions, - MLErrorObject, - MLResponseError, -} from '../../../common/util/errors'; - -export type ToastNotificationService = ReturnType; - -export function toastNotificationServiceProvider(toastNotifications: ToastsStart) { - return { - displayDangerToast(toastOrTitle: ToastInput, options?: ToastOptions) { - toastNotifications.addDanger(toastOrTitle, options); - }, - - displaySuccessToast(toastOrTitle: ToastInput, options?: ToastOptions) { - toastNotifications.addSuccess(toastOrTitle, options); - }, - - displayErrorToast(error: any, toastTitle: string) { - const errorObj = this.parseErrorMessage(error); - if (errorObj.fullErrorMessage !== undefined) { - // Provide access to the full error message via the 'See full error' button. - toastNotifications.addError(new Error(errorObj.fullErrorMessage), { - title: toastTitle, - toastMessage: errorObj.message, - }); - } else { - toastNotifications.addDanger( - { - title: toastTitle, - text: errorObj.message, - }, - { toastLifeTimeMs: 30000 } - ); - } - }, - - parseErrorMessage( - error: - | MLCustomHttpResponseOptions - | undefined - | string - | MLResponseError - ): MLErrorObject { - if ( - typeof error === 'object' && - 'response' in error && - typeof error.response === 'string' && - error.statusCode !== undefined - ) { - // MLResponseError which has been received back as part of a 'successful' response - // where the error was passed in a separate property in the response. - const wrapMlResponseError = { - body: error, - statusCode: error.statusCode, - }; - return extractErrorProperties(wrapMlResponseError); - } - - return extractErrorProperties( - error as - | MLCustomHttpResponseOptions - | undefined - | string - ); - }, - }; -} - -/** - * Hook to use {@link ToastNotificationService} in React components. - */ -export function useToastNotificationService(): ToastNotificationService { - const { toasts } = useNotifications(); - return useMemo(() => toastNotificationServiceProvider(toasts), []); -} diff --git a/x-pack/plugins/ml/public/application/components/messagebar/index.ts b/x-pack/plugins/ml/public/application/services/toast_notification_service/index.ts similarity index 58% rename from x-pack/plugins/ml/public/application/components/messagebar/index.ts rename to x-pack/plugins/ml/public/application/services/toast_notification_service/index.ts index 35130d28a890d..1259f3b47d8e0 100644 --- a/x-pack/plugins/ml/public/application/components/messagebar/index.ts +++ b/x-pack/plugins/ml/public/application/services/toast_notification_service/index.ts @@ -4,4 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export { mlMessageBarService } from './messagebar_service'; +export { + ToastNotificationService, + toastNotificationServiceProvider, + useToastNotificationService, + getToastNotificationService, +} from './toast_notification_service'; diff --git a/x-pack/plugins/ml/public/application/services/toast_notification_service/toast_notification_service.ts b/x-pack/plugins/ml/public/application/services/toast_notification_service/toast_notification_service.ts new file mode 100644 index 0000000000000..61e0480313ebe --- /dev/null +++ b/x-pack/plugins/ml/public/application/services/toast_notification_service/toast_notification_service.ts @@ -0,0 +1,58 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { ToastInput, ToastOptions, ToastsStart } from 'kibana/public'; +import { useMemo } from 'react'; +import { getToastNotifications } from '../../util/dependency_cache'; +import { useNotifications } from '../../contexts/kibana'; +import { + ErrorType, + extractErrorProperties, + MLRequestFailure, +} from '../../../../common/util/errors'; + +export type ToastNotificationService = ReturnType; + +export function toastNotificationServiceProvider(toastNotifications: ToastsStart) { + function displayDangerToast(toastOrTitle: ToastInput, options?: ToastOptions) { + toastNotifications.addDanger(toastOrTitle, options); + } + + function displayWarningToast(toastOrTitle: ToastInput, options?: ToastOptions) { + toastNotifications.addWarning(toastOrTitle, options); + } + + function displaySuccessToast(toastOrTitle: ToastInput, options?: ToastOptions) { + toastNotifications.addSuccess(toastOrTitle, options); + } + + function displayErrorToast(error: ErrorType, title?: string) { + const errorObj = extractErrorProperties(error); + toastNotifications.addError(new MLRequestFailure(errorObj, error), { + title: + title ?? + i18n.translate('xpack.ml.toastNotificationService.errorTitle', { + defaultMessage: 'An error has occurred', + }), + }); + } + + return { displayDangerToast, displayWarningToast, displaySuccessToast, displayErrorToast }; +} + +export function getToastNotificationService() { + const toastNotifications = getToastNotifications(); + return toastNotificationServiceProvider(toastNotifications); +} + +/** + * Hook to use {@link ToastNotificationService} in React components. + */ +export function useToastNotificationService(): ToastNotificationService { + const { toasts } = useNotifications(); + return useMemo(() => toastNotificationServiceProvider(toasts), []); +} diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/delete_calendars.js b/x-pack/plugins/ml/public/application/settings/calendars/list/delete_calendars.js index 50777485903d2..e0c7a4db6e898 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/list/delete_calendars.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/list/delete_calendars.js @@ -7,7 +7,7 @@ import { getToastNotifications } from '../../../util/dependency_cache'; import { ml } from '../../../services/ml_api_service'; import { i18n } from '@kbn/i18n'; -import { getErrorMessage } from '../../../../../common/util/errors'; +import { extractErrorMessage } from '../../../../../common/util/errors'; export async function deleteCalendars(calendarsToDelete, callback) { if (calendarsToDelete === undefined || calendarsToDelete.length === 0) { @@ -47,7 +47,7 @@ export async function deleteCalendars(calendarsToDelete, callback) { }, } ), - text: getErrorMessage(error), + text: extractErrorMessage(error), }); } } diff --git a/x-pack/plugins/ml/public/application/util/ml_error.ts b/x-pack/plugins/ml/public/application/util/ml_error.ts deleted file mode 100644 index 2a0280404c189..0000000000000 --- a/x-pack/plugins/ml/public/application/util/ml_error.ts +++ /dev/null @@ -1,22 +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 { KbnError } from '../../../../../../src/plugins/kibana_utils/public'; - -export class MLRequestFailure extends KbnError { - origError: any; - resp: any; - // takes an Error object and and optional response object - // if error is falsy (null) the response object will be used - // notify will show the full expandable stack trace of the response if a response object is used and no error is passed in. - constructor(error: any, resp: any) { - error = error || {}; - super(error.message || JSON.stringify(resp)); - - this.origError = error; - this.resp = typeof resp === 'string' ? JSON.parse(resp) : resp; - } -} diff --git a/x-pack/plugins/ml/server/models/job_service/datafeeds.ts b/x-pack/plugins/ml/server/models/job_service/datafeeds.ts index c0eb1b72825df..62ef9b3621610 100644 --- a/x-pack/plugins/ml/server/models/job_service/datafeeds.ts +++ b/x-pack/plugins/ml/server/models/job_service/datafeeds.ts @@ -118,6 +118,11 @@ export function datafeedsProvider({ asInternalUser }: IScopedClusterClient) { } catch (error) { if (isRequestTimeout(error)) { return fillResultsWithTimeouts(results, datafeedId, datafeedIds, DATAFEED_STATE.STOPPED); + } else { + results[datafeedId] = { + started: false, + error: error.body, + }; } } } diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index 7606420eacefc..4dad12af5e684 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -325,19 +325,20 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat success: false, }; - // Check if analyticsId is valid and get destination index - if (deleteDestIndex || deleteDestIndexPattern) { - try { - const { body } = await client.asInternalUser.ml.getDataFrameAnalytics({ - id: analyticsId, - }); - if (Array.isArray(body.data_frame_analytics) && body.data_frame_analytics.length > 0) { - destinationIndex = body.data_frame_analytics[0].dest.index; - } - } catch (e) { - return response.customError(wrapError(e)); + try { + // Check if analyticsId is valid and get destination index + const { body } = await client.asInternalUser.ml.getDataFrameAnalytics({ + id: analyticsId, + }); + if (Array.isArray(body.data_frame_analytics) && body.data_frame_analytics.length > 0) { + destinationIndex = body.data_frame_analytics[0].dest.index; } + } catch (e) { + // exist early if the job doesn't exist + return response.customError(wrapError(e)); + } + if (deleteDestIndex || deleteDestIndexPattern) { // If user checks box to delete the destinationIndex associated with the job if (destinationIndex && deleteDestIndex) { // Verify if user has privilege to delete the destination index @@ -349,8 +350,8 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat index: destinationIndex, }); destIndexDeleted.success = true; - } catch (deleteIndexError) { - destIndexDeleted.error = wrapError(deleteIndexError); + } catch ({ body }) { + destIndexDeleted.error = body; } } else { return response.forbidden(); @@ -366,7 +367,7 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat } destIndexPatternDeleted.success = true; } catch (deleteDestIndexPatternError) { - destIndexPatternDeleted.error = wrapError(deleteDestIndexPatternError); + destIndexPatternDeleted.error = deleteDestIndexPatternError; } } } @@ -378,11 +379,8 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat id: analyticsId, }); analyticsJobDeleted.success = true; - } catch (deleteDFAError) { - analyticsJobDeleted.error = wrapError(deleteDFAError); - if (analyticsJobDeleted.error.statusCode === 404) { - return response.notFound(); - } + } catch ({ body }) { + analyticsJobDeleted.error = body; } const results = { analyticsJobDeleted, diff --git a/x-pack/plugins/transform/common/utils/errors.ts b/x-pack/plugins/transform/common/utils/errors.ts new file mode 100644 index 0000000000000..0c31d7e1584f0 --- /dev/null +++ b/x-pack/plugins/transform/common/utils/errors.ts @@ -0,0 +1,31 @@ +/* + * 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. + */ + +export interface ErrorResponse { + body: { + statusCode: number; + error: string; + message: string; + attributes?: any; + }; + name: string; +} + +export function isErrorResponse(arg: any): arg is ErrorResponse { + return arg?.body?.error !== undefined && arg?.body?.message !== undefined; +} + +export function getErrorMessage(error: any) { + if (isErrorResponse(error)) { + return `${error.body.error}: ${error.body.message}`; + } + + if (typeof error === 'object' && typeof error.message === 'string') { + return error.message; + } + + return JSON.stringify(error); +} diff --git a/x-pack/plugins/transform/public/__mocks__/shared_imports.ts b/x-pack/plugins/transform/public/__mocks__/shared_imports.ts index e115e086f45b5..f7441fd93f38a 100644 --- a/x-pack/plugins/transform/public/__mocks__/shared_imports.ts +++ b/x-pack/plugins/transform/public/__mocks__/shared_imports.ts @@ -15,7 +15,6 @@ export const useRequest = jest.fn(() => ({ // just passing through the reimports export { - getErrorMessage, getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, multiColumnSortFactory, diff --git a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx index 43c5ae6fad1b1..fdf77c8ebee51 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx @@ -12,7 +12,8 @@ import { DeleteTransformStatus, TransformEndpointRequest, } from '../../../common'; -import { extractErrorMessage, getErrorMessage } from '../../shared_imports'; +import { extractErrorMessage } from '../../shared_imports'; +import { getErrorMessage } from '../../../common/utils/errors'; import { useAppDependencies, useToastNotifications } from '../app_dependencies'; import { REFRESH_TRANSFORM_LIST_STATE, refreshTransformList$, TransformListRow } from '../common'; import { ToastNotificationText } from '../components'; diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts index ad5850f26be2e..946f7991d049d 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -12,7 +12,6 @@ import { getFieldType, getDataGridSchemaFromKibanaFieldType, getFieldsFromKibanaIndexPattern, - getErrorMessage, showDataGridColumnChartErrorMessageToast, useDataGrid, useRenderCellValue, @@ -21,6 +20,7 @@ import { UseIndexDataReturnType, INDEX_STATUS, } from '../../shared_imports'; +import { getErrorMessage } from '../../../common/utils/errors'; import { isDefaultQuery, matchAllQuery, PivotQuery } from '../common'; diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts index a9f34996b9b51..a0e7c5dde494a 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts @@ -18,13 +18,13 @@ import { formatHumanReadableDateTimeSeconds } from '../../shared_imports'; import { getNestedProperty } from '../../../common/utils/object_utils'; import { - getErrorMessage, multiColumnSortFactory, useDataGrid, RenderCellValue, UseIndexDataReturnType, INDEX_STATUS, } from '../../shared_imports'; +import { getErrorMessage } from '../../../common/utils/errors'; import { getPreviewRequestBody, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index 255a245081d5a..2fa1b7c713370 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -32,7 +32,7 @@ import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_reac import { PROGRESS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants'; -import { getErrorMessage } from '../../../../../shared_imports'; +import { getErrorMessage } from '../../../../../../common/utils/errors'; import { getTransformProgress, getDiscoverUrl } from '../../../../common'; import { useApi } from '../../../../hooks/use_api'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index 271fde27f519a..85f4065e8c069 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -16,7 +16,7 @@ import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_reac import { TransformId } from '../../../../../../common'; import { isValidIndexName } from '../../../../../../common/utils/es_utils'; -import { getErrorMessage } from '../../../../../shared_imports'; +import { getErrorMessage } from '../../../../../../common/utils/errors'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { ToastNotificationText } from '../../../../components'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx index 77a7ae25ce887..735a059e57e14 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx @@ -23,7 +23,7 @@ import { EuiTitle, } from '@elastic/eui'; -import { getErrorMessage } from '../../../../../shared_imports'; +import { getErrorMessage } from '../../../../../../common/utils/errors'; import { refreshTransformList$, diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index abbc39dd6c728..196df250b7a3d 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -15,7 +15,6 @@ export { export { getFieldType, - getErrorMessage, extractErrorMessage, formatHumanReadableDateTimeSeconds, getDataGridSchemaFromKibanaFieldType, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9c7799f11ee0d..e1cafa34519f5 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10890,7 +10890,6 @@ "xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage": "データフレーム分析ジョブ{analyticsId}の削除中にエラーが発生しました。", "xpack.ml.dataframe.analyticsList.deleteAnalyticsPrivilegeErrorMessage": "ユーザーはインデックス{indexName}を削除する権限がありません。{error}", "xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage": "データフレーム分析ジョブ{analyticsId}の削除リクエストが受け付けられました。", - "xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexErrorMessage": "ディスティネーションインデックス{destinationIndex}の削除中にエラーが発生しました。{error}", "xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternErrorMessage": "インデックスパターン{destinationIndex}の削除中にエラーが発生しました。{error}", "xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternSuccessMessage": "インデックスパターン{destinationIndex}を削除する要求が確認されました。", "xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexSuccessMessage": "ディスティネーションインデックス{destinationIndex}を削除する要求が確認されました。", @@ -11358,16 +11357,9 @@ "xpack.ml.jobService.activeDatafeedsLabel": "アクティブなデータフィード", "xpack.ml.jobService.activeMLNodesLabel": "アクティブな ML ノード", "xpack.ml.jobService.closedJobsLabel": "ジョブを作成", - "xpack.ml.jobService.couldNotStartDatafeedErrorMessage": "{jobId} のデータフィードを開始できませんでした", - "xpack.ml.jobService.couldNotStopDatafeedErrorMessage": "{jobId} のデータフィードを停止できませんでした", - "xpack.ml.jobService.couldNotUpdateDatafeedErrorMessage": "データフィードを更新できませんでした: {datafeedId}", - "xpack.ml.jobService.datafeedsListCouldNotBeRetrievedErrorMessage": "データフィードリストを取得できませんでした", "xpack.ml.jobService.failedJobsLabel": "失敗したジョブ", - "xpack.ml.jobService.jobsListCouldNotBeRetrievedErrorMessage": "ジョブリストを取得できませんでした", "xpack.ml.jobService.openJobsLabel": "ジョブを開く", - "xpack.ml.jobService.requestMayHaveTimedOutErrorMessage": "リクエストがタイムアウトし、まだバックグラウンドで実行中の可能性があります。", "xpack.ml.jobService.totalJobsLabel": "合計ジョブ数", - "xpack.ml.jobService.updateJobErrorTitle": "ジョブを更新できませんでした: {jobId}", "xpack.ml.jobService.validateJobErrorTitle": "ジョブ検証エラー", "xpack.ml.jobsList.actionExecuteSuccessfullyNotificationMessage": "{successesJobsCount, plural, one{{successJob}} other{# 件のジョブ}} {actionTextPT}成功", "xpack.ml.jobsList.actionFailedNotificationMessage": "{failureId} が {actionText} に失敗しました", @@ -11572,7 +11564,6 @@ "xpack.ml.maxFileSizeSettingsDescription": "ファイルデータビジュアライザーでデータをインポートするときのファイルサイズ上限を設定します。この設定でサポートされている最大値は1 GBです。", "xpack.ml.maxFileSizeSettingsError": "200 MB、1 GBなどの有効なデータサイズにしてください。", "xpack.ml.maxFileSizeSettingsName": "ファイルデータビジュアライザーの最大ファイルアップロードサイズ", - "xpack.ml.messagebarService.errorTitle": "エラーが発生しました", "xpack.ml.models.jobService.allOtherRequestsCancelledDescription": " 他のすべてのリクエストはキャンセルされました。", "xpack.ml.models.jobService.categorization.messages.failureToGetTokens": "フィールド値の例のサンプルをトークン化することができませんでした。{message}", "xpack.ml.models.jobService.categorization.messages.insufficientPrivileges": "権限が不十分なため、フィールド値の例のトークン化を実行できませんでした。そのため、フィールド値を確認し、カテゴリー分けジョブでの使用が適当かを確認することができません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8a6bc996d9bdf..b0de74ab2150a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10896,7 +10896,6 @@ "xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage": "删除数据帧分析作业 {analyticsId} 时发生错误", "xpack.ml.dataframe.analyticsList.deleteAnalyticsPrivilegeErrorMessage": "用户无权删除索引 {indexName}:{error}", "xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage": "删除的数据帧分析作业 {analyticsId} 的请求已确认。", - "xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexErrorMessage": "删除目标索引 {destinationIndex} 时发生错误:{error}", "xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternErrorMessage": "删除索引模式 {destinationIndex} 时发生错误:{error}", "xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternSuccessMessage": "删除索引模式 {destinationIndex} 的请求已确认。", "xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexSuccessMessage": "删除目标索引 {destinationIndex} 的请求已确认。", @@ -11365,16 +11364,9 @@ "xpack.ml.jobService.activeDatafeedsLabel": "活动数据馈送", "xpack.ml.jobService.activeMLNodesLabel": "活动 ML 节点", "xpack.ml.jobService.closedJobsLabel": "已关闭的作业", - "xpack.ml.jobService.couldNotStartDatafeedErrorMessage": "无法开始 {jobId} 的数据馈送", - "xpack.ml.jobService.couldNotStopDatafeedErrorMessage": "无法停止 {jobId} 的数据馈送", - "xpack.ml.jobService.couldNotUpdateDatafeedErrorMessage": "无法更新数据馈送:{datafeedId}", - "xpack.ml.jobService.datafeedsListCouldNotBeRetrievedErrorMessage": "无法检索数据馈送列表", "xpack.ml.jobService.failedJobsLabel": "失败的作业", - "xpack.ml.jobService.jobsListCouldNotBeRetrievedErrorMessage": "无法检索作业列表", "xpack.ml.jobService.openJobsLabel": "打开的作业", - "xpack.ml.jobService.requestMayHaveTimedOutErrorMessage": "请求可能已超时,并可能仍在后台运行。", "xpack.ml.jobService.totalJobsLabel": "总计作业数", - "xpack.ml.jobService.updateJobErrorTitle": "无法更新作业:{jobId}", "xpack.ml.jobService.validateJobErrorTitle": "作业验证错误", "xpack.ml.jobsList.actionExecuteSuccessfullyNotificationMessage": "{successesJobsCount, plural, one{{successJob}} other{# 个作业}}{actionTextPT}已成功", "xpack.ml.jobsList.actionFailedNotificationMessage": "{failureId} 未能{actionText}", @@ -11579,7 +11571,6 @@ "xpack.ml.maxFileSizeSettingsDescription": "设置在文件数据可视化工具中导入数据时的文件大小限制。此设置支持的最高值为 1GB。", "xpack.ml.maxFileSizeSettingsError": "应为有效的数据大小。如 200MB、1GB", "xpack.ml.maxFileSizeSettingsName": "文件数据可视化工具最大文件上传大小", - "xpack.ml.messagebarService.errorTitle": "发生了错误", "xpack.ml.models.jobService.allOtherRequestsCancelledDescription": " 所有其他请求已取消。", "xpack.ml.models.jobService.categorization.messages.failureToGetTokens": "无法对示例字段值样本进行分词。{message}", "xpack.ml.models.jobService.categorization.messages.insufficientPrivileges": "由于权限不足,无法对字段值示例执行分词。因此,无法检查字段值是否适合用于归类作业。", diff --git a/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts b/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts index c6043b7a282d4..53a9d9e790d67 100644 --- a/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts +++ b/x-pack/test/api_integration/apis/ml/data_frame_analytics/delete.ts @@ -120,7 +120,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(404); expect(body.error).to.eql('Not Found'); - expect(body.message).to.eql('Not Found'); + expect(body.message).to.eql('resource_not_found_exception'); }); describe('with deleteDestIndex setting', function () {