mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[search source] return rawResponse on search failure (#168389)
Closes https://github.com/elastic/kibana/issues/167099 #### Problem `/bsearch` and `/search` APIs only return `error` key from elasticsearch error response. This is problematic because Inspector needs `rawResponse` to populate "Clusters and shards" While working on this issue, I discovered another problem with how error responses are added to inspector requestResponder. The `Error` instance is added as `json` key. This is a little awkward since the response tab just stringifies the contents of `json`, thus stringifing the Error object instead of just the error body returned from API. This PR address this problem by setting `json` to either `attributes` or `{ message }`. #### Solution PR updates `/bsearch` and `/search` APIs to return `{ attributes: { error: ErrorCause, rawResponse }}` for failed responses. Solution avoided changing KbnServerError and reportServerError since these methods are used extensivly throughout Kibana (see https://github.com/elastic/kibana/pull/167544#discussion_r1342460941 for more details). Instead, KbnSearchError and reportSearchError are created to report search error messages. #### Test 1) install web logs sample data set 2) open discover 3) add filter ``` { "error_query": { "indices": [ { "error_type": "exception", "message": "local shard failure message 123", "name": "kibana_sample_data_logs", "shard_ids": [ 0 ] } ] } } ``` 4) Open inspector. Verify "Clusters and shards" tab is visible and populated. Verify "Response" tab shows "error" and "rawResponse" keys. <img width="500" alt="Screenshot 2023-10-09 at 9 29 16 AM" src="461b0eb0
-0502-4d48-a487-68025ef24d35"> <img width="500" alt="Screenshot 2023-10-09 at 9 29 06 AM" src="9aff41eb
-f771-48e3-a66d-1447689c2c6a"> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Gloria Hornero <gloria.hornero@elastic.co>
This commit is contained in:
parent
3587c20835
commit
adf3b8b436
34 changed files with 333 additions and 242 deletions
|
@ -166,7 +166,7 @@ export const getEqlFn = ({
|
|||
body: response.rawResponse,
|
||||
};
|
||||
} catch (e) {
|
||||
request.error({ json: e });
|
||||
request.error({ json: 'attributes' in e ? e.attributes : { message: e.message } });
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -188,7 +188,7 @@ export const getEsdslFn = ({
|
|||
body: rawResponse,
|
||||
};
|
||||
} catch (e) {
|
||||
request.error({ json: e });
|
||||
request.error({ json: 'attributes' in e ? e.attributes : { message: e.message } });
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -227,7 +227,9 @@ export const getEsqlFn = ({ getStartDependencies }: EsqlFnArguments) => {
|
|||
.ok({ json: rawResponse });
|
||||
},
|
||||
error(error) {
|
||||
logInspectorRequest().error({ json: error });
|
||||
logInspectorRequest().error({
|
||||
json: 'attributes' in error ? error.attributes : { message: error.message },
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
|
|
|
@ -248,7 +248,9 @@ export const getEssqlFn = ({ getStartDependencies }: EssqlFnArguments) => {
|
|||
.ok({ json: rawResponse });
|
||||
},
|
||||
error(error) {
|
||||
logInspectorRequest().error({ json: error });
|
||||
logInspectorRequest().error({
|
||||
json: 'attributes' in error ? error.attributes : { message: error.message },
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
|
|
|
@ -458,7 +458,9 @@ export class SearchSource {
|
|||
const last$ = s$
|
||||
.pipe(
|
||||
catchError((e) => {
|
||||
requestResponder?.error({ json: e });
|
||||
requestResponder?.error({
|
||||
json: 'attributes' in e ? e.attributes : { message: e.message },
|
||||
});
|
||||
return EMPTY;
|
||||
}),
|
||||
last(undefined, null),
|
||||
|
|
|
@ -167,7 +167,6 @@ export type {
|
|||
SerializedSearchSourceFields,
|
||||
// errors
|
||||
IEsError,
|
||||
Reason,
|
||||
WaitUntilNextSessionCompletesOptions,
|
||||
} from './search';
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import { EsError } from './es_error';
|
||||
import { IEsError } from './types';
|
||||
|
||||
describe('EsError', () => {
|
||||
it('contains the same body as the wrapped error', () => {
|
||||
|
@ -19,7 +20,7 @@ describe('EsError', () => {
|
|||
reason: 'top-level reason',
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
} as IEsError;
|
||||
const esError = new EsError(error);
|
||||
|
||||
expect(typeof esError.attributes).toEqual('object');
|
||||
|
@ -33,20 +34,22 @@ describe('EsError', () => {
|
|||
'x_content_parse_exception: [x_content_parse_exception] Reason: [1:78] [date_histogram] failed to parse field [calendar_interval]',
|
||||
statusCode: 400,
|
||||
attributes: {
|
||||
root_cause: [
|
||||
{
|
||||
type: 'x_content_parse_exception',
|
||||
reason: '[1:78] [date_histogram] failed to parse field [calendar_interval]',
|
||||
error: {
|
||||
root_cause: [
|
||||
{
|
||||
type: 'x_content_parse_exception',
|
||||
reason: '[1:78] [date_histogram] failed to parse field [calendar_interval]',
|
||||
},
|
||||
],
|
||||
type: 'x_content_parse_exception',
|
||||
reason: '[1:78] [date_histogram] failed to parse field [calendar_interval]',
|
||||
caused_by: {
|
||||
type: 'illegal_argument_exception',
|
||||
reason: 'The supplied interval [2q] could not be parsed as a calendar interval.',
|
||||
},
|
||||
],
|
||||
type: 'x_content_parse_exception',
|
||||
reason: '[1:78] [date_histogram] failed to parse field [calendar_interval]',
|
||||
caused_by: {
|
||||
type: 'illegal_argument_exception',
|
||||
reason: 'The supplied interval [2q] could not be parsed as a calendar interval.',
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
} as IEsError;
|
||||
const esError = new EsError(error);
|
||||
expect(esError.message).toEqual(
|
||||
'EsError: The supplied interval [2q] could not be parsed as a calendar interval.'
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiCodeBlock, EuiSpacer } from '@elastic/eui';
|
||||
import { ApplicationStart } from '@kbn/core/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ApplicationStart } from '@kbn/core/public';
|
||||
import { KbnError } from '@kbn/kibana-utils-plugin/common';
|
||||
import { IEsError } from './types';
|
||||
import { getRootCause } from './utils';
|
||||
|
@ -20,7 +20,7 @@ export class EsError extends KbnError {
|
|||
constructor(protected readonly err: IEsError) {
|
||||
super(
|
||||
`EsError: ${
|
||||
getRootCause(err)?.reason ||
|
||||
getRootCause(err?.attributes?.error)?.reason ||
|
||||
i18n.translate('data.esError.unknownRootCause', { defaultMessage: 'unknown' })
|
||||
}`
|
||||
);
|
||||
|
@ -28,18 +28,20 @@ export class EsError extends KbnError {
|
|||
}
|
||||
|
||||
public getErrorMessage(application: ApplicationStart) {
|
||||
const rootCause = getRootCause(this.err)?.reason;
|
||||
const topLevelCause = this.attributes?.reason;
|
||||
if (!this.attributes?.error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rootCause = getRootCause(this.attributes.error)?.reason;
|
||||
const topLevelCause = this.attributes.error.reason;
|
||||
const cause = rootCause ?? topLevelCause;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
{cause ? (
|
||||
<EuiCodeBlock data-test-subj="errMessage" isCopyable={true} paddingSize="s">
|
||||
{cause}
|
||||
</EuiCodeBlock>
|
||||
) : null}
|
||||
<EuiCodeBlock data-test-subj="errMessage" isCopyable={true} paddingSize="s">
|
||||
{cause}
|
||||
</EuiCodeBlock>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,11 +23,13 @@ describe('PainlessError', () => {
|
|||
const e = new PainlessError({
|
||||
statusCode: 400,
|
||||
message: 'search_phase_execution_exception',
|
||||
attributes: searchPhaseException.error,
|
||||
attributes: {
|
||||
error: searchPhaseException.error,
|
||||
},
|
||||
});
|
||||
const component = mount(e.getErrorMessage(startMock.application));
|
||||
|
||||
const failedShards = e.attributes?.failed_shards![0];
|
||||
const failedShards = searchPhaseException.error.failed_shards![0];
|
||||
|
||||
const stackTraceElem = findTestSubject(component, 'painlessStackTrace').getDOMNode();
|
||||
const stackTrace = failedShards!.reason.script_stack!.splice(-2).join('\n');
|
||||
|
|
|
@ -31,7 +31,7 @@ export class PainlessError extends EsError {
|
|||
});
|
||||
}
|
||||
|
||||
const rootCause = getRootCause(this.err);
|
||||
const rootCause = getRootCause(this.err.attributes?.error);
|
||||
const scriptFromStackTrace = rootCause?.script_stack
|
||||
? rootCause?.script_stack?.slice(-2).join('\n')
|
||||
: undefined;
|
||||
|
@ -78,7 +78,7 @@ export class PainlessError extends EsError {
|
|||
export function isPainlessError(err: Error | IEsError) {
|
||||
if (!isEsError(err)) return false;
|
||||
|
||||
const rootCause = getRootCause(err as IEsError);
|
||||
const rootCause = getRootCause((err as IEsError).attributes?.error);
|
||||
if (!rootCause) return false;
|
||||
|
||||
const { lang } = rootCause;
|
||||
|
|
|
@ -5,39 +5,13 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { KibanaServerError } from '@kbn/kibana-utils-plugin/common';
|
||||
|
||||
export interface FailedShard {
|
||||
shard: number;
|
||||
index: string;
|
||||
node: string;
|
||||
reason: Reason;
|
||||
}
|
||||
|
||||
export interface Reason {
|
||||
type: string;
|
||||
reason?: string;
|
||||
script_stack?: string[];
|
||||
position?: {
|
||||
offset: number;
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
lang?: estypes.ScriptLanguage;
|
||||
script?: string;
|
||||
caused_by?: {
|
||||
type: string;
|
||||
reason: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface IEsErrorAttributes {
|
||||
type: string;
|
||||
reason: string;
|
||||
root_cause?: Reason[];
|
||||
failed_shards?: FailedShard[];
|
||||
caused_by?: IEsErrorAttributes;
|
||||
rawResponse?: estypes.SearchResponseBody;
|
||||
error?: estypes.ErrorCause;
|
||||
}
|
||||
|
||||
export type IEsError = KibanaServerError<IEsErrorAttributes>;
|
||||
|
|
|
@ -5,26 +5,21 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { ErrorCause } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { KibanaServerError } from '@kbn/kibana-utils-plugin/common';
|
||||
import type { FailedShard, Reason } from './types';
|
||||
|
||||
export function getFailedShards(err: KibanaServerError<any>): FailedShard | undefined {
|
||||
const errorInfo = err.attributes;
|
||||
const failedShards = errorInfo?.failed_shards || errorInfo?.caused_by?.failed_shards;
|
||||
return failedShards ? failedShards[0] : undefined;
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
|
||||
function getFailedShardCause(error: estypes.ErrorCause): estypes.ErrorCause | undefined {
|
||||
const failedShards = error.failed_shards || error.caused_by?.failed_shards;
|
||||
return failedShards ? failedShards[0]?.reason : undefined;
|
||||
}
|
||||
|
||||
function getNestedCause(err: KibanaServerError | ErrorCause): Reason {
|
||||
const attr = ((err as KibanaServerError).attributes || err) as ErrorCause;
|
||||
const { type, reason, caused_by: causedBy } = attr;
|
||||
if (causedBy) {
|
||||
return getNestedCause(causedBy);
|
||||
}
|
||||
return { type, reason };
|
||||
function getNestedCause(error: estypes.ErrorCause): estypes.ErrorCause {
|
||||
return error.caused_by ? getNestedCause(error.caused_by) : error;
|
||||
}
|
||||
|
||||
export function getRootCause(err: KibanaServerError) {
|
||||
// Give shard failures priority, then try to get the error navigating nested objects
|
||||
return getFailedShards(err)?.reason || getNestedCause(err);
|
||||
export function getRootCause(error?: estypes.ErrorCause): estypes.ErrorCause | undefined {
|
||||
return error
|
||||
? // Give shard failures priority, then try to get the error navigating nested objects
|
||||
getFailedShardCause(error) || getNestedCause(error)
|
||||
: undefined;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import * as resourceNotFoundException from '../../../common/search/test_data/res
|
|||
import { BehaviorSubject } from 'rxjs';
|
||||
import { dataPluginMock } from '../../mocks';
|
||||
import { UI_SETTINGS } from '../../../common';
|
||||
import type { IEsError } from '../errors';
|
||||
|
||||
jest.mock('./utils', () => {
|
||||
const originalModule = jest.requireActual('./utils');
|
||||
|
@ -151,7 +152,9 @@ describe('SearchInterceptor', () => {
|
|||
new PainlessError({
|
||||
statusCode: 400,
|
||||
message: 'search_phase_execution_exception',
|
||||
attributes: searchPhaseException.error,
|
||||
attributes: {
|
||||
error: searchPhaseException.error,
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(mockCoreSetup.notifications.toasts.addDanger).toBeCalledTimes(1);
|
||||
|
@ -1452,10 +1455,12 @@ describe('SearchInterceptor', () => {
|
|||
});
|
||||
|
||||
test('Should throw Painless error on server error with OSS format', async () => {
|
||||
const mockResponse: any = {
|
||||
const mockResponse: IEsError = {
|
||||
statusCode: 400,
|
||||
message: 'search_phase_execution_exception',
|
||||
attributes: searchPhaseException.error,
|
||||
attributes: {
|
||||
error: searchPhaseException.error,
|
||||
},
|
||||
};
|
||||
fetchMock.mockRejectedValueOnce(mockResponse);
|
||||
const mockRequest: IEsSearchRequest = {
|
||||
|
@ -1466,10 +1471,12 @@ describe('SearchInterceptor', () => {
|
|||
});
|
||||
|
||||
test('Should throw ES error on ES server error', async () => {
|
||||
const mockResponse: any = {
|
||||
const mockResponse: IEsError = {
|
||||
statusCode: 400,
|
||||
message: 'resource_not_found_exception',
|
||||
attributes: resourceNotFoundException.error,
|
||||
attributes: {
|
||||
error: resourceNotFoundException.error,
|
||||
},
|
||||
};
|
||||
fetchMock.mockRejectedValueOnce(mockResponse);
|
||||
const mockRequest: IEsSearchRequest = {
|
||||
|
|
|
@ -194,18 +194,18 @@ export class SearchInterceptor {
|
|||
// The timeout error is shown any time a request times out, or once per session, if the request is part of a session.
|
||||
this.showTimeoutError(err, options?.sessionId);
|
||||
return err;
|
||||
} else if (e instanceof AbortError || e instanceof BfetchRequestError) {
|
||||
}
|
||||
|
||||
if (e instanceof AbortError || e instanceof BfetchRequestError) {
|
||||
// In the case an application initiated abort, throw the existing AbortError, same with BfetchRequestErrors
|
||||
return e;
|
||||
} else if (isEsError(e)) {
|
||||
if (isPainlessError(e)) {
|
||||
return new PainlessError(e, options?.indexPattern);
|
||||
} else {
|
||||
return new EsError(e);
|
||||
}
|
||||
} else {
|
||||
return e instanceof Error ? e : new Error(e.message);
|
||||
}
|
||||
|
||||
if (isEsError(e)) {
|
||||
return isPainlessError(e) ? new PainlessError(e, options?.indexPattern) : new EsError(e);
|
||||
}
|
||||
|
||||
return e instanceof Error ? e : new Error(e.message);
|
||||
}
|
||||
|
||||
private getSerializableOptions(options?: ISearchOptions) {
|
||||
|
|
60
src/plugins/data/server/search/report_search_error.ts
Normal file
60
src/plugins/data/server/search/report_search_error.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
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { errors } from '@elastic/elasticsearch';
|
||||
import { KibanaResponseFactory } from '@kbn/core/server';
|
||||
import { KbnError } from '@kbn/kibana-utils-plugin/common';
|
||||
|
||||
// Why not use just use kibana-utils-plugin KbnServerError and reportServerError?
|
||||
//
|
||||
// Search errors need to surface additional information
|
||||
// such as rawResponse and sanitized requestParams.
|
||||
// KbnServerError and reportServerError are used widely throughtout Kibana.
|
||||
// KbnSearchError and reportSearchError exist to avoid polluting
|
||||
// non-search usages of KbnServerError and reportServerError with extra information.
|
||||
export class KbnSearchError extends KbnError {
|
||||
public errBody?: Record<string, any>;
|
||||
constructor(message: string, public readonly statusCode: number, errBody?: Record<string, any>) {
|
||||
super(message);
|
||||
this.errBody = errBody;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats any error thrown into a standardized `KbnSearchError`.
|
||||
* @param e `Error` or `ElasticsearchClientError`
|
||||
* @returns `KbnSearchError`
|
||||
*/
|
||||
export function getKbnSearchError(e: Error) {
|
||||
if (e instanceof KbnSearchError) return e;
|
||||
return new KbnSearchError(
|
||||
e.message ?? 'Unknown error',
|
||||
e instanceof errors.ResponseError ? e.statusCode! : 500,
|
||||
e instanceof errors.ResponseError ? e.body : undefined
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param res Formats a `KbnSearchError` into a server error response
|
||||
* @param err
|
||||
*/
|
||||
export function reportSearchError(res: KibanaResponseFactory, err: KbnSearchError) {
|
||||
return res.customError({
|
||||
statusCode: err.statusCode ?? 500,
|
||||
body: {
|
||||
message: err.message,
|
||||
attributes: err.errBody
|
||||
? {
|
||||
error: err.errBody.error,
|
||||
rawResponse: err.errBody.response,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
}
|
|
@ -48,7 +48,12 @@ export function registerBsearchRoute(
|
|||
throw {
|
||||
message: err.message,
|
||||
statusCode: err.statusCode,
|
||||
attributes: err.errBody?.error,
|
||||
attributes: err.errBody
|
||||
? {
|
||||
error: err.errBody.error,
|
||||
rawResponse: err.errBody.response,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
})
|
||||
)
|
||||
|
|
|
@ -14,13 +14,13 @@ import { registerSearchRoute } from './search';
|
|||
import { DataPluginStart } from '../../plugin';
|
||||
import * as searchPhaseException from '../../../common/search/test_data/search_phase_execution_exception.json';
|
||||
import * as indexNotFoundException from '../../../common/search/test_data/index_not_found_exception.json';
|
||||
import { KbnServerError } from '@kbn/kibana-utils-plugin/server';
|
||||
import { KbnSearchError } from '../report_search_error';
|
||||
|
||||
describe('Search service', () => {
|
||||
let mockCoreSetup: MockedKeys<CoreSetup<{}, DataPluginStart>>;
|
||||
|
||||
function mockEsError(message: string, statusCode: number, attributes?: Record<string, any>) {
|
||||
return new KbnServerError(message, statusCode, attributes);
|
||||
function mockEsError(message: string, statusCode: number, errBody?: Record<string, any>) {
|
||||
return new KbnSearchError(message, statusCode, errBody);
|
||||
}
|
||||
|
||||
async function runMockSearch(mockContext: any, mockRequest: any, mockResponse: any) {
|
||||
|
@ -112,7 +112,10 @@ describe('Search service', () => {
|
|||
const error: any = mockResponse.customError.mock.calls[0][0];
|
||||
expect(error.statusCode).toBe(400);
|
||||
expect(error.body.message).toBe('search_phase_execution_exception');
|
||||
expect(error.body.attributes).toBe(searchPhaseException.error);
|
||||
expect(error.body.attributes).toEqual({
|
||||
error: searchPhaseException.error,
|
||||
rawResponse: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('handler returns an error response if the search throws an index not found error', async () => {
|
||||
|
@ -138,7 +141,10 @@ describe('Search service', () => {
|
|||
const error: any = mockResponse.customError.mock.calls[0][0];
|
||||
expect(error.statusCode).toBe(404);
|
||||
expect(error.body.message).toBe('index_not_found_exception');
|
||||
expect(error.body.attributes).toBe(indexNotFoundException.error);
|
||||
expect(error.body.attributes).toEqual({
|
||||
error: indexNotFoundException.error,
|
||||
rawResponse: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('handler returns an error response if the search throws a general error', async () => {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { first } from 'rxjs/operators';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { reportServerError } from '@kbn/kibana-utils-plugin/server';
|
||||
import { reportSearchError } from '../report_search_error';
|
||||
import { getRequestAbortedSignal } from '../../lib';
|
||||
import type { DataPluginRouter } from '../types';
|
||||
|
||||
|
@ -71,7 +72,7 @@ export function registerSearchRoute(router: DataPluginRouter): void {
|
|||
|
||||
return res.ok({ body: response });
|
||||
} catch (err) {
|
||||
return reportServerError(res, err);
|
||||
return reportSearchError(res, err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -14,7 +14,7 @@ import { SearchStrategyDependencies } from '../../types';
|
|||
|
||||
import * as indexNotFoundException from '../../../../common/search/test_data/index_not_found_exception.json';
|
||||
import { errors } from '@elastic/elasticsearch';
|
||||
import { KbnServerError } from '@kbn/kibana-utils-plugin/server';
|
||||
import { KbnSearchError } from '../../report_search_error';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
describe('ES search strategy', () => {
|
||||
|
@ -150,7 +150,7 @@ describe('ES search strategy', () => {
|
|||
.toPromise();
|
||||
} catch (e) {
|
||||
expect(esClient.search).toBeCalled();
|
||||
expect(e).toBeInstanceOf(KbnServerError);
|
||||
expect(e).toBeInstanceOf(KbnSearchError);
|
||||
expect(e.statusCode).toBe(404);
|
||||
expect(e.message).toBe(errResponse.message);
|
||||
expect(e.errBody).toBe(indexNotFoundException);
|
||||
|
@ -167,7 +167,7 @@ describe('ES search strategy', () => {
|
|||
.toPromise();
|
||||
} catch (e) {
|
||||
expect(esClient.search).toBeCalled();
|
||||
expect(e).toBeInstanceOf(KbnServerError);
|
||||
expect(e).toBeInstanceOf(KbnSearchError);
|
||||
expect(e.statusCode).toBe(500);
|
||||
expect(e.message).toBe(errResponse.message);
|
||||
expect(e.errBody).toBe(undefined);
|
||||
|
@ -184,14 +184,14 @@ describe('ES search strategy', () => {
|
|||
.toPromise();
|
||||
} catch (e) {
|
||||
expect(esClient.search).toBeCalled();
|
||||
expect(e).toBeInstanceOf(KbnServerError);
|
||||
expect(e).toBeInstanceOf(KbnSearchError);
|
||||
expect(e.statusCode).toBe(500);
|
||||
expect(e.message).toBe(errResponse.message);
|
||||
expect(e.errBody).toBe(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
it('throws KbnServerError for unknown index type', async () => {
|
||||
it('throws KbnSearchError for unknown index type', async () => {
|
||||
const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' };
|
||||
|
||||
try {
|
||||
|
@ -200,7 +200,7 @@ describe('ES search strategy', () => {
|
|||
.toPromise();
|
||||
} catch (e) {
|
||||
expect(esClient.search).not.toBeCalled();
|
||||
expect(e).toBeInstanceOf(KbnServerError);
|
||||
expect(e).toBeInstanceOf(KbnSearchError);
|
||||
expect(e.message).toBe('Unsupported index pattern type banana');
|
||||
expect(e.statusCode).toBe(400);
|
||||
expect(e.errBody).toBe(undefined);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import { firstValueFrom, from, Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import type { Logger, SharedGlobalConfig } from '@kbn/core/server';
|
||||
import { getKbnServerError, KbnServerError } from '@kbn/kibana-utils-plugin/server';
|
||||
import { getKbnSearchError, KbnSearchError } from '../../report_search_error';
|
||||
import type { ISearchStrategy } from '../../types';
|
||||
import type { SearchUsage } from '../../collectors/search';
|
||||
import { getDefaultSearchParams, getShardTimeout } from './request_utils';
|
||||
|
@ -25,14 +25,14 @@ export const esSearchStrategyProvider = (
|
|||
* @param request
|
||||
* @param options
|
||||
* @param deps
|
||||
* @throws `KbnServerError`
|
||||
* @throws `KbnSearchError`
|
||||
* @returns `Observable<IEsSearchResponse<any>>`
|
||||
*/
|
||||
search: (request, { abortSignal, transport, ...options }, { esClient, uiSettingsClient }) => {
|
||||
// Only default index pattern type is supported here.
|
||||
// See ese for other type support.
|
||||
if (request.indexType) {
|
||||
throw new KbnServerError(`Unsupported index pattern type ${request.indexType}`, 400);
|
||||
throw new KbnSearchError(`Unsupported index pattern type ${request.indexType}`, 400);
|
||||
}
|
||||
|
||||
const isPit = request.params?.body?.pit != null;
|
||||
|
@ -57,7 +57,7 @@ export const esSearchStrategyProvider = (
|
|||
const response = shimHitsTotal(body, options);
|
||||
return toKibanaSearchResponse(response);
|
||||
} catch (e) {
|
||||
throw getKbnServerError(e);
|
||||
throw getKbnSearchError(e);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
||||
import { KbnServerError } from '@kbn/kibana-utils-plugin/server';
|
||||
import { KbnSearchError } from '../../report_search_error';
|
||||
import { errors } from '@elastic/elasticsearch';
|
||||
import * as indexNotFoundException from '../../../../common/search/test_data/index_not_found_exception.json';
|
||||
import * as xContentParseException from '../../../../common/search/test_data/x_content_parse_exception.json';
|
||||
|
@ -456,14 +457,14 @@ describe('ES search strategy', () => {
|
|||
mockLogger
|
||||
);
|
||||
|
||||
let err: KbnServerError | undefined;
|
||||
let err: KbnSearchError | undefined;
|
||||
try {
|
||||
await esSearch.search({ params }, {}, mockDeps).toPromise();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
expect(mockSubmitCaller).toBeCalled();
|
||||
expect(err).toBeInstanceOf(KbnServerError);
|
||||
expect(err).toBeInstanceOf(KbnSearchError);
|
||||
expect(err?.statusCode).toBe(404);
|
||||
expect(err?.message).toBe(errResponse.message);
|
||||
expect(err?.errBody).toBe(indexNotFoundException);
|
||||
|
@ -481,14 +482,14 @@ describe('ES search strategy', () => {
|
|||
mockLogger
|
||||
);
|
||||
|
||||
let err: KbnServerError | undefined;
|
||||
let err: KbnSearchError | undefined;
|
||||
try {
|
||||
await esSearch.search({ params }, {}, mockDeps).toPromise();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
expect(mockSubmitCaller).toBeCalled();
|
||||
expect(err).toBeInstanceOf(KbnServerError);
|
||||
expect(err).toBeInstanceOf(KbnSearchError);
|
||||
expect(err?.statusCode).toBe(500);
|
||||
expect(err?.message).toBe(errResponse.message);
|
||||
expect(err?.errBody).toBe(undefined);
|
||||
|
|
|
@ -11,7 +11,8 @@ import type { IScopedClusterClient, Logger, SharedGlobalConfig } from '@kbn/core
|
|||
import { catchError, tap } from 'rxjs/operators';
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { firstValueFrom, from } from 'rxjs';
|
||||
import { getKbnServerError, KbnServerError } from '@kbn/kibana-utils-plugin/server';
|
||||
import { getKbnServerError } from '@kbn/kibana-utils-plugin/server';
|
||||
import { getKbnSearchError, KbnSearchError } from '../../report_search_error';
|
||||
import type { ISearchStrategy, SearchStrategyDependencies } from '../../types';
|
||||
import type {
|
||||
IAsyncSearchOptions,
|
||||
|
@ -94,7 +95,7 @@ export const enhancedEsSearchStrategyProvider = (
|
|||
tap((response) => (id = response.id)),
|
||||
tap(searchUsageObserver(logger, usage)),
|
||||
catchError((e) => {
|
||||
throw getKbnServerError(e);
|
||||
throw getKbnSearchError(e);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -136,7 +137,7 @@ export const enhancedEsSearchStrategyProvider = (
|
|||
...getTotalLoaded(response),
|
||||
};
|
||||
} catch (e) {
|
||||
throw getKbnServerError(e);
|
||||
throw getKbnSearchError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,12 +147,12 @@ export const enhancedEsSearchStrategyProvider = (
|
|||
* @param options
|
||||
* @param deps `SearchStrategyDependencies`
|
||||
* @returns `Observable<IEsSearchResponse<any>>`
|
||||
* @throws `KbnServerError`
|
||||
* @throws `KbnSearchError`
|
||||
*/
|
||||
search: (request, options: IAsyncSearchOptions, deps) => {
|
||||
logger.debug(`search ${JSON.stringify(request.params) || request.id}`);
|
||||
if (request.indexType && request.indexType !== 'rollup') {
|
||||
throw new KbnServerError('Unknown indexType', 400);
|
||||
throw new KbnSearchError('Unknown indexType', 400);
|
||||
}
|
||||
|
||||
if (request.indexType === undefined || !deps.rollupsEnabled) {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { from } from 'rxjs';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import { getKbnServerError, KbnServerError } from '@kbn/kibana-utils-plugin/server';
|
||||
import { getKbnSearchError, KbnSearchError } from '../../report_search_error';
|
||||
import type { ISearchStrategy } from '../../types';
|
||||
|
||||
const ES_TIMEOUT_IN_MS = 120000;
|
||||
|
@ -21,7 +21,7 @@ export const esqlSearchStrategyProvider = (
|
|||
* @param request
|
||||
* @param options
|
||||
* @param deps
|
||||
* @throws `KbnServerError`
|
||||
* @throws `KbnSearchError`
|
||||
* @returns `Observable<IEsSearchResponse<any>>`
|
||||
*/
|
||||
search: (request, { abortSignal, ...options }, { esClient, uiSettingsClient }) => {
|
||||
|
@ -39,7 +39,7 @@ export const esqlSearchStrategyProvider = (
|
|||
// Only default index pattern type is supported here.
|
||||
// See ese for other type support.
|
||||
if (request.indexType) {
|
||||
throw new KbnServerError(`Unsupported index pattern type ${request.indexType}`, 400);
|
||||
throw new KbnSearchError(`Unsupported index pattern type ${request.indexType}`, 400);
|
||||
}
|
||||
|
||||
const search = async () => {
|
||||
|
@ -67,7 +67,7 @@ export const esqlSearchStrategyProvider = (
|
|||
warning: headers?.warning,
|
||||
};
|
||||
} catch (e) {
|
||||
throw getKbnServerError(e);
|
||||
throw getKbnSearchError(e);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { merge } from 'lodash';
|
||||
import { KbnServerError } from '@kbn/kibana-utils-plugin/server';
|
||||
import { KbnSearchError } from '../../report_search_error';
|
||||
import { errors } from '@elastic/elasticsearch';
|
||||
import * as indexNotFoundException from '../../../../common/search/test_data/index_not_found_exception.json';
|
||||
import { SearchStrategyDependencies } from '../../types';
|
||||
|
@ -221,14 +221,14 @@ describe('SQL search strategy', () => {
|
|||
};
|
||||
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
|
||||
|
||||
let err: KbnServerError | undefined;
|
||||
let err: KbnSearchError | undefined;
|
||||
try {
|
||||
await esSearch.search({ params }, {}, mockDeps).toPromise();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
expect(mockSqlQuery).toBeCalled();
|
||||
expect(err).toBeInstanceOf(KbnServerError);
|
||||
expect(err).toBeInstanceOf(KbnSearchError);
|
||||
expect(err?.statusCode).toBe(404);
|
||||
expect(err?.message).toBe(errResponse.message);
|
||||
expect(err?.errBody).toBe(indexNotFoundException);
|
||||
|
@ -245,14 +245,14 @@ describe('SQL search strategy', () => {
|
|||
};
|
||||
const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger);
|
||||
|
||||
let err: KbnServerError | undefined;
|
||||
let err: KbnSearchError | undefined;
|
||||
try {
|
||||
await esSearch.search({ params }, {}, mockDeps).toPromise();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
expect(mockSqlQuery).toBeCalled();
|
||||
expect(err).toBeInstanceOf(KbnServerError);
|
||||
expect(err).toBeInstanceOf(KbnSearchError);
|
||||
expect(err?.statusCode).toBe(500);
|
||||
expect(err?.message).toBe(errResponse.message);
|
||||
expect(err?.errBody).toBe(undefined);
|
||||
|
|
|
@ -11,6 +11,7 @@ import type { IScopedClusterClient, Logger } from '@kbn/core/server';
|
|||
import { catchError, tap } from 'rxjs/operators';
|
||||
import { SqlQueryResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { getKbnServerError } from '@kbn/kibana-utils-plugin/server';
|
||||
import { getKbnSearchError } from '../../report_search_error';
|
||||
import type { ISearchStrategy, SearchStrategyDependencies } from '../../types';
|
||||
import type {
|
||||
IAsyncSearchOptions,
|
||||
|
@ -94,7 +95,7 @@ export const sqlSearchStrategyProvider = (
|
|||
}).pipe(
|
||||
tap((response) => (id = response.id)),
|
||||
catchError((e) => {
|
||||
throw getKbnServerError(e);
|
||||
throw getKbnSearchError(e);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -105,7 +106,7 @@ export const sqlSearchStrategyProvider = (
|
|||
* @param options
|
||||
* @param deps `SearchStrategyDependencies`
|
||||
* @returns `Observable<IEsSearchResponse<any>>`
|
||||
* @throws `KbnServerError`
|
||||
* @throws `KbnSearchError`
|
||||
*/
|
||||
search: (request, options: IAsyncSearchOptions, deps) => {
|
||||
logger.debug(`sql search: search request=${JSON.stringify(request)}`);
|
||||
|
|
|
@ -20,7 +20,8 @@ export const verifyErrorResponse = (
|
|||
}
|
||||
if (shouldHaveAttrs) {
|
||||
expect(r).to.have.property('attributes');
|
||||
expect(r.attributes).to.have.property('root_cause');
|
||||
expect(r.attributes).to.have.property('error');
|
||||
expect(r.attributes.error).to.have.property('root_cause');
|
||||
} else {
|
||||
expect(r).not.to.have.property('attributes');
|
||||
}
|
||||
|
|
|
@ -21,6 +21,45 @@ const runtimeFieldError = {
|
|||
message: 'status_exception',
|
||||
statusCode: 400,
|
||||
attributes: {
|
||||
error: {
|
||||
type: 'status_exception',
|
||||
reason: 'error while executing search',
|
||||
caused_by: {
|
||||
type: 'search_phase_execution_exception',
|
||||
reason: 'all shards failed',
|
||||
phase: 'query',
|
||||
grouped: true,
|
||||
failed_shards: [
|
||||
{
|
||||
shard: 0,
|
||||
index: 'indexpattern_source',
|
||||
node: 'jtqB1-UhQluyjeXIpQFqAA',
|
||||
reason: {
|
||||
type: 'script_exception',
|
||||
reason: 'runtime error',
|
||||
script_stack: [
|
||||
'java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:68)',
|
||||
'java.base/java.lang.Integer.parseInt(Integer.java:652)',
|
||||
'java.base/java.lang.Integer.parseInt(Integer.java:770)',
|
||||
"emit(Integer.parseInt('hello'))",
|
||||
' ^---- HERE',
|
||||
],
|
||||
script: "emit(Integer.parseInt('hello'))",
|
||||
lang: 'painless',
|
||||
position: { offset: 12, start: 0, end: 31 },
|
||||
caused_by: {
|
||||
type: 'number_format_exception',
|
||||
reason: 'For input string: "hello"',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
error: {
|
||||
type: 'status_exception',
|
||||
reason: 'error while executing search',
|
||||
caused_by: {
|
||||
|
@ -53,38 +92,6 @@ const runtimeFieldError = {
|
|||
},
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
type: 'status_exception',
|
||||
reason: 'error while executing search',
|
||||
caused_by: {
|
||||
type: 'search_phase_execution_exception',
|
||||
reason: 'all shards failed',
|
||||
phase: 'query',
|
||||
grouped: true,
|
||||
failed_shards: [
|
||||
{
|
||||
shard: 0,
|
||||
index: 'indexpattern_source',
|
||||
node: 'jtqB1-UhQluyjeXIpQFqAA',
|
||||
reason: {
|
||||
type: 'script_exception',
|
||||
reason: 'runtime error',
|
||||
script_stack: [
|
||||
'java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:68)',
|
||||
'java.base/java.lang.Integer.parseInt(Integer.java:652)',
|
||||
'java.base/java.lang.Integer.parseInt(Integer.java:770)',
|
||||
"emit(Integer.parseInt('hello'))",
|
||||
' ^---- HERE',
|
||||
],
|
||||
script: "emit(Integer.parseInt('hello'))",
|
||||
lang: 'painless',
|
||||
position: { offset: 12, start: 0, end: 31 },
|
||||
caused_by: { type: 'number_format_exception', reason: 'For input string: "hello"' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -99,6 +106,31 @@ const scriptedFieldError = {
|
|||
message: 'status_exception',
|
||||
statusCode: 500,
|
||||
attributes: {
|
||||
error: {
|
||||
type: 'status_exception',
|
||||
reason: 'error while executing search',
|
||||
caused_by: {
|
||||
type: 'search_phase_execution_exception',
|
||||
reason: 'all shards failed',
|
||||
phase: 'query',
|
||||
grouped: true,
|
||||
failed_shards: [
|
||||
{
|
||||
shard: 0,
|
||||
index: 'indexpattern_source',
|
||||
node: 'jtqB1-UhQluyjeXIpQFqAA',
|
||||
reason: {
|
||||
type: 'aggregation_execution_exception',
|
||||
reason: 'Unsupported script value [hello], expected a number, date, or boolean',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
error: {
|
||||
type: 'status_exception',
|
||||
reason: 'error while executing search',
|
||||
caused_by: {
|
||||
|
@ -120,27 +152,6 @@ const scriptedFieldError = {
|
|||
},
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
type: 'status_exception',
|
||||
reason: 'error while executing search',
|
||||
caused_by: {
|
||||
type: 'search_phase_execution_exception',
|
||||
reason: 'all shards failed',
|
||||
phase: 'query',
|
||||
grouped: true,
|
||||
failed_shards: [
|
||||
{
|
||||
shard: 0,
|
||||
index: 'indexpattern_source',
|
||||
node: 'jtqB1-UhQluyjeXIpQFqAA',
|
||||
reason: {
|
||||
type: 'aggregation_execution_exception',
|
||||
reason: 'Unsupported script value [hello], expected a number, date, or boolean',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -174,41 +185,7 @@ const tsdbCounterUsedWithWrongOperationError = {
|
|||
name: 'Error',
|
||||
original: {
|
||||
attributes: {
|
||||
type: 'status_exception',
|
||||
reason: 'error while executing search',
|
||||
caused_by: {
|
||||
type: 'search_phase_execution_exception',
|
||||
reason: 'all shards failed',
|
||||
phase: 'query',
|
||||
grouped: true,
|
||||
failed_shards: [
|
||||
{
|
||||
shard: 0,
|
||||
index: 'tsdb_index',
|
||||
reason: {
|
||||
type: 'illegal_argument_exception',
|
||||
reason:
|
||||
'Field [bytes_counter] of type [long][counter] is not supported for aggregation [sum]',
|
||||
},
|
||||
},
|
||||
],
|
||||
caused_by: {
|
||||
type: 'illegal_argument_exception',
|
||||
reason:
|
||||
'Field [bytes_counter] of type [long][counter] is not supported for aggregation [sum]',
|
||||
caused_by: {
|
||||
type: 'illegal_argument_exception',
|
||||
reason:
|
||||
'Field [bytes_counter] of type [long][counter] is not supported for aggregation [sum]',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: {
|
||||
message:
|
||||
'status_exception\n\tCaused by:\n\t\tsearch_phase_execution_exception: all shards failed',
|
||||
statusCode: 400,
|
||||
attributes: {
|
||||
error: {
|
||||
type: 'status_exception',
|
||||
reason: 'error while executing search',
|
||||
caused_by: {
|
||||
|
@ -240,6 +217,44 @@ const tsdbCounterUsedWithWrongOperationError = {
|
|||
},
|
||||
},
|
||||
},
|
||||
err: {
|
||||
message:
|
||||
'status_exception\n\tCaused by:\n\t\tsearch_phase_execution_exception: all shards failed',
|
||||
statusCode: 400,
|
||||
attributes: {
|
||||
error: {
|
||||
type: 'status_exception',
|
||||
reason: 'error while executing search',
|
||||
caused_by: {
|
||||
type: 'search_phase_execution_exception',
|
||||
reason: 'all shards failed',
|
||||
phase: 'query',
|
||||
grouped: true,
|
||||
failed_shards: [
|
||||
{
|
||||
shard: 0,
|
||||
index: 'tsdb_index',
|
||||
reason: {
|
||||
type: 'illegal_argument_exception',
|
||||
reason:
|
||||
'Field [bytes_counter] of type [long][counter] is not supported for aggregation [sum]',
|
||||
},
|
||||
},
|
||||
],
|
||||
caused_by: {
|
||||
type: 'illegal_argument_exception',
|
||||
reason:
|
||||
'Field [bytes_counter] of type [long][counter] is not supported for aggregation [sum]',
|
||||
caused_by: {
|
||||
type: 'illegal_argument_exception',
|
||||
reason:
|
||||
'Field [bytes_counter] of type [long][counter] is not supported for aggregation [sum]',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -7,18 +7,16 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isEqual, uniqWith } from 'lodash';
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { ExpressionRenderError } from '@kbn/expressions-plugin/public';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { isEsError } from '@kbn/data-plugin/public';
|
||||
import type { IEsError, Reason } from '@kbn/data-plugin/public';
|
||||
import React from 'react';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import { RemovableUserMessage } from '../types';
|
||||
|
||||
type ErrorCause = Required<IEsError>['attributes'];
|
||||
|
||||
interface RequestError extends Error {
|
||||
body?: { attributes?: { error: { caused_by: ErrorCause } } };
|
||||
body?: { attributes?: { error: { caused_by: estypes.ErrorCause } } };
|
||||
}
|
||||
|
||||
interface ReasonDescription {
|
||||
|
@ -62,7 +60,7 @@ function getNestedErrorClauseWithContext({
|
|||
caused_by: causedBy,
|
||||
lang,
|
||||
script,
|
||||
}: Reason): ReasonDescription[] {
|
||||
}: estypes.ErrorCause): ReasonDescription[] {
|
||||
if (!causedBy) {
|
||||
// scripted fields error has changed with no particular hint about painless in it,
|
||||
// so it tries to lookup in the message for the script word
|
||||
|
@ -83,12 +81,12 @@ function getNestedErrorClauseWithContext({
|
|||
return [{ ...payload, context: { type, reason } }];
|
||||
}
|
||||
|
||||
function getNestedErrorClause(e: ErrorCause | Reason): ReasonDescription[] {
|
||||
function getNestedErrorClause(e: estypes.ErrorCause): ReasonDescription[] {
|
||||
const { type, reason = '', caused_by: causedBy } = e;
|
||||
// Painless scripts errors are nested within the failed_shards property
|
||||
if ('failed_shards' in e) {
|
||||
if (e.failed_shards) {
|
||||
return e.failed_shards.flatMap((shardCause) =>
|
||||
return (e.failed_shards as estypes.ShardFailure[]).flatMap((shardCause) =>
|
||||
getNestedErrorClauseWithContext(shardCause.reason)
|
||||
);
|
||||
}
|
||||
|
@ -101,13 +99,15 @@ function getNestedErrorClause(e: ErrorCause | Reason): ReasonDescription[] {
|
|||
|
||||
function getErrorSources(e: Error) {
|
||||
if (isRequestError(e)) {
|
||||
return getNestedErrorClause(e.body!.attributes!.error as ErrorCause);
|
||||
return getNestedErrorClause(e.body!.attributes!.error as estypes.ErrorCause);
|
||||
}
|
||||
if (isEsError(e)) {
|
||||
if (e.attributes?.reason) {
|
||||
return getNestedErrorClause(e.attributes);
|
||||
if (e.attributes?.error?.reason) {
|
||||
return getNestedErrorClause(e.attributes.error);
|
||||
}
|
||||
if (e.attributes?.error?.caused_by) {
|
||||
return getNestedErrorClause(e.attributes.error.caused_by);
|
||||
}
|
||||
return getNestedErrorClause(e.attributes?.caused_by as ErrorCause);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -234,7 +234,7 @@ describe('useAppToasts', () => {
|
|||
|
||||
it('prefers the attributes reason if we have it for the message', async () => {
|
||||
const error: IEsError = {
|
||||
attributes: { type: 'some type', reason: 'message we want' },
|
||||
attributes: { error: { type: 'some type', reason: 'message we want' } },
|
||||
statusCode: 200,
|
||||
message: 'message we do not want',
|
||||
};
|
||||
|
@ -244,11 +244,11 @@ describe('useAppToasts', () => {
|
|||
|
||||
it('works with an EsError, by using the inner error and not outer error if available', async () => {
|
||||
const error: MaybeESError = {
|
||||
attributes: { type: 'some type', reason: 'message we want' },
|
||||
attributes: { error: { type: 'some type', reason: 'message we want' } },
|
||||
statusCode: 400,
|
||||
err: {
|
||||
statusCode: 200,
|
||||
attributes: { reason: 'attribute message we do not want' },
|
||||
attributes: { error: { reason: 'attribute message we do not want' } },
|
||||
},
|
||||
message: 'main message we do not want',
|
||||
};
|
||||
|
@ -258,11 +258,11 @@ describe('useAppToasts', () => {
|
|||
|
||||
it('creates a stack trace of a EsError and not the outer object', async () => {
|
||||
const error: MaybeESError = {
|
||||
attributes: { type: 'some type', reason: 'message we do not want' },
|
||||
attributes: { error: { type: 'some type', reason: 'message we do not want' } },
|
||||
statusCode: 400,
|
||||
err: {
|
||||
statusCode: 200,
|
||||
attributes: { reason: 'attribute message we do want' },
|
||||
attributes: { error: { reason: 'attribute message we do want' } },
|
||||
},
|
||||
message: 'main message we do not want',
|
||||
};
|
||||
|
@ -270,7 +270,7 @@ describe('useAppToasts', () => {
|
|||
const parsedStack = JSON.parse(result.stack ?? '');
|
||||
expect(parsedStack).toEqual({
|
||||
statusCode: 200,
|
||||
attributes: { reason: 'attribute message we do want' },
|
||||
attributes: { error: { reason: 'attribute message we do want' } },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -98,8 +98,10 @@ export const esErrorToErrorStack = (error: IEsError & MaybeESError): Error => {
|
|||
? `(${error.statusCode})`
|
||||
: '';
|
||||
const stringifiedError = getStringifiedStack(maybeUnWrapped);
|
||||
const adaptedError = new Error(`${error.attributes?.reason ?? error.message} ${statusCode}`);
|
||||
adaptedError.name = error.attributes?.reason ?? error.message;
|
||||
const adaptedError = new Error(
|
||||
`${error.attributes?.error?.reason ?? error.message} ${statusCode}`
|
||||
);
|
||||
adaptedError.name = error.attributes?.error?.reason ?? error.message;
|
||||
if (stringifiedError != null) {
|
||||
adaptedError.stack = stringifiedError;
|
||||
}
|
||||
|
|
|
@ -93,8 +93,10 @@ export const esErrorToErrorStack = (error: IEsError & MaybeESError): Error => {
|
|||
? `(${error.statusCode})`
|
||||
: '';
|
||||
const stringifiedError = getStringifiedStack(maybeUnWrapped);
|
||||
const adaptedError = new Error(`${error.attributes?.reason ?? error.message} ${statusCode}`);
|
||||
adaptedError.name = error.attributes?.reason ?? error.message;
|
||||
const adaptedError = new Error(
|
||||
`${error.attributes?.error?.reason ?? error.message} ${statusCode}`
|
||||
);
|
||||
adaptedError.name = error.attributes?.error?.reason ?? error.message;
|
||||
if (stringifiedError != null) {
|
||||
adaptedError.stack = stringifiedError;
|
||||
}
|
||||
|
|
|
@ -411,7 +411,11 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
.set('kbn-xsrf', 'foo')
|
||||
.send()
|
||||
.expect(400);
|
||||
verifyErrorResponse(resp.body, 400, 'illegal_argument_exception', true);
|
||||
|
||||
expect(resp.body.statusCode).to.be(400);
|
||||
expect(resp.body.message).to.include.string('illegal_argument_exception');
|
||||
expect(resp.body).to.have.property('attributes');
|
||||
expect(resp.body.attributes).to.have.property('root_cause');
|
||||
});
|
||||
|
||||
it('should delete an in-progress search', async function () {
|
||||
|
|
|
@ -19,7 +19,8 @@ export const verifyErrorResponse = (
|
|||
}
|
||||
if (shouldHaveAttrs) {
|
||||
expect(r).to.have.property('attributes');
|
||||
expect(r.attributes).to.have.property('root_cause');
|
||||
expect(r.attributes).to.have.property('error');
|
||||
expect(r.attributes.error).to.have.property('root_cause');
|
||||
} else {
|
||||
expect(r).not.to.have.property('attributes');
|
||||
}
|
||||
|
|
|
@ -380,7 +380,10 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
.set('kbn-xsrf', 'foo')
|
||||
.send()
|
||||
.expect(400);
|
||||
verifyErrorResponse(resp.body, 400, 'illegal_argument_exception', true);
|
||||
expect(resp.body.statusCode).to.be(400);
|
||||
expect(resp.body.message).to.include.string('illegal_argument_exception');
|
||||
expect(resp.body).to.have.property('attributes');
|
||||
expect(resp.body.attributes).to.have.property('root_cause');
|
||||
});
|
||||
|
||||
it('should delete an in-progress search', async function () {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue