[ML] Improving client side error handling (#76743)

* [ML] Improving client side error handling

* adding stacktrace to request errors

* copying error parsing to transforms

* update snapshot

* fixing jest tests

* adding test and removing debug log output

* updating translations

* rewriting error extracting code

* fixing tests

* removing message bar service

* removing test code

* updating translations

* combining job creation error handling

* refactoring error files

* updating test

* fixing bug in DFA deletion

* improving mml warning

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
James Gowdy 2020-09-11 08:53:34 +01:00 committed by GitHub
parent 22b4e40ea0
commit ea8086b3f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 591 additions and 939 deletions

View file

@ -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<ResponseError>;
error?: EsErrorBody | Boom;
}
export type IndexName = string;

View file

@ -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<MLResponseError> = {
body: {
message: {
msg: testMsg,
},
},
statusCode: 404,
};
expect(extractErrorMessage(bodyWithNestedErrorMsg)).toBe(testMsg);
const bodyWithStringMsg: MLCustomHttpResponseOptions<MLResponseError> = {
body: {
msg: testMsg,
statusCode: 404,
response: `{"error":{"reason":"${testMsg}"}}`,
},
statusCode: 404,
};
expect(extractErrorMessage(bodyWithStringMsg)).toBe(testMsg);
const bodyWithStringMessage: MLCustomHttpResponseOptions<ResponseError> = {
body: {
message: testMsg,
},
statusCode: 404,
};
expect(extractErrorMessage(bodyWithStringMessage)).toBe(testMsg);
const bodyWithString: MLCustomHttpResponseOptions<ResponseError> = {
body: testMsg,
statusCode: 404,
};
expect(extractErrorMessage(bodyWithString)).toBe(testMsg);
const bodyWithError: MLCustomHttpResponseOptions<ResponseError> = {
body: new Error(testMsg),
statusCode: 404,
};
expect(extractErrorMessage(bodyWithError)).toBe(testMsg);
const bodyWithBoomError: MLCustomHttpResponseOptions<BoomResponse> = {
statusCode: 404,
body: {
data: [],
isBoom: true,
isServer: false,
output: {
statusCode: 404,
payload: {
statusCode: 404,
error: testMsg,
message: testMsg,
},
headers: {},
},
},
};
expect(extractErrorMessage(bodyWithBoomError)).toBe(testMsg);
});
});
});

View file

@ -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<MLResponseError | ResponseError | BoomResponse>
| 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<MLResponseError | ResponseError | BoomResponse>
| 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;
};

View file

@ -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<MLResponseError> = {
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<MLResponseError> = {
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<any> = {
message: '',
reformat: () => '',
name: '',
data: [],
isBoom: true,
isServer: false,
output: {
statusCode: 404,
payload: {
statusCode: 404,
error: testMsg,
message: testMsg,
},
headers: {},
},
};
expect(extractErrorMessage(boomError)).toBe(testMsg);
});
});
});

View file

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

View file

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

View file

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

View file

@ -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<T> extends HttpFetchError {
body: T;
}
export type ErrorType = MLHttpFetchError<MLResponseError> | 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;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {}}
/>
</EuiButton>
<Modal
close={[Function]}
title={
<FormattedMessage
defaultMessage="Validate job {title}"
id="xpack.ml.validateJob.modal.validateJobTitle"
values={
Object {
"title": "test-id",
}
}
/>
}
>
<MessageList
idFilterList={Array []}
messages={
Array [
Object {
"fieldName": "airline",
"id": "over_field_low_cardinality",
"status": "warning",
"text": "Cardinality of over_field \\"airline\\" is low and therefore less suitable for population analysis.",
"url": "https://www.elastic.co/blog/sizing-machine-learning-with-elasticsearch",
},
]
}
/>
<EuiText>
<FormattedMessage
defaultMessage="Job validation performs certain checks against job configurations and underlying source data and provides specific advice on how to adjust settings that are more likely to produce insightful results."
id="xpack.ml.validateJob.modal.jobValidationDescriptionText"
values={Object {}}
/>
</EuiText>
<EuiText>
<FormattedMessage
defaultMessage="For more information, see {mlJobTipsLink}."
id="xpack.ml.validateJob.modal.linkToJobTipsText"
values={
Object {
"mlJobTipsLink": <EuiLink
href="https://www.elastic.co/guide/en/machine-learning/jest-metadata-mock-branch/create-jobs.html#job-tips"
target="_blank"
>
<FormattedMessage
defaultMessage="Machine Learning Job Tips"
id="xpack.ml.validateJob.modal.linkToJobTipsText.mlJobTipsLinkText"
values={Object {}}
/>
</EuiLink>,
}
}
/>
</EuiText>
</Modal>
</div>
</Fragment>
`;
@ -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 {}}
/>
</EuiButton>
<Modal
close={[Function]}
title={
<FormattedMessage
defaultMessage="Validate job {title}"
id="xpack.ml.validateJob.modal.validateJobTitle"
values={
Object {
"title": "test-id",
}
}
/>
}
>
<MessageList
idFilterList={Array []}
messages={Array []}
/>
<EuiText>
<FormattedMessage
defaultMessage="Job validation performs certain checks against job configurations and underlying source data and provides specific advice on how to adjust settings that are more likely to produce insightful results."
id="xpack.ml.validateJob.modal.jobValidationDescriptionText"
values={Object {}}
/>
</EuiText>
<EuiText>
<FormattedMessage
defaultMessage="For more information, see {mlJobTipsLink}."
id="xpack.ml.validateJob.modal.linkToJobTipsText"
values={
Object {
"mlJobTipsLink": <EuiLink
href="https://www.elastic.co/guide/en/machine-learning/jest-metadata-mock-branch/create-jobs.html#job-tips"
target="_blank"
>
<FormattedMessage
defaultMessage="Machine Learning Job Tips"
id="xpack.ml.validateJob.modal.linkToJobTipsText.mlJobTipsLinkText"
values={Object {}}
/>
</EuiLink>,
}
}
/>
</EuiText>
</Modal>
</div>
</Fragment>
`;

View file

@ -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[];

View file

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

View file

@ -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 = <ValidateJob getJobConfig={getJobConfig} mlJobService={mlJobService} />;
const component = <ValidateJob getJobConfig={getJobConfig} ml={ml} kibana={kibana} />;
const wrapper = shallowWithIntl(component);

View file

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

View file

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

View file

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

View file

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

View file

@ -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();

View file

@ -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',
{

View file

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

View file

@ -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() {

View file

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

View file

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

View file

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

View file

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

View file

@ -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<CalculatePayload>();
const error$ = new Subject<ErrorResponse['body']>();
const error$ = new Subject<MLHttpFetchError<MLResponseError>>();
return {
get error$(): Observable<ErrorResponse['body']> {
get error$(): Observable<MLHttpFetchError<MLResponseError>> {
return error$.asObservable();
},
get updates$(): Observable<string> {
@ -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,
});
})
);

View file

@ -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();

View file

@ -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<Props> = ({ setIsValid }) => {
setFieldExamples(null);
setValidationChecks([]);
setOverallValidStatus(CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID);
mlMessageBarService.notify.error(error);
getToastNotificationService().displayErrorToast(error);
}
} else {
setFieldExamples(null);

View file

@ -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<Props> = ({ setIsValid }) => {
.loadFieldExampleValues(splitField)
.then(setFieldValues)
.catch((error) => {
mlMessageBarService.notify.error(error);
getToastNotificationService().displayErrorToast(error);
});
} else {
setFieldValues([]);
@ -138,7 +138,7 @@ export const MultiMetricDetectors: FC<Props> = ({ setIsValid }) => {
);
setLineChartsData(resp);
} catch (error) {
mlMessageBarService.notify.error(error);
getToastNotificationService().displayErrorToast(error);
setLineChartsData([]);
}
setLoadingData(false);

View file

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

View file

@ -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<Props> = ({ setIsValid }) => {
setLineChartsData(resp);
} catch (error) {
mlMessageBarService.notify.error(error);
getToastNotificationService().displayErrorToast(error);
setLineChartsData([]);
}
setLoadingData(false);

View file

@ -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<number, string[]>;
@ -81,7 +81,7 @@ export const PopulationDetectorsSummary: FC = () => {
setLineChartsData(resp);
} catch (error) {
mlMessageBarService.notify.error(error);
getToastNotificationService().displayErrorToast(error);
setLineChartsData({});
}
setLoadingData(false);

View file

@ -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<Props> = ({ setIsValid }) => {
setLineChartData(resp);
}
} catch (error) {
mlMessageBarService.notify.error(error);
getToastNotificationService().displayErrorToast(error);
setLineChartData({});
}
setLoadingData(false);

View file

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

View file

@ -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<Props> = ({ jobRunner }) => {
defaultMessage: `Error starting job`,
}
),
text: getErrorMessage(error),
text: extractErrorMessage(error),
});
}
}

View file

@ -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<StepProps> = ({ 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<StepProps> = ({ 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],

View file

@ -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<StepProps> = ({ setCurrentStep, isCurrentStep })
<ValidateJob
getJobConfig={getJobConfig}
getDuration={getDuration}
mlJobService={mlJobService}
ml={ml}
embedded={true}
setIsValid={setIsValid}
idFilterList={idFilterList}

View file

@ -14,15 +14,13 @@ import { i18n } from '@kbn/i18n';
import { ml } from './ml_api_service';
import { mlMessageBarService } from '../components/messagebar';
import { getToastNotifications } from '../util/dependency_cache';
import { getToastNotificationService } from '../services/toast_notification_service';
import { isWebUrl } from '../util/url_utils';
import { ML_DATA_PREVIEW_COUNT } from '../../../common/util/job_utils';
import { TIME_FORMAT } from '../../../common/constants/time_format';
import { parseInterval } from '../../../common/util/parse_interval';
import { toastNotificationServiceProvider } from '../services/toast_notification_service';
import { validateTimeRange } from '../util/date_utils';
const msgs = mlMessageBarService;
let jobs = [];
let datafeedIds = {};
@ -119,7 +117,6 @@ class JobService {
return new Promise((resolve, reject) => {
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;

View file

@ -62,7 +62,7 @@ export interface BucketSpanEstimatorResponse {
name: string;
ms: number;
error?: boolean;
message?: { msg: string } | string;
message?: string;
}
export interface GetTimeFieldRangeResponse {

View file

@ -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<typeof toastNotificationServiceProvider>;
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<MLResponseError | ResponseError | BoomResponse>
| 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<MLResponseError | ResponseError | BoomResponse>
| undefined
| string
);
},
};
}
/**
* Hook to use {@link ToastNotificationService} in React components.
*/
export function useToastNotificationService(): ToastNotificationService {
const { toasts } = useNotifications();
return useMemo(() => toastNotificationServiceProvider(toasts), []);
}

View file

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

View file

@ -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<typeof toastNotificationServiceProvider>;
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), []);
}

View file

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

View file

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

View file

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

View file

@ -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,

View file

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

View file

@ -15,7 +15,6 @@ export const useRequest = jest.fn(() => ({
// just passing through the reimports
export {
getErrorMessage,
getDataGridSchemaFromKibanaFieldType,
getFieldsFromKibanaIndexPattern,
multiColumnSortFactory,

View file

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

View file

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

View file

@ -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,

View file

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

View file

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

View file

@ -23,7 +23,7 @@ import {
EuiTitle,
} from '@elastic/eui';
import { getErrorMessage } from '../../../../../shared_imports';
import { getErrorMessage } from '../../../../../../common/utils/errors';
import {
refreshTransformList$,

View file

@ -15,7 +15,6 @@ export {
export {
getFieldType,
getErrorMessage,
extractErrorMessage,
formatHumanReadableDateTimeSeconds,
getDataGridSchemaFromKibanaFieldType,

View file

@ -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": "権限が不十分なため、フィールド値の例のトークン化を実行できませんでした。そのため、フィールド値を確認し、カテゴリー分けジョブでの使用が適当かを確認することができません。",

View file

@ -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": "由于权限不足,无法对字段值示例执行分词。因此,无法检查字段值是否适合用于归类作业。",

View file

@ -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 () {