mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[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:
parent
22b4e40ea0
commit
ea8086b3f2
61 changed files with 591 additions and 939 deletions
|
@ -4,11 +4,12 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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 {
|
export interface DeleteDataFrameAnalyticsWithIndexStatus {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: CustomHttpResponseOptions<ResponseError>;
|
error?: EsErrorBody | Boom;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IndexName = string;
|
export type IndexName = string;
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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;
|
|
||||||
};
|
|
99
x-pack/plugins/ml/common/util/errors/errors.test.ts
Normal file
99
x-pack/plugins/ml/common/util/errors/errors.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
20
x-pack/plugins/ml/common/util/errors/index.ts
Normal file
20
x-pack/plugins/ml/common/util/errors/index.ts
Normal 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';
|
83
x-pack/plugins/ml/common/util/errors/process_errors.ts
Normal file
83
x-pack/plugins/ml/common/util/errors/process_errors.ts
Normal 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;
|
||||||
|
};
|
26
x-pack/plugins/ml/common/util/errors/request_error.ts
Normal file
26
x-pack/plugins/ml/common/util/errors/request_error.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
x-pack/plugins/ml/common/util/errors/types.ts
Normal file
60
x-pack/plugins/ml/common/util/errors/types.ts
Normal 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;
|
||||||
|
}
|
|
@ -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;
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EuiButton,
|
EuiButton,
|
||||||
|
@ -51,8 +53,7 @@ import { getPartitioningFieldNames } from '../../../../common/util/job_utils';
|
||||||
import { withKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
import { withKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||||
import { mlJobService } from '../../services/job_service';
|
import { mlJobService } from '../../services/job_service';
|
||||||
import { ml } from '../../services/ml_api_service';
|
import { ml } from '../../services/ml_api_service';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { extractErrorMessage } from '../../../../common/util/errors';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
|
||||||
|
|
||||||
class RuleEditorFlyoutUI extends Component {
|
class RuleEditorFlyoutUI extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -431,8 +432,8 @@ class RuleEditorFlyoutUI extends Component {
|
||||||
values: { jobId },
|
values: { jobId },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (error.message) {
|
if (error.error) {
|
||||||
errorMessage += ` : ${error.message}`;
|
errorMessage += ` : ${extractErrorMessage(error.error)}`;
|
||||||
}
|
}
|
||||||
toasts.addDanger(errorMessage);
|
toasts.addDanger(errorMessage);
|
||||||
});
|
});
|
||||||
|
|
|
@ -146,22 +146,17 @@ export function updateJobRules(job, detectorIndex, rules) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
mlJobService
|
ml.updateJob({ jobId: jobId, job: jobData })
|
||||||
.updateJob(jobId, jobData)
|
.then(() => {
|
||||||
.then((resp) => {
|
// Refresh the job data in the job service before resolving.
|
||||||
if (resp.success) {
|
mlJobService
|
||||||
// Refresh the job data in the job service before resolving.
|
.refreshJob(jobId)
|
||||||
mlJobService
|
.then(() => {
|
||||||
.refreshJob(jobId)
|
resolve({ success: true });
|
||||||
.then(() => {
|
})
|
||||||
resolve({ success: true });
|
.catch((refreshResp) => {
|
||||||
})
|
reject(refreshResp);
|
||||||
.catch((refreshResp) => {
|
});
|
||||||
reject(refreshResp);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
reject(resp);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch((resp) => {
|
.catch((resp) => {
|
||||||
reject(resp);
|
reject(resp);
|
||||||
|
|
|
@ -8,7 +8,7 @@ exports[`ValidateJob renders button and modal with a message 1`] = `
|
||||||
iconSide="right"
|
iconSide="right"
|
||||||
iconType="questionInCircle"
|
iconType="questionInCircle"
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
isLoading={false}
|
isLoading={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
size="s"
|
size="s"
|
||||||
>
|
>
|
||||||
|
@ -18,62 +18,6 @@ exports[`ValidateJob renders button and modal with a message 1`] = `
|
||||||
values={Object {}}
|
values={Object {}}
|
||||||
/>
|
/>
|
||||||
</EuiButton>
|
</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>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
@ -108,7 +52,7 @@ exports[`ValidateJob renders the button and modal with a success message 1`] = `
|
||||||
iconSide="right"
|
iconSide="right"
|
||||||
iconType="questionInCircle"
|
iconType="questionInCircle"
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
isLoading={false}
|
isLoading={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
size="s"
|
size="s"
|
||||||
>
|
>
|
||||||
|
@ -118,52 +62,6 @@ exports[`ValidateJob renders the button and modal with a success message 1`] = `
|
||||||
values={Object {}}
|
values={Object {}}
|
||||||
/>
|
/>
|
||||||
</EuiButton>
|
</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>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { FC } from 'react';
|
||||||
declare const ValidateJob: FC<{
|
declare const ValidateJob: FC<{
|
||||||
getJobConfig: any;
|
getJobConfig: any;
|
||||||
getDuration: any;
|
getDuration: any;
|
||||||
mlJobService: any;
|
ml: any;
|
||||||
embedded?: boolean;
|
embedded?: boolean;
|
||||||
setIsValid?: (valid: boolean) => void;
|
setIsValid?: (valid: boolean) => void;
|
||||||
idFilterList?: string[];
|
idFilterList?: string[];
|
||||||
|
|
|
@ -32,6 +32,8 @@ import { getDocLinks } from '../../util/dependency_cache';
|
||||||
|
|
||||||
import { VALIDATION_STATUS } from '../../../../common/constants/validation';
|
import { VALIDATION_STATUS } from '../../../../common/constants/validation';
|
||||||
import { getMostSevereMessageStatus } from '../../../../common/util/validation_utils';
|
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 defaultIconType = 'questionInCircle';
|
||||||
const getDefaultState = () => ({
|
const getDefaultState = () => ({
|
||||||
|
@ -182,7 +184,7 @@ Modal.propType = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ValidateJob extends Component {
|
export class ValidateJobUI extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = getDefaultState();
|
this.state = getDefaultState();
|
||||||
|
@ -209,25 +211,40 @@ export class ValidateJob extends Component {
|
||||||
if (typeof job === 'object') {
|
if (typeof job === 'object') {
|
||||||
let shouldShowLoadingIndicator = true;
|
let shouldShowLoadingIndicator = true;
|
||||||
|
|
||||||
this.props.mlJobService.validateJob({ duration, fields, job }).then((data) => {
|
this.props.ml
|
||||||
shouldShowLoadingIndicator = false;
|
.validateJob({ duration, fields, job })
|
||||||
this.setState({
|
.then((messages) => {
|
||||||
...this.state,
|
shouldShowLoadingIndicator = false;
|
||||||
ui: {
|
this.setState({
|
||||||
...this.state.ui,
|
...this.state,
|
||||||
iconType: statusToEuiIconType(getMostSevereMessageStatus(data.messages)),
|
ui: {
|
||||||
isLoading: false,
|
...this.state.ui,
|
||||||
isModalVisible: true,
|
iconType: statusToEuiIconType(getMostSevereMessageStatus(messages)),
|
||||||
},
|
isLoading: false,
|
||||||
data,
|
isModalVisible: true,
|
||||||
title: job.job_id,
|
},
|
||||||
});
|
data: {
|
||||||
if (typeof this.props.setIsValid === 'function') {
|
messages,
|
||||||
this.props.setIsValid(
|
success: true,
|
||||||
data.messages.some((m) => m.status === VALIDATION_STATUS.ERROR) === false
|
},
|
||||||
|
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
|
// wait for 250ms before triggering the loading indicator
|
||||||
// to avoid flickering when there's a loading time below
|
// 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,
|
fields: PropTypes.object,
|
||||||
fill: PropTypes.bool,
|
fill: PropTypes.bool,
|
||||||
getDuration: PropTypes.func,
|
getDuration: PropTypes.func,
|
||||||
getJobConfig: PropTypes.func.isRequired,
|
getJobConfig: PropTypes.func.isRequired,
|
||||||
isCurrentJobConfig: PropTypes.bool,
|
isCurrentJobConfig: PropTypes.bool,
|
||||||
isDisabled: PropTypes.bool,
|
isDisabled: PropTypes.bool,
|
||||||
mlJobService: PropTypes.object.isRequired,
|
ml: PropTypes.object.isRequired,
|
||||||
embedded: PropTypes.bool,
|
embedded: PropTypes.bool,
|
||||||
setIsValid: PropTypes.func,
|
setIsValid: PropTypes.func,
|
||||||
idFilterList: PropTypes.array,
|
idFilterList: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ValidateJob = withKibana(ValidateJobUI);
|
||||||
|
|
|
@ -16,6 +16,12 @@ jest.mock('../../util/dependency_cache', () => ({
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../../../../../src/plugins/kibana_react/public', () => ({
|
||||||
|
withKibana: (comp) => {
|
||||||
|
return comp;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const job = {
|
const job = {
|
||||||
job_id: 'test-id',
|
job_id: 'test-id',
|
||||||
};
|
};
|
||||||
|
@ -25,11 +31,16 @@ const getJobConfig = () => job;
|
||||||
function prepareTest(messages) {
|
function prepareTest(messages) {
|
||||||
const p = Promise.resolve(messages);
|
const p = Promise.resolve(messages);
|
||||||
|
|
||||||
const mlJobService = {
|
const ml = {
|
||||||
validateJob: () => p,
|
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);
|
const wrapper = shallowWithIntl(component);
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { distinctUntilChanged, filter } from 'rxjs/operators';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { ml } from '../../services/ml_api_service';
|
import { ml } from '../../services/ml_api_service';
|
||||||
import { Dictionary } from '../../../../common/types/common';
|
import { Dictionary } from '../../../../common/types/common';
|
||||||
import { getErrorMessage } from '../../../../common/util/errors';
|
import { extractErrorMessage } from '../../../../common/util/errors';
|
||||||
import { SavedSearchQuery } from '../../contexts/ml';
|
import { SavedSearchQuery } from '../../contexts/ml';
|
||||||
import {
|
import {
|
||||||
AnalysisConfig,
|
AnalysisConfig,
|
||||||
|
@ -486,7 +486,7 @@ export const loadEvalData = async ({
|
||||||
results.eval = evalResult;
|
results.eval = evalResult;
|
||||||
return results;
|
return results;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
results.error = getErrorMessage(e);
|
results.error = extractErrorMessage(e);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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 { EsSorting, SearchResponse7, UseDataGridReturnType } from '../../components/data_grid';
|
||||||
import { ml } from '../../services/ml_api_service';
|
import { ml } from '../../services/ml_api_service';
|
||||||
|
@ -62,7 +62,7 @@ export const getIndexData = async (
|
||||||
setTableItems(docs);
|
setTableItems(docs);
|
||||||
setStatus(INDEX_STATUS.LOADED);
|
setStatus(INDEX_STATUS.LOADED);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setErrorMessage(getErrorMessage(e));
|
setErrorMessage(extractErrorMessage(e));
|
||||||
setStatus(INDEX_STATUS.ERROR);
|
setStatus(INDEX_STATUS.ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { IndexPattern } from '../../../../../../../src/plugins/data/public';
|
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 { getIndexPatternIdFromName } from '../../util/index_utils';
|
||||||
import { ml } from '../../services/ml_api_service';
|
import { ml } from '../../services/ml_api_service';
|
||||||
|
@ -83,12 +83,12 @@ export const useResultsViewConfig = (jobId: string) => {
|
||||||
setIsLoadingJobConfig(false);
|
setIsLoadingJobConfig(false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setJobCapsServiceErrorMessage(getErrorMessage(e));
|
setJobCapsServiceErrorMessage(extractErrorMessage(e));
|
||||||
setIsLoadingJobConfig(false);
|
setIsLoadingJobConfig(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setJobConfigErrorMessage(getErrorMessage(e));
|
setJobConfigErrorMessage(extractErrorMessage(e));
|
||||||
setIsLoadingJobConfig(false);
|
setIsLoadingJobConfig(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -25,7 +25,7 @@ import {
|
||||||
SearchResponse7,
|
SearchResponse7,
|
||||||
UseIndexDataReturnType,
|
UseIndexDataReturnType,
|
||||||
} from '../../../../components/data_grid';
|
} from '../../../../components/data_grid';
|
||||||
import { getErrorMessage } from '../../../../../../common/util/errors';
|
import { extractErrorMessage } from '../../../../../../common/util/errors';
|
||||||
import { INDEX_STATUS } from '../../../common/analytics';
|
import { INDEX_STATUS } from '../../../common/analytics';
|
||||||
import { ml } from '../../../../services/ml_api_service';
|
import { ml } from '../../../../services/ml_api_service';
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ export const useIndexData = (
|
||||||
setTableItems(docs);
|
setTableItems(docs);
|
||||||
setStatus(INDEX_STATUS.LOADED);
|
setStatus(INDEX_STATUS.LOADED);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setErrorMessage(getErrorMessage(e));
|
setErrorMessage(extractErrorMessage(e));
|
||||||
setStatus(INDEX_STATUS.ERROR);
|
setStatus(INDEX_STATUS.ERROR);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { MlContext } from '../../../../../contexts/ml';
|
||||||
import { kibanaContextValueMock } from '../../../../../contexts/ml/__mocks__/kibana_context_value';
|
import { kibanaContextValueMock } from '../../../../../contexts/ml/__mocks__/kibana_context_value';
|
||||||
|
|
||||||
import { useCreateAnalyticsForm } from './use_create_analytics_form';
|
import { useCreateAnalyticsForm } from './use_create_analytics_form';
|
||||||
import { getErrorMessage } from '../../../../../../../common/util/errors';
|
|
||||||
|
|
||||||
const getMountedHook = () =>
|
const getMountedHook = () =>
|
||||||
mountHook(
|
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()', () => {
|
describe('useCreateAnalyticsForm()', () => {
|
||||||
test('initialization', () => {
|
test('initialization', () => {
|
||||||
const { getLastHookValue } = getMountedHook();
|
const { getLastHookValue } = getMountedHook();
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { useReducer } from 'react';
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
import { getErrorMessage } from '../../../../../../../common/util/errors';
|
import { extractErrorMessage } from '../../../../../../../common/util/errors';
|
||||||
import { DeepReadonly } from '../../../../../../../common/types/common';
|
import { DeepReadonly } from '../../../../../../../common/types/common';
|
||||||
import { ml } from '../../../../../services/ml_api_service';
|
import { ml } from '../../../../../services/ml_api_service';
|
||||||
import { useMlContext } from '../../../../../contexts/ml';
|
import { useMlContext } from '../../../../../contexts/ml';
|
||||||
|
@ -115,7 +115,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
|
||||||
refresh();
|
refresh();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addRequestMessage({
|
addRequestMessage({
|
||||||
error: getErrorMessage(e),
|
error: extractErrorMessage(e),
|
||||||
message: i18n.translate(
|
message: i18n.translate(
|
||||||
'xpack.ml.dataframe.analytics.create.errorCreatingDataFrameAnalyticsJob',
|
'xpack.ml.dataframe.analytics.create.errorCreatingDataFrameAnalyticsJob',
|
||||||
{
|
{
|
||||||
|
@ -178,7 +178,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addRequestMessage({
|
addRequestMessage({
|
||||||
error: getErrorMessage(e),
|
error: extractErrorMessage(e),
|
||||||
message: i18n.translate(
|
message: i18n.translate(
|
||||||
'xpack.ml.dataframe.analytics.create.createIndexPatternErrorMessage',
|
'xpack.ml.dataframe.analytics.create.createIndexPatternErrorMessage',
|
||||||
{
|
{
|
||||||
|
@ -199,7 +199,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addRequestMessage({
|
addRequestMessage({
|
||||||
error: getErrorMessage(e),
|
error: extractErrorMessage(e),
|
||||||
message: i18n.translate(
|
message: i18n.translate(
|
||||||
'xpack.ml.dataframe.analytics.create.errorGettingDataFrameAnalyticsList',
|
'xpack.ml.dataframe.analytics.create.errorGettingDataFrameAnalyticsList',
|
||||||
{
|
{
|
||||||
|
@ -225,7 +225,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addRequestMessage({
|
addRequestMessage({
|
||||||
error: getErrorMessage(e),
|
error: extractErrorMessage(e),
|
||||||
message: i18n.translate(
|
message: i18n.translate(
|
||||||
'xpack.ml.dataframe.analytics.create.errorGettingIndexPatternTitles',
|
'xpack.ml.dataframe.analytics.create.errorGettingIndexPatternTitles',
|
||||||
{
|
{
|
||||||
|
@ -260,7 +260,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
|
||||||
refresh();
|
refresh();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addRequestMessage({
|
addRequestMessage({
|
||||||
error: getErrorMessage(e),
|
error: extractErrorMessage(e),
|
||||||
message: i18n.translate(
|
message: i18n.translate(
|
||||||
'xpack.ml.dataframe.analytics.create.errorStartingDataFrameAnalyticsJob',
|
'xpack.ml.dataframe.analytics.create.errorStartingDataFrameAnalyticsJob',
|
||||||
{
|
{
|
||||||
|
|
|
@ -85,12 +85,11 @@ export const deleteAnalyticsAndDestIndex = async (
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (status.destIndexDeleted?.error) {
|
if (status.destIndexDeleted?.error) {
|
||||||
const error = extractErrorMessage(status.destIndexDeleted.error);
|
toastNotificationService.displayErrorToast(
|
||||||
toastNotificationService.displayDangerToast(
|
status.destIndexDeleted.error,
|
||||||
i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexErrorMessage', {
|
i18n.translate('xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexErrorMessage', {
|
||||||
defaultMessage:
|
defaultMessage: 'An error occurred deleting destination index {destinationIndex}',
|
||||||
'An error occurred deleting destination index {destinationIndex}: {error}',
|
values: { destinationIndex },
|
||||||
values: { destinationIndex, error },
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@ import mockAnomalyRecord from './__mocks__/mock_anomaly_record.json';
|
||||||
import mockDetectorsByJob from './__mocks__/mock_detectors_by_job.json';
|
import mockDetectorsByJob from './__mocks__/mock_detectors_by_job.json';
|
||||||
import mockJobConfig from './__mocks__/mock_job_config.json';
|
import mockJobConfig from './__mocks__/mock_job_config.json';
|
||||||
|
|
||||||
jest.mock('../../util/ml_error', () => class MLRequestFailure {});
|
|
||||||
|
|
||||||
jest.mock('../../services/job_service', () => ({
|
jest.mock('../../services/job_service', () => ({
|
||||||
mlJobService: {
|
mlJobService: {
|
||||||
getJob() {
|
getJob() {
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { cloneDeep, isEqual, pick } from 'lodash';
|
import { cloneDeep, isEqual, pick } from 'lodash';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import {
|
import {
|
||||||
EuiButton,
|
EuiButton,
|
||||||
EuiButtonEmpty,
|
EuiButtonEmpty,
|
||||||
|
@ -28,8 +30,6 @@ import { loadFullJob } from '../utils';
|
||||||
import { validateModelMemoryLimit, validateGroupNames, isValidCustomUrls } from '../validate_job';
|
import { validateModelMemoryLimit, validateGroupNames, isValidCustomUrls } from '../validate_job';
|
||||||
import { toastNotificationServiceProvider } from '../../../../services/toast_notification_service';
|
import { toastNotificationServiceProvider } from '../../../../services/toast_notification_service';
|
||||||
import { withKibana } from '../../../../../../../../../src/plugins/kibana_react/public';
|
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 { collapseLiteralStrings } from '../../../../../../shared_imports';
|
||||||
import { DATAFEED_STATE } from '../../../../../../common/constants/states';
|
import { DATAFEED_STATE } from '../../../../../../common/constants/states';
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
import { difference } from 'lodash';
|
import { difference } from 'lodash';
|
||||||
import { getNewJobLimits } from '../../../../services/ml_server_info';
|
import { getNewJobLimits } from '../../../../services/ml_server_info';
|
||||||
import { mlJobService } from '../../../../services/job_service';
|
|
||||||
import { processCreatedBy } from '../../../../../../common/util/job_utils';
|
import { processCreatedBy } from '../../../../../../common/util/job_utils';
|
||||||
import { getSavedObjectsClient } from '../../../../util/dependency_cache';
|
import { getSavedObjectsClient } from '../../../../util/dependency_cache';
|
||||||
|
import { ml } from '../../../../services/ml_api_service';
|
||||||
|
|
||||||
export function saveJob(job, newJobData, finish) {
|
export function saveJob(job, newJobData, finish) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -41,14 +41,9 @@ export function saveJob(job, newJobData, finish) {
|
||||||
|
|
||||||
// if anything has changed, post the changes
|
// if anything has changed, post the changes
|
||||||
if (Object.keys(jobData).length) {
|
if (Object.keys(jobData).length) {
|
||||||
mlJobService
|
ml.updateJob({ jobId: job.job_id, job: jobData })
|
||||||
.updateJob(job.job_id, jobData)
|
.then(() => {
|
||||||
.then((resp) => {
|
saveDatafeedWrapper();
|
||||||
if (resp.success) {
|
|
||||||
saveDatafeedWrapper();
|
|
||||||
} else {
|
|
||||||
reject(resp);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
reject(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) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (Object.keys(datafeedData).length) {
|
if (Object.keys(datafeedConfig).length) {
|
||||||
const datafeedId = job.datafeed_config.datafeed_id;
|
const datafeedId = job.datafeed_config.datafeed_id;
|
||||||
mlJobService.updateDatafeed(datafeedId, datafeedData).then((resp) => {
|
ml.updateDatafeed({ datafeedId, datafeedConfig })
|
||||||
if (resp.success) {
|
.then(() => {
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
})
|
||||||
reject(resp);
|
.catch((error) => {
|
||||||
}
|
reject(error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EuiButton,
|
EuiButton,
|
||||||
|
@ -25,9 +27,7 @@ import { ml } from '../../../../../services/ml_api_service';
|
||||||
import { checkPermission } from '../../../../../capabilities/check_capabilities';
|
import { checkPermission } from '../../../../../capabilities/check_capabilities';
|
||||||
import { GroupList } from './group_list';
|
import { GroupList } from './group_list';
|
||||||
import { NewGroupInput } from './new_group_input';
|
import { NewGroupInput } from './new_group_input';
|
||||||
import { mlMessageBarService } from '../../../../../components/messagebar';
|
import { getToastNotificationService } from '../../../../../services/toast_notification_service';
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
|
||||||
|
|
||||||
function createSelectedGroups(jobs, groups) {
|
function createSelectedGroups(jobs, groups) {
|
||||||
const jobIds = jobs.map((j) => j.id);
|
const jobIds = jobs.map((j) => j.id);
|
||||||
|
@ -160,7 +160,7 @@ export class GroupSelector extends Component {
|
||||||
// check success of each job update
|
// check success of each job update
|
||||||
if (resp.hasOwnProperty(jobId)) {
|
if (resp.hasOwnProperty(jobId)) {
|
||||||
if (resp[jobId].success === false) {
|
if (resp[jobId].success === false) {
|
||||||
mlMessageBarService.notify.error(resp[jobId].error);
|
getToastNotificationService().displayErrorToast(resp[jobId].error);
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ export class GroupSelector extends Component {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(error);
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,17 +5,19 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { each } from 'lodash';
|
import { each } from 'lodash';
|
||||||
import { mlMessageBarService } from '../../../components/messagebar';
|
import { i18n } from '@kbn/i18n';
|
||||||
import rison from 'rison-node';
|
import rison from 'rison-node';
|
||||||
|
|
||||||
import { mlJobService } from '../../../services/job_service';
|
import { mlJobService } from '../../../services/job_service';
|
||||||
import { toastNotificationServiceProvider } from '../../../services/toast_notification_service';
|
import {
|
||||||
import { ml } from '../../../services/ml_api_service';
|
getToastNotificationService,
|
||||||
|
toastNotificationServiceProvider,
|
||||||
|
} from '../../../services/toast_notification_service';
|
||||||
import { getToastNotifications } from '../../../util/dependency_cache';
|
import { getToastNotifications } from '../../../util/dependency_cache';
|
||||||
|
import { ml } from '../../../services/ml_api_service';
|
||||||
import { stringMatch } from '../../../util/string_utils';
|
import { stringMatch } from '../../../util/string_utils';
|
||||||
import { JOB_STATE, DATAFEED_STATE } from '../../../../../common/constants/states';
|
import { JOB_STATE, DATAFEED_STATE } from '../../../../../common/constants/states';
|
||||||
import { parseInterval } from '../../../../../common/util/parse_interval';
|
import { parseInterval } from '../../../../../common/util/parse_interval';
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { mlCalendarService } from '../../../services/calendar_service';
|
import { mlCalendarService } from '../../../services/calendar_service';
|
||||||
|
|
||||||
export function loadFullJob(jobId) {
|
export function loadFullJob(jobId) {
|
||||||
|
@ -60,7 +62,6 @@ export function forceStartDatafeeds(jobs, start, end, finish = () => {}) {
|
||||||
finish();
|
finish();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
mlMessageBarService.notify.error(error);
|
|
||||||
const toastNotifications = getToastNotifications();
|
const toastNotifications = getToastNotifications();
|
||||||
toastNotifications.addDanger(
|
toastNotifications.addDanger(
|
||||||
i18n.translate('xpack.ml.jobsList.startJobErrorMessage', {
|
i18n.translate('xpack.ml.jobsList.startJobErrorMessage', {
|
||||||
|
@ -81,7 +82,6 @@ export function stopDatafeeds(jobs, finish = () => {}) {
|
||||||
finish();
|
finish();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
mlMessageBarService.notify.error(error);
|
|
||||||
const toastNotifications = getToastNotifications();
|
const toastNotifications = getToastNotifications();
|
||||||
toastNotifications.addDanger(
|
toastNotifications.addDanger(
|
||||||
i18n.translate('xpack.ml.jobsList.stopJobErrorMessage', {
|
i18n.translate('xpack.ml.jobsList.stopJobErrorMessage', {
|
||||||
|
@ -219,9 +219,8 @@ export async function cloneJob(jobId) {
|
||||||
|
|
||||||
window.location.href = '#/jobs/new_job';
|
window.location.href = '#/jobs/new_job';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(
|
||||||
const toastNotifications = getToastNotifications();
|
error,
|
||||||
toastNotifications.addDanger(
|
|
||||||
i18n.translate('xpack.ml.jobsList.cloneJobErrorMessage', {
|
i18n.translate('xpack.ml.jobsList.cloneJobErrorMessage', {
|
||||||
defaultMessage: 'Could not clone {jobId}. Job could not be found',
|
defaultMessage: 'Could not clone {jobId}. Job could not be found',
|
||||||
values: { jobId },
|
values: { jobId },
|
||||||
|
@ -239,13 +238,11 @@ export function closeJobs(jobs, finish = () => {}) {
|
||||||
finish();
|
finish();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(
|
||||||
const toastNotifications = getToastNotifications();
|
error,
|
||||||
toastNotifications.addDanger(
|
|
||||||
i18n.translate('xpack.ml.jobsList.closeJobErrorMessage', {
|
i18n.translate('xpack.ml.jobsList.closeJobErrorMessage', {
|
||||||
defaultMessage: 'Jobs failed to close',
|
defaultMessage: 'Jobs failed to close',
|
||||||
}),
|
})
|
||||||
error
|
|
||||||
);
|
);
|
||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
|
@ -260,13 +257,11 @@ export function deleteJobs(jobs, finish = () => {}) {
|
||||||
finish();
|
finish();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(
|
||||||
const toastNotifications = getToastNotifications();
|
error,
|
||||||
toastNotifications.addDanger(
|
|
||||||
i18n.translate('xpack.ml.jobsList.deleteJobErrorMessage', {
|
i18n.translate('xpack.ml.jobsList.deleteJobErrorMessage', {
|
||||||
defaultMessage: 'Jobs failed to delete',
|
defaultMessage: 'Jobs failed to delete',
|
||||||
}),
|
})
|
||||||
error
|
|
||||||
);
|
);
|
||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { useEffect, useMemo } from 'react';
|
||||||
import { DEFAULT_MODEL_MEMORY_LIMIT } from '../../../../../../../common/constants/new_job';
|
import { DEFAULT_MODEL_MEMORY_LIMIT } from '../../../../../../../common/constants/new_job';
|
||||||
import { ml } from '../../../../../services/ml_api_service';
|
import { ml } from '../../../../../services/ml_api_service';
|
||||||
import { JobValidator, VALIDATION_DELAY_MS } from '../../job_validator/job_validator';
|
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 { useMlKibana } from '../../../../../contexts/kibana';
|
||||||
import { JobCreator } from '../job_creator';
|
import { JobCreator } from '../job_creator';
|
||||||
|
|
||||||
|
@ -36,10 +36,10 @@ export const modelMemoryEstimatorProvider = (
|
||||||
jobValidator: JobValidator
|
jobValidator: JobValidator
|
||||||
) => {
|
) => {
|
||||||
const modelMemoryCheck$ = new Subject<CalculatePayload>();
|
const modelMemoryCheck$ = new Subject<CalculatePayload>();
|
||||||
const error$ = new Subject<ErrorResponse['body']>();
|
const error$ = new Subject<MLHttpFetchError<MLResponseError>>();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get error$(): Observable<ErrorResponse['body']> {
|
get error$(): Observable<MLHttpFetchError<MLResponseError>> {
|
||||||
return error$.asObservable();
|
return error$.asObservable();
|
||||||
},
|
},
|
||||||
get updates$(): Observable<string> {
|
get updates$(): Observable<string> {
|
||||||
|
@ -64,7 +64,7 @@ export const modelMemoryEstimatorProvider = (
|
||||||
catchError((error) => {
|
catchError((error) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error('Model memory limit could not be calculated', error.body);
|
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
|
// fallback to the default in case estimation failed
|
||||||
return of(DEFAULT_MODEL_MEMORY_LIMIT);
|
return of(DEFAULT_MODEL_MEMORY_LIMIT);
|
||||||
})
|
})
|
||||||
|
@ -120,7 +120,8 @@ export const useModelMemoryEstimator = (
|
||||||
title: i18n.translate('xpack.ml.newJob.wizard.estimateModelMemoryError', {
|
title: i18n.translate('xpack.ml.newJob.wizard.estimateModelMemoryError', {
|
||||||
defaultMessage: 'Model memory limit could not be calculated',
|
defaultMessage: 'Model memory limit could not be calculated',
|
||||||
}),
|
}),
|
||||||
text: error.message,
|
text:
|
||||||
|
error.body.attributes?.body.error.caused_by?.reason || error.body.message || undefined,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
} from '../../../../../common/job_creator';
|
} from '../../../../../common/job_creator';
|
||||||
import { ml, BucketSpanEstimatorData } from '../../../../../../../services/ml_api_service';
|
import { ml, BucketSpanEstimatorData } from '../../../../../../../services/ml_api_service';
|
||||||
import { useMlContext } from '../../../../../../../contexts/ml';
|
import { useMlContext } from '../../../../../../../contexts/ml';
|
||||||
import { mlMessageBarService } from '../../../../../../../components/messagebar';
|
import { getToastNotificationService } from '../../../../../../../services/toast_notification_service';
|
||||||
|
|
||||||
export enum ESTIMATE_STATUS {
|
export enum ESTIMATE_STATUS {
|
||||||
NOT_RUNNING,
|
NOT_RUNNING,
|
||||||
|
@ -68,7 +68,7 @@ export function useEstimateBucketSpan() {
|
||||||
const { name, error, message } = await ml.estimateBucketSpan(data);
|
const { name, error, message } = await ml.estimateBucketSpan(data);
|
||||||
setStatus(ESTIMATE_STATUS.NOT_RUNNING);
|
setStatus(ESTIMATE_STATUS.NOT_RUNNING);
|
||||||
if (error === true) {
|
if (error === true) {
|
||||||
mlMessageBarService.notify.error(message);
|
getToastNotificationService().displayErrorToast(message);
|
||||||
} else {
|
} else {
|
||||||
jobCreator.bucketSpan = name;
|
jobCreator.bucketSpan = name;
|
||||||
jobCreatorUpdate();
|
jobCreatorUpdate();
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import React, { FC, useContext, useEffect, useState } from 'react';
|
import React, { FC, useContext, useEffect, useState } from 'react';
|
||||||
import { EuiHorizontalRule } from '@elastic/eui';
|
import { EuiHorizontalRule } from '@elastic/eui';
|
||||||
import { mlMessageBarService } from '../../../../../../../components/messagebar';
|
import { getToastNotificationService } from '../../../../../../../services/toast_notification_service';
|
||||||
|
|
||||||
import { JobCreatorContext } from '../../../job_creator_context';
|
import { JobCreatorContext } from '../../../job_creator_context';
|
||||||
import { CategorizationJobCreator } from '../../../../../common/job_creator';
|
import { CategorizationJobCreator } from '../../../../../common/job_creator';
|
||||||
|
@ -94,7 +94,7 @@ export const CategorizationDetectors: FC<Props> = ({ setIsValid }) => {
|
||||||
setFieldExamples(null);
|
setFieldExamples(null);
|
||||||
setValidationChecks([]);
|
setValidationChecks([]);
|
||||||
setOverallValidStatus(CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID);
|
setOverallValidStatus(CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID);
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setFieldExamples(null);
|
setFieldExamples(null);
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { AggFieldPair } from '../../../../../../../../../common/types/fields';
|
||||||
import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings';
|
import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings';
|
||||||
import { MetricSelector } from './metric_selector';
|
import { MetricSelector } from './metric_selector';
|
||||||
import { ChartGrid } from './chart_grid';
|
import { ChartGrid } from './chart_grid';
|
||||||
import { mlMessageBarService } from '../../../../../../../components/messagebar';
|
import { getToastNotificationService } from '../../../../../../../services/toast_notification_service';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setIsValid: (na: boolean) => void;
|
setIsValid: (na: boolean) => void;
|
||||||
|
@ -109,7 +109,7 @@ export const MultiMetricDetectors: FC<Props> = ({ setIsValid }) => {
|
||||||
.loadFieldExampleValues(splitField)
|
.loadFieldExampleValues(splitField)
|
||||||
.then(setFieldValues)
|
.then(setFieldValues)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setFieldValues([]);
|
setFieldValues([]);
|
||||||
|
@ -138,7 +138,7 @@ export const MultiMetricDetectors: FC<Props> = ({ setIsValid }) => {
|
||||||
);
|
);
|
||||||
setLineChartsData(resp);
|
setLineChartsData(resp);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(error);
|
||||||
setLineChartsData([]);
|
setLineChartsData([]);
|
||||||
}
|
}
|
||||||
setLoadingData(false);
|
setLoadingData(false);
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { Results, ModelItem, Anomaly } from '../../../../../common/results_loade
|
||||||
import { LineChartData } from '../../../../../common/chart_loader';
|
import { LineChartData } from '../../../../../common/chart_loader';
|
||||||
import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings';
|
import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings';
|
||||||
import { ChartGrid } from './chart_grid';
|
import { ChartGrid } from './chart_grid';
|
||||||
import { mlMessageBarService } from '../../../../../../../components/messagebar';
|
import { getToastNotificationService } from '../../../../../../../services/toast_notification_service';
|
||||||
|
|
||||||
export const MultiMetricDetectorsSummary: FC = () => {
|
export const MultiMetricDetectorsSummary: FC = () => {
|
||||||
const { jobCreator: jc, chartLoader, resultsLoader, chartInterval } = useContext(
|
const { jobCreator: jc, chartLoader, resultsLoader, chartInterval } = useContext(
|
||||||
|
@ -43,7 +43,7 @@ export const MultiMetricDetectorsSummary: FC = () => {
|
||||||
const tempFieldValues = await chartLoader.loadFieldExampleValues(jobCreator.splitField);
|
const tempFieldValues = await chartLoader.loadFieldExampleValues(jobCreator.splitField);
|
||||||
setFieldValues(tempFieldValues);
|
setFieldValues(tempFieldValues);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -75,7 +75,7 @@ export const MultiMetricDetectorsSummary: FC = () => {
|
||||||
);
|
);
|
||||||
setLineChartsData(resp);
|
setLineChartsData(resp);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(error);
|
||||||
setLineChartsData({});
|
setLineChartsData({});
|
||||||
}
|
}
|
||||||
setLoadingData(false);
|
setLoadingData(false);
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { getChartSettings, defaultChartSettings } from '../../../charts/common/s
|
||||||
import { MetricSelector } from './metric_selector';
|
import { MetricSelector } from './metric_selector';
|
||||||
import { SplitFieldSelector } from '../split_field';
|
import { SplitFieldSelector } from '../split_field';
|
||||||
import { ChartGrid } from './chart_grid';
|
import { ChartGrid } from './chart_grid';
|
||||||
import { mlMessageBarService } from '../../../../../../../components/messagebar';
|
import { getToastNotificationService } from '../../../../../../../services/toast_notification_service';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setIsValid: (na: boolean) => void;
|
setIsValid: (na: boolean) => void;
|
||||||
|
@ -159,7 +159,7 @@ export const PopulationDetectors: FC<Props> = ({ setIsValid }) => {
|
||||||
|
|
||||||
setLineChartsData(resp);
|
setLineChartsData(resp);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(error);
|
||||||
setLineChartsData([]);
|
setLineChartsData([]);
|
||||||
}
|
}
|
||||||
setLoadingData(false);
|
setLoadingData(false);
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { LineChartData } from '../../../../../common/chart_loader';
|
||||||
import { Field, AggFieldPair } from '../../../../../../../../../common/types/fields';
|
import { Field, AggFieldPair } from '../../../../../../../../../common/types/fields';
|
||||||
import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings';
|
import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings';
|
||||||
import { ChartGrid } from './chart_grid';
|
import { ChartGrid } from './chart_grid';
|
||||||
import { mlMessageBarService } from '../../../../../../../components/messagebar';
|
import { getToastNotificationService } from '../../../../../../../services/toast_notification_service';
|
||||||
|
|
||||||
type DetectorFieldValues = Record<number, string[]>;
|
type DetectorFieldValues = Record<number, string[]>;
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ export const PopulationDetectorsSummary: FC = () => {
|
||||||
|
|
||||||
setLineChartsData(resp);
|
setLineChartsData(resp);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(error);
|
||||||
setLineChartsData({});
|
setLineChartsData({});
|
||||||
}
|
}
|
||||||
setLoadingData(false);
|
setLoadingData(false);
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { newJobCapsService } from '../../../../../../../services/new_job_capabil
|
||||||
import { AggFieldPair } from '../../../../../../../../../common/types/fields';
|
import { AggFieldPair } from '../../../../../../../../../common/types/fields';
|
||||||
import { AnomalyChart, CHART_TYPE } from '../../../charts/anomaly_chart';
|
import { AnomalyChart, CHART_TYPE } from '../../../charts/anomaly_chart';
|
||||||
import { getChartSettings } from '../../../charts/common/settings';
|
import { getChartSettings } from '../../../charts/common/settings';
|
||||||
import { mlMessageBarService } from '../../../../../../../components/messagebar';
|
import { getToastNotificationService } from '../../../../../../../services/toast_notification_service';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setIsValid: (na: boolean) => void;
|
setIsValid: (na: boolean) => void;
|
||||||
|
@ -93,7 +93,7 @@ export const SingleMetricDetectors: FC<Props> = ({ setIsValid }) => {
|
||||||
setLineChartData(resp);
|
setLineChartData(resp);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(error);
|
||||||
setLineChartData({});
|
setLineChartData({});
|
||||||
}
|
}
|
||||||
setLoadingData(false);
|
setLoadingData(false);
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Results, ModelItem, Anomaly } from '../../../../../common/results_loade
|
||||||
import { LineChartData } from '../../../../../common/chart_loader';
|
import { LineChartData } from '../../../../../common/chart_loader';
|
||||||
import { AnomalyChart, CHART_TYPE } from '../../../charts/anomaly_chart';
|
import { AnomalyChart, CHART_TYPE } from '../../../charts/anomaly_chart';
|
||||||
import { getChartSettings } from '../../../charts/common/settings';
|
import { getChartSettings } from '../../../charts/common/settings';
|
||||||
import { mlMessageBarService } from '../../../../../../../components/messagebar';
|
import { getToastNotificationService } from '../../../../../../../services/toast_notification_service';
|
||||||
|
|
||||||
const DTR_IDX = 0;
|
const DTR_IDX = 0;
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ export const SingleMetricDetectorsSummary: FC = () => {
|
||||||
setLineChartData(resp);
|
setLineChartData(resp);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mlMessageBarService.notify.error(error);
|
getToastNotificationService().displayErrorToast(error);
|
||||||
setLineChartData({});
|
setLineChartData({});
|
||||||
}
|
}
|
||||||
setLoadingData(false);
|
setLoadingData(false);
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { JobRunner } from '../../../../../common/job_runner';
|
import { JobRunner } from '../../../../../common/job_runner';
|
||||||
import { useMlKibana } from '../../../../../../../contexts/kibana';
|
import { useMlKibana } from '../../../../../../../contexts/kibana';
|
||||||
import { getErrorMessage } from '../../../../../../../../../common/util/errors';
|
import { extractErrorMessage } from '../../../../../../../../../common/util/errors';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { CreateWatchFlyout } from '../../../../../../jobs_list/components/create_watch_flyout/index';
|
import { CreateWatchFlyout } from '../../../../../../jobs_list/components/create_watch_flyout/index';
|
||||||
|
@ -70,7 +70,7 @@ export const PostSaveOptions: FC<Props> = ({ jobRunner }) => {
|
||||||
defaultMessage: `Error starting job`,
|
defaultMessage: `Error starting job`,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
text: getErrorMessage(error),
|
text: extractErrorMessage(error),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,13 @@ import { JobCreatorContext } from '../job_creator_context';
|
||||||
import { JobRunner } from '../../../common/job_runner';
|
import { JobRunner } from '../../../common/job_runner';
|
||||||
import { mlJobService } from '../../../../../services/job_service';
|
import { mlJobService } from '../../../../../services/job_service';
|
||||||
import { JsonEditorFlyout, EDITOR_MODE } from '../common/json_editor_flyout';
|
import { JsonEditorFlyout, EDITOR_MODE } from '../common/json_editor_flyout';
|
||||||
import { getErrorMessage } from '../../../../../../../common/util/errors';
|
|
||||||
import { isSingleMetricJobCreator, isAdvancedJobCreator } from '../../../common/job_creator';
|
import { isSingleMetricJobCreator, isAdvancedJobCreator } from '../../../common/job_creator';
|
||||||
import { JobDetails } from './components/job_details';
|
import { JobDetails } from './components/job_details';
|
||||||
import { DatafeedDetails } from './components/datafeed_details';
|
import { DatafeedDetails } from './components/datafeed_details';
|
||||||
import { DetectorChart } from './components/detector_chart';
|
import { DetectorChart } from './components/detector_chart';
|
||||||
import { JobProgress } from './components/job_progress';
|
import { JobProgress } from './components/job_progress';
|
||||||
import { PostSaveOptions } from './components/post_save_options';
|
import { PostSaveOptions } from './components/post_save_options';
|
||||||
|
import { toastNotificationServiceProvider } from '../../../../../services/toast_notification_service';
|
||||||
import {
|
import {
|
||||||
convertToAdvancedJob,
|
convertToAdvancedJob,
|
||||||
resetJob,
|
resetJob,
|
||||||
|
@ -72,15 +72,7 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
|
||||||
const jr = await jobCreator.createAndStartJob();
|
const jr = await jobCreator.createAndStartJob();
|
||||||
setJobRunner(jr);
|
setJobRunner(jr);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// catch and display all job creation errors
|
handleJobCreationError(error);
|
||||||
const { toasts } = notifications;
|
|
||||||
toasts.addDanger({
|
|
||||||
title: i18n.translate('xpack.ml.newJob.wizard.summaryStep.createJobError', {
|
|
||||||
defaultMessage: `Job creation error`,
|
|
||||||
}),
|
|
||||||
text: getErrorMessage(error),
|
|
||||||
});
|
|
||||||
setCreatingJob(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,18 +83,21 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
|
||||||
await jobCreator.createDatafeed();
|
await jobCreator.createDatafeed();
|
||||||
advancedStartDatafeed(jobCreator, navigateToPath);
|
advancedStartDatafeed(jobCreator, navigateToPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// catch and display all job creation errors
|
handleJobCreationError(error);
|
||||||
const { toasts } = notifications;
|
|
||||||
toasts.addDanger({
|
|
||||||
title: i18n.translate('xpack.ml.newJob.wizard.summaryStep.createJobError', {
|
|
||||||
defaultMessage: `Job creation error`,
|
|
||||||
}),
|
|
||||||
text: getErrorMessage(error),
|
|
||||||
});
|
|
||||||
setCreatingJob(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
function viewResults() {
|
||||||
const url = mlJobService.createResultsUrl(
|
const url = mlJobService.createResultsUrl(
|
||||||
[jobCreator.jobId],
|
[jobCreator.jobId],
|
||||||
|
|
|
@ -8,7 +8,7 @@ import React, { Fragment, FC, useContext, useState, useEffect } from 'react';
|
||||||
import { WizardNav } from '../wizard_nav';
|
import { WizardNav } from '../wizard_nav';
|
||||||
import { WIZARD_STEPS, StepProps } from '../step_types';
|
import { WIZARD_STEPS, StepProps } from '../step_types';
|
||||||
import { JobCreatorContext } from '../job_creator_context';
|
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 { ValidateJob } from '../../../../../components/validate_job';
|
||||||
import { JOB_TYPE } from '../../../../../../../common/constants/new_job';
|
import { JOB_TYPE } from '../../../../../../../common/constants/new_job';
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ export const ValidationStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep })
|
||||||
<ValidateJob
|
<ValidateJob
|
||||||
getJobConfig={getJobConfig}
|
getJobConfig={getJobConfig}
|
||||||
getDuration={getDuration}
|
getDuration={getDuration}
|
||||||
mlJobService={mlJobService}
|
ml={ml}
|
||||||
embedded={true}
|
embedded={true}
|
||||||
setIsValid={setIsValid}
|
setIsValid={setIsValid}
|
||||||
idFilterList={idFilterList}
|
idFilterList={idFilterList}
|
||||||
|
|
|
@ -14,15 +14,13 @@ import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
import { ml } from './ml_api_service';
|
import { ml } from './ml_api_service';
|
||||||
|
|
||||||
import { mlMessageBarService } from '../components/messagebar';
|
import { getToastNotificationService } from '../services/toast_notification_service';
|
||||||
import { getToastNotifications } from '../util/dependency_cache';
|
|
||||||
import { isWebUrl } from '../util/url_utils';
|
import { isWebUrl } from '../util/url_utils';
|
||||||
import { ML_DATA_PREVIEW_COUNT } from '../../../common/util/job_utils';
|
import { ML_DATA_PREVIEW_COUNT } from '../../../common/util/job_utils';
|
||||||
import { TIME_FORMAT } from '../../../common/constants/time_format';
|
import { TIME_FORMAT } from '../../../common/constants/time_format';
|
||||||
import { parseInterval } from '../../../common/util/parse_interval';
|
import { parseInterval } from '../../../common/util/parse_interval';
|
||||||
import { toastNotificationServiceProvider } from '../services/toast_notification_service';
|
|
||||||
import { validateTimeRange } from '../util/date_utils';
|
import { validateTimeRange } from '../util/date_utils';
|
||||||
const msgs = mlMessageBarService;
|
|
||||||
let jobs = [];
|
let jobs = [];
|
||||||
let datafeedIds = {};
|
let datafeedIds = {};
|
||||||
|
|
||||||
|
@ -119,7 +117,6 @@ class JobService {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
jobs = [];
|
jobs = [];
|
||||||
datafeedIds = {};
|
datafeedIds = {};
|
||||||
|
|
||||||
ml.getJobs()
|
ml.getJobs()
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
jobs = resp.jobs;
|
jobs = resp.jobs;
|
||||||
|
@ -162,7 +159,6 @@ class JobService {
|
||||||
}
|
}
|
||||||
processBasicJobInfo(this, jobs);
|
processBasicJobInfo(this, jobs);
|
||||||
this.jobs = jobs;
|
this.jobs = jobs;
|
||||||
createJobStats(this.jobs, this.jobStats);
|
|
||||||
resolve({ jobs: this.jobs });
|
resolve({ jobs: this.jobs });
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -176,12 +172,7 @@ class JobService {
|
||||||
|
|
||||||
function error(err) {
|
function error(err) {
|
||||||
console.log('jobService error getting list of jobs:', err);
|
console.log('jobService error getting list of jobs:', err);
|
||||||
msgs.notify.error(
|
getToastNotificationService().displayErrorToast(err);
|
||||||
i18n.translate('xpack.ml.jobService.jobsListCouldNotBeRetrievedErrorMessage', {
|
|
||||||
defaultMessage: 'Jobs list could not be retrieved',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
msgs.notify.error('', err);
|
|
||||||
reject({ jobs, err });
|
reject({ jobs, err });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -248,7 +239,6 @@ class JobService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.jobs = jobs;
|
this.jobs = jobs;
|
||||||
createJobStats(this.jobs, this.jobStats);
|
|
||||||
resolve({ jobs: this.jobs });
|
resolve({ jobs: this.jobs });
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -263,12 +253,7 @@ class JobService {
|
||||||
|
|
||||||
function error(err) {
|
function error(err) {
|
||||||
console.log('JobService error getting list of jobs:', err);
|
console.log('JobService error getting list of jobs:', err);
|
||||||
msgs.notify.error(
|
getToastNotificationService().displayErrorToast(err);
|
||||||
i18n.translate('xpack.ml.jobService.jobsListCouldNotBeRetrievedErrorMessage', {
|
|
||||||
defaultMessage: 'Jobs list could not be retrieved',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
msgs.notify.error('', err);
|
|
||||||
reject({ jobs, err });
|
reject({ jobs, err });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -280,9 +265,6 @@ class JobService {
|
||||||
|
|
||||||
ml.getDatafeeds(sId)
|
ml.getDatafeeds(sId)
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
// console.log('loadDatafeeds query response:', resp);
|
|
||||||
|
|
||||||
// make deep copy of datafeeds
|
|
||||||
const datafeeds = resp.datafeeds;
|
const datafeeds = resp.datafeeds;
|
||||||
|
|
||||||
// load datafeeds stats
|
// load datafeeds stats
|
||||||
|
@ -309,12 +291,7 @@ class JobService {
|
||||||
|
|
||||||
function error(err) {
|
function error(err) {
|
||||||
console.log('loadDatafeeds error getting list of datafeeds:', err);
|
console.log('loadDatafeeds error getting list of datafeeds:', err);
|
||||||
msgs.notify.error(
|
getToastNotificationService().displayErrorToast(err);
|
||||||
i18n.translate('xpack.ml.jobService.datafeedsListCouldNotBeRetrievedErrorMessage', {
|
|
||||||
defaultMessage: 'datafeeds list could not be retrieved',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
msgs.notify.error('', err);
|
|
||||||
reject({ jobs, err });
|
reject({ jobs, err });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -415,62 +392,6 @@ class JobService {
|
||||||
return tempJob;
|
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
|
// find a job based on the id
|
||||||
getJob(jobId) {
|
getJob(jobId) {
|
||||||
const job = find(jobs, (j) => {
|
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
|
// start the datafeed for a given job
|
||||||
// refresh the job state on start success
|
// refresh the job state on start success
|
||||||
startDatafeed(datafeedId, jobId, start, end) {
|
startDatafeed(datafeedId, jobId, start, end) {
|
||||||
|
@ -677,49 +579,6 @@ class JobService {
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log('jobService error starting datafeed:', 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);
|
reject(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -887,51 +746,6 @@ function processBasicJobInfo(localJobService, jobsList) {
|
||||||
return processedJobsList;
|
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) {
|
function createResultsUrlForJobs(jobsList, resultsPage, userTimeRange) {
|
||||||
let from = undefined;
|
let from = undefined;
|
||||||
let to = undefined;
|
let to = undefined;
|
||||||
|
|
|
@ -62,7 +62,7 @@ export interface BucketSpanEstimatorResponse {
|
||||||
name: string;
|
name: string;
|
||||||
ms: number;
|
ms: number;
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
message?: { msg: string } | string;
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetTimeFieldRangeResponse {
|
export interface GetTimeFieldRangeResponse {
|
||||||
|
|
|
@ -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), []);
|
|
||||||
}
|
|
|
@ -4,4 +4,9 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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';
|
|
@ -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), []);
|
||||||
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
import { getToastNotifications } from '../../../util/dependency_cache';
|
import { getToastNotifications } from '../../../util/dependency_cache';
|
||||||
import { ml } from '../../../services/ml_api_service';
|
import { ml } from '../../../services/ml_api_service';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { getErrorMessage } from '../../../../../common/util/errors';
|
import { extractErrorMessage } from '../../../../../common/util/errors';
|
||||||
|
|
||||||
export async function deleteCalendars(calendarsToDelete, callback) {
|
export async function deleteCalendars(calendarsToDelete, callback) {
|
||||||
if (calendarsToDelete === undefined || calendarsToDelete.length === 0) {
|
if (calendarsToDelete === undefined || calendarsToDelete.length === 0) {
|
||||||
|
@ -47,7 +47,7 @@ export async function deleteCalendars(calendarsToDelete, callback) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
text: getErrorMessage(error),
|
text: extractErrorMessage(error),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -118,6 +118,11 @@ export function datafeedsProvider({ asInternalUser }: IScopedClusterClient) {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isRequestTimeout(error)) {
|
if (isRequestTimeout(error)) {
|
||||||
return fillResultsWithTimeouts(results, datafeedId, datafeedIds, DATAFEED_STATE.STOPPED);
|
return fillResultsWithTimeouts(results, datafeedId, datafeedIds, DATAFEED_STATE.STOPPED);
|
||||||
|
} else {
|
||||||
|
results[datafeedId] = {
|
||||||
|
started: false,
|
||||||
|
error: error.body,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,19 +325,20 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat
|
||||||
success: false,
|
success: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if analyticsId is valid and get destination index
|
try {
|
||||||
if (deleteDestIndex || deleteDestIndexPattern) {
|
// Check if analyticsId is valid and get destination index
|
||||||
try {
|
const { body } = await client.asInternalUser.ml.getDataFrameAnalytics({
|
||||||
const { body } = await client.asInternalUser.ml.getDataFrameAnalytics({
|
id: analyticsId,
|
||||||
id: analyticsId,
|
});
|
||||||
});
|
if (Array.isArray(body.data_frame_analytics) && body.data_frame_analytics.length > 0) {
|
||||||
if (Array.isArray(body.data_frame_analytics) && body.data_frame_analytics.length > 0) {
|
destinationIndex = body.data_frame_analytics[0].dest.index;
|
||||||
destinationIndex = body.data_frame_analytics[0].dest.index;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return response.customError(wrapError(e));
|
|
||||||
}
|
}
|
||||||
|
} 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 user checks box to delete the destinationIndex associated with the job
|
||||||
if (destinationIndex && deleteDestIndex) {
|
if (destinationIndex && deleteDestIndex) {
|
||||||
// Verify if user has privilege to delete the destination index
|
// Verify if user has privilege to delete the destination index
|
||||||
|
@ -349,8 +350,8 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat
|
||||||
index: destinationIndex,
|
index: destinationIndex,
|
||||||
});
|
});
|
||||||
destIndexDeleted.success = true;
|
destIndexDeleted.success = true;
|
||||||
} catch (deleteIndexError) {
|
} catch ({ body }) {
|
||||||
destIndexDeleted.error = wrapError(deleteIndexError);
|
destIndexDeleted.error = body;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return response.forbidden();
|
return response.forbidden();
|
||||||
|
@ -366,7 +367,7 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat
|
||||||
}
|
}
|
||||||
destIndexPatternDeleted.success = true;
|
destIndexPatternDeleted.success = true;
|
||||||
} catch (deleteDestIndexPatternError) {
|
} catch (deleteDestIndexPatternError) {
|
||||||
destIndexPatternDeleted.error = wrapError(deleteDestIndexPatternError);
|
destIndexPatternDeleted.error = deleteDestIndexPatternError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,11 +379,8 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitializat
|
||||||
id: analyticsId,
|
id: analyticsId,
|
||||||
});
|
});
|
||||||
analyticsJobDeleted.success = true;
|
analyticsJobDeleted.success = true;
|
||||||
} catch (deleteDFAError) {
|
} catch ({ body }) {
|
||||||
analyticsJobDeleted.error = wrapError(deleteDFAError);
|
analyticsJobDeleted.error = body;
|
||||||
if (analyticsJobDeleted.error.statusCode === 404) {
|
|
||||||
return response.notFound();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const results = {
|
const results = {
|
||||||
analyticsJobDeleted,
|
analyticsJobDeleted,
|
||||||
|
|
31
x-pack/plugins/transform/common/utils/errors.ts
Normal file
31
x-pack/plugins/transform/common/utils/errors.ts
Normal 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);
|
||||||
|
}
|
|
@ -15,7 +15,6 @@ export const useRequest = jest.fn(() => ({
|
||||||
|
|
||||||
// just passing through the reimports
|
// just passing through the reimports
|
||||||
export {
|
export {
|
||||||
getErrorMessage,
|
|
||||||
getDataGridSchemaFromKibanaFieldType,
|
getDataGridSchemaFromKibanaFieldType,
|
||||||
getFieldsFromKibanaIndexPattern,
|
getFieldsFromKibanaIndexPattern,
|
||||||
multiColumnSortFactory,
|
multiColumnSortFactory,
|
||||||
|
|
|
@ -12,7 +12,8 @@ import {
|
||||||
DeleteTransformStatus,
|
DeleteTransformStatus,
|
||||||
TransformEndpointRequest,
|
TransformEndpointRequest,
|
||||||
} from '../../../common';
|
} 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 { useAppDependencies, useToastNotifications } from '../app_dependencies';
|
||||||
import { REFRESH_TRANSFORM_LIST_STATE, refreshTransformList$, TransformListRow } from '../common';
|
import { REFRESH_TRANSFORM_LIST_STATE, refreshTransformList$, TransformListRow } from '../common';
|
||||||
import { ToastNotificationText } from '../components';
|
import { ToastNotificationText } from '../components';
|
||||||
|
|
|
@ -12,7 +12,6 @@ import {
|
||||||
getFieldType,
|
getFieldType,
|
||||||
getDataGridSchemaFromKibanaFieldType,
|
getDataGridSchemaFromKibanaFieldType,
|
||||||
getFieldsFromKibanaIndexPattern,
|
getFieldsFromKibanaIndexPattern,
|
||||||
getErrorMessage,
|
|
||||||
showDataGridColumnChartErrorMessageToast,
|
showDataGridColumnChartErrorMessageToast,
|
||||||
useDataGrid,
|
useDataGrid,
|
||||||
useRenderCellValue,
|
useRenderCellValue,
|
||||||
|
@ -21,6 +20,7 @@ import {
|
||||||
UseIndexDataReturnType,
|
UseIndexDataReturnType,
|
||||||
INDEX_STATUS,
|
INDEX_STATUS,
|
||||||
} from '../../shared_imports';
|
} from '../../shared_imports';
|
||||||
|
import { getErrorMessage } from '../../../common/utils/errors';
|
||||||
|
|
||||||
import { isDefaultQuery, matchAllQuery, PivotQuery } from '../common';
|
import { isDefaultQuery, matchAllQuery, PivotQuery } from '../common';
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,13 @@ import { formatHumanReadableDateTimeSeconds } from '../../shared_imports';
|
||||||
import { getNestedProperty } from '../../../common/utils/object_utils';
|
import { getNestedProperty } from '../../../common/utils/object_utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getErrorMessage,
|
|
||||||
multiColumnSortFactory,
|
multiColumnSortFactory,
|
||||||
useDataGrid,
|
useDataGrid,
|
||||||
RenderCellValue,
|
RenderCellValue,
|
||||||
UseIndexDataReturnType,
|
UseIndexDataReturnType,
|
||||||
INDEX_STATUS,
|
INDEX_STATUS,
|
||||||
} from '../../shared_imports';
|
} from '../../shared_imports';
|
||||||
|
import { getErrorMessage } from '../../../common/utils/errors';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getPreviewRequestBody,
|
getPreviewRequestBody,
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_reac
|
||||||
|
|
||||||
import { PROGRESS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants';
|
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 { getTransformProgress, getDiscoverUrl } from '../../../../common';
|
||||||
import { useApi } from '../../../../hooks/use_api';
|
import { useApi } from '../../../../hooks/use_api';
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_reac
|
||||||
import { TransformId } from '../../../../../../common';
|
import { TransformId } from '../../../../../../common';
|
||||||
import { isValidIndexName } from '../../../../../../common/utils/es_utils';
|
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 { useAppDependencies, useToastNotifications } from '../../../../app_dependencies';
|
||||||
import { ToastNotificationText } from '../../../../components';
|
import { ToastNotificationText } from '../../../../components';
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {
|
||||||
EuiTitle,
|
EuiTitle,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
|
||||||
import { getErrorMessage } from '../../../../../shared_imports';
|
import { getErrorMessage } from '../../../../../../common/utils/errors';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
refreshTransformList$,
|
refreshTransformList$,
|
||||||
|
|
|
@ -15,7 +15,6 @@ export {
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getFieldType,
|
getFieldType,
|
||||||
getErrorMessage,
|
|
||||||
extractErrorMessage,
|
extractErrorMessage,
|
||||||
formatHumanReadableDateTimeSeconds,
|
formatHumanReadableDateTimeSeconds,
|
||||||
getDataGridSchemaFromKibanaFieldType,
|
getDataGridSchemaFromKibanaFieldType,
|
||||||
|
|
|
@ -10890,7 +10890,6 @@
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage": "データフレーム分析ジョブ{analyticsId}の削除中にエラーが発生しました。",
|
"xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage": "データフレーム分析ジョブ{analyticsId}の削除中にエラーが発生しました。",
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsPrivilegeErrorMessage": "ユーザーはインデックス{indexName}を削除する権限がありません。{error}",
|
"xpack.ml.dataframe.analyticsList.deleteAnalyticsPrivilegeErrorMessage": "ユーザーはインデックス{indexName}を削除する権限がありません。{error}",
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage": "データフレーム分析ジョブ{analyticsId}の削除リクエストが受け付けられました。",
|
"xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage": "データフレーム分析ジョブ{analyticsId}の削除リクエストが受け付けられました。",
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexErrorMessage": "ディスティネーションインデックス{destinationIndex}の削除中にエラーが発生しました。{error}",
|
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternErrorMessage": "インデックスパターン{destinationIndex}の削除中にエラーが発生しました。{error}",
|
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternErrorMessage": "インデックスパターン{destinationIndex}の削除中にエラーが発生しました。{error}",
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternSuccessMessage": "インデックスパターン{destinationIndex}を削除する要求が確認されました。",
|
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternSuccessMessage": "インデックスパターン{destinationIndex}を削除する要求が確認されました。",
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexSuccessMessage": "ディスティネーションインデックス{destinationIndex}を削除する要求が確認されました。",
|
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexSuccessMessage": "ディスティネーションインデックス{destinationIndex}を削除する要求が確認されました。",
|
||||||
|
@ -11358,16 +11357,9 @@
|
||||||
"xpack.ml.jobService.activeDatafeedsLabel": "アクティブなデータフィード",
|
"xpack.ml.jobService.activeDatafeedsLabel": "アクティブなデータフィード",
|
||||||
"xpack.ml.jobService.activeMLNodesLabel": "アクティブな ML ノード",
|
"xpack.ml.jobService.activeMLNodesLabel": "アクティブな ML ノード",
|
||||||
"xpack.ml.jobService.closedJobsLabel": "ジョブを作成",
|
"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.failedJobsLabel": "失敗したジョブ",
|
||||||
"xpack.ml.jobService.jobsListCouldNotBeRetrievedErrorMessage": "ジョブリストを取得できませんでした",
|
|
||||||
"xpack.ml.jobService.openJobsLabel": "ジョブを開く",
|
"xpack.ml.jobService.openJobsLabel": "ジョブを開く",
|
||||||
"xpack.ml.jobService.requestMayHaveTimedOutErrorMessage": "リクエストがタイムアウトし、まだバックグラウンドで実行中の可能性があります。",
|
|
||||||
"xpack.ml.jobService.totalJobsLabel": "合計ジョブ数",
|
"xpack.ml.jobService.totalJobsLabel": "合計ジョブ数",
|
||||||
"xpack.ml.jobService.updateJobErrorTitle": "ジョブを更新できませんでした: {jobId}",
|
|
||||||
"xpack.ml.jobService.validateJobErrorTitle": "ジョブ検証エラー",
|
"xpack.ml.jobService.validateJobErrorTitle": "ジョブ検証エラー",
|
||||||
"xpack.ml.jobsList.actionExecuteSuccessfullyNotificationMessage": "{successesJobsCount, plural, one{{successJob}} other{# 件のジョブ}} {actionTextPT}成功",
|
"xpack.ml.jobsList.actionExecuteSuccessfullyNotificationMessage": "{successesJobsCount, plural, one{{successJob}} other{# 件のジョブ}} {actionTextPT}成功",
|
||||||
"xpack.ml.jobsList.actionFailedNotificationMessage": "{failureId} が {actionText} に失敗しました",
|
"xpack.ml.jobsList.actionFailedNotificationMessage": "{failureId} が {actionText} に失敗しました",
|
||||||
|
@ -11572,7 +11564,6 @@
|
||||||
"xpack.ml.maxFileSizeSettingsDescription": "ファイルデータビジュアライザーでデータをインポートするときのファイルサイズ上限を設定します。この設定でサポートされている最大値は1 GBです。",
|
"xpack.ml.maxFileSizeSettingsDescription": "ファイルデータビジュアライザーでデータをインポートするときのファイルサイズ上限を設定します。この設定でサポートされている最大値は1 GBです。",
|
||||||
"xpack.ml.maxFileSizeSettingsError": "200 MB、1 GBなどの有効なデータサイズにしてください。",
|
"xpack.ml.maxFileSizeSettingsError": "200 MB、1 GBなどの有効なデータサイズにしてください。",
|
||||||
"xpack.ml.maxFileSizeSettingsName": "ファイルデータビジュアライザーの最大ファイルアップロードサイズ",
|
"xpack.ml.maxFileSizeSettingsName": "ファイルデータビジュアライザーの最大ファイルアップロードサイズ",
|
||||||
"xpack.ml.messagebarService.errorTitle": "エラーが発生しました",
|
|
||||||
"xpack.ml.models.jobService.allOtherRequestsCancelledDescription": " 他のすべてのリクエストはキャンセルされました。",
|
"xpack.ml.models.jobService.allOtherRequestsCancelledDescription": " 他のすべてのリクエストはキャンセルされました。",
|
||||||
"xpack.ml.models.jobService.categorization.messages.failureToGetTokens": "フィールド値の例のサンプルをトークン化することができませんでした。{message}",
|
"xpack.ml.models.jobService.categorization.messages.failureToGetTokens": "フィールド値の例のサンプルをトークン化することができませんでした。{message}",
|
||||||
"xpack.ml.models.jobService.categorization.messages.insufficientPrivileges": "権限が不十分なため、フィールド値の例のトークン化を実行できませんでした。そのため、フィールド値を確認し、カテゴリー分けジョブでの使用が適当かを確認することができません。",
|
"xpack.ml.models.jobService.categorization.messages.insufficientPrivileges": "権限が不十分なため、フィールド値の例のトークン化を実行できませんでした。そのため、フィールド値を確認し、カテゴリー分けジョブでの使用が適当かを確認することができません。",
|
||||||
|
|
|
@ -10896,7 +10896,6 @@
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage": "删除数据帧分析作业 {analyticsId} 时发生错误",
|
"xpack.ml.dataframe.analyticsList.deleteAnalyticsErrorMessage": "删除数据帧分析作业 {analyticsId} 时发生错误",
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsPrivilegeErrorMessage": "用户无权删除索引 {indexName}:{error}",
|
"xpack.ml.dataframe.analyticsList.deleteAnalyticsPrivilegeErrorMessage": "用户无权删除索引 {indexName}:{error}",
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage": "删除的数据帧分析作业 {analyticsId} 的请求已确认。",
|
"xpack.ml.dataframe.analyticsList.deleteAnalyticsSuccessMessage": "删除的数据帧分析作业 {analyticsId} 的请求已确认。",
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexErrorMessage": "删除目标索引 {destinationIndex} 时发生错误:{error}",
|
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternErrorMessage": "删除索引模式 {destinationIndex} 时发生错误:{error}",
|
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternErrorMessage": "删除索引模式 {destinationIndex} 时发生错误:{error}",
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternSuccessMessage": "删除索引模式 {destinationIndex} 的请求已确认。",
|
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexPatternSuccessMessage": "删除索引模式 {destinationIndex} 的请求已确认。",
|
||||||
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexSuccessMessage": "删除目标索引 {destinationIndex} 的请求已确认。",
|
"xpack.ml.dataframe.analyticsList.deleteAnalyticsWithIndexSuccessMessage": "删除目标索引 {destinationIndex} 的请求已确认。",
|
||||||
|
@ -11365,16 +11364,9 @@
|
||||||
"xpack.ml.jobService.activeDatafeedsLabel": "活动数据馈送",
|
"xpack.ml.jobService.activeDatafeedsLabel": "活动数据馈送",
|
||||||
"xpack.ml.jobService.activeMLNodesLabel": "活动 ML 节点",
|
"xpack.ml.jobService.activeMLNodesLabel": "活动 ML 节点",
|
||||||
"xpack.ml.jobService.closedJobsLabel": "已关闭的作业",
|
"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.failedJobsLabel": "失败的作业",
|
||||||
"xpack.ml.jobService.jobsListCouldNotBeRetrievedErrorMessage": "无法检索作业列表",
|
|
||||||
"xpack.ml.jobService.openJobsLabel": "打开的作业",
|
"xpack.ml.jobService.openJobsLabel": "打开的作业",
|
||||||
"xpack.ml.jobService.requestMayHaveTimedOutErrorMessage": "请求可能已超时,并可能仍在后台运行。",
|
|
||||||
"xpack.ml.jobService.totalJobsLabel": "总计作业数",
|
"xpack.ml.jobService.totalJobsLabel": "总计作业数",
|
||||||
"xpack.ml.jobService.updateJobErrorTitle": "无法更新作业:{jobId}",
|
|
||||||
"xpack.ml.jobService.validateJobErrorTitle": "作业验证错误",
|
"xpack.ml.jobService.validateJobErrorTitle": "作业验证错误",
|
||||||
"xpack.ml.jobsList.actionExecuteSuccessfullyNotificationMessage": "{successesJobsCount, plural, one{{successJob}} other{# 个作业}}{actionTextPT}已成功",
|
"xpack.ml.jobsList.actionExecuteSuccessfullyNotificationMessage": "{successesJobsCount, plural, one{{successJob}} other{# 个作业}}{actionTextPT}已成功",
|
||||||
"xpack.ml.jobsList.actionFailedNotificationMessage": "{failureId} 未能{actionText}",
|
"xpack.ml.jobsList.actionFailedNotificationMessage": "{failureId} 未能{actionText}",
|
||||||
|
@ -11579,7 +11571,6 @@
|
||||||
"xpack.ml.maxFileSizeSettingsDescription": "设置在文件数据可视化工具中导入数据时的文件大小限制。此设置支持的最高值为 1GB。",
|
"xpack.ml.maxFileSizeSettingsDescription": "设置在文件数据可视化工具中导入数据时的文件大小限制。此设置支持的最高值为 1GB。",
|
||||||
"xpack.ml.maxFileSizeSettingsError": "应为有效的数据大小。如 200MB、1GB",
|
"xpack.ml.maxFileSizeSettingsError": "应为有效的数据大小。如 200MB、1GB",
|
||||||
"xpack.ml.maxFileSizeSettingsName": "文件数据可视化工具最大文件上传大小",
|
"xpack.ml.maxFileSizeSettingsName": "文件数据可视化工具最大文件上传大小",
|
||||||
"xpack.ml.messagebarService.errorTitle": "发生了错误",
|
|
||||||
"xpack.ml.models.jobService.allOtherRequestsCancelledDescription": " 所有其他请求已取消。",
|
"xpack.ml.models.jobService.allOtherRequestsCancelledDescription": " 所有其他请求已取消。",
|
||||||
"xpack.ml.models.jobService.categorization.messages.failureToGetTokens": "无法对示例字段值样本进行分词。{message}",
|
"xpack.ml.models.jobService.categorization.messages.failureToGetTokens": "无法对示例字段值样本进行分词。{message}",
|
||||||
"xpack.ml.models.jobService.categorization.messages.insufficientPrivileges": "由于权限不足,无法对字段值示例执行分词。因此,无法检查字段值是否适合用于归类作业。",
|
"xpack.ml.models.jobService.categorization.messages.insufficientPrivileges": "由于权限不足,无法对字段值示例执行分词。因此,无法检查字段值是否适合用于归类作业。",
|
||||||
|
|
|
@ -120,7 +120,7 @@ export default ({ getService }: FtrProviderContext) => {
|
||||||
.expect(404);
|
.expect(404);
|
||||||
|
|
||||||
expect(body.error).to.eql('Not Found');
|
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 () {
|
describe('with deleteDestIndex setting', function () {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue