Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fe435c5
[search source] return rawResponse when search fails
nreese Oct 5, 2023
55656c3
pull error from new structure
nreese Oct 5, 2023
7921fd3
cleanup handleSearchError
nreese Oct 5, 2023
90de42d
reportSearchError
nreese Oct 5, 2023
7dc2d0d
tslint
nreese Oct 9, 2023
b79a744
send attributes to inspector instead of Error object
nreese Oct 9, 2023
43db56b
throw KbnSearchError when search fails
nreese Oct 9, 2023
17b9b84
fix integration tests
nreese Oct 9, 2023
1313601
[CI] Auto-commit changed files from 'node scripts/precommit_hook.js -…
kibanamachine Oct 9, 2023
9191337
lens tslint
nreese Oct 9, 2023
37403ee
cleanup
nreese Oct 9, 2023
cef72de
[CI] Auto-commit changed files from 'node scripts/precommit_hook.js -…
kibanamachine Oct 9, 2023
cfc6e46
timelines tslint
nreese Oct 9, 2023
78e32f4
[CI] Auto-commit changed files from 'node scripts/precommit_hook.js -…
kibanamachine Oct 9, 2023
7295273
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Oct 9, 2023
86e35f2
security_solution tslint
nreese Oct 9, 2023
e5b159b
[CI] Auto-commit changed files from 'node scripts/precommit_hook.js -…
kibanamachine Oct 9, 2023
5ce3155
fix unit tests
nreese Oct 9, 2023
e9c0137
[CI] Auto-commit changed files from 'node scripts/precommit_hook.js -…
kibanamachine Oct 9, 2023
fab4790
update expects for delete api test
nreese Oct 9, 2023
8d6693e
fix serverless verify_error
nreese Oct 10, 2023
fe0b2f8
Merge branch 'main' into kbn_search_error
kibanamachine Oct 10, 2023
127150b
fix integration tests
nreese Oct 10, 2023
8831278
Merge branch 'main' into kbn_search_error
kibanamachine Oct 11, 2023
034e64b
Merge branch 'main' into kbn_search_error
kibanamachine Oct 11, 2023
45b3a47
Merge branch 'main' into kbn_search_error
kibanamachine Oct 11, 2023
15ba4bf
skipping problematic test
MadameSheema Oct 12, 2023
91ece0d
Merge branch 'main' into kbn_search_error
MadameSheema Oct 12, 2023
940dcb5
Merge branch 'main' into kbn_search_error
kibanamachine Oct 22, 2023
a17c45c
remove skipping test as fix has been merged
nreese Oct 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/plugins/data/common/search/expressions/eql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
},
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/data/common/search/expressions/esdsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
},
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/data/common/search/expressions/esql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
});
},
})
);
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/data/common/search/expressions/essql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
});
},
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 0 additions & 1 deletion src/plugins/data/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ export type {
SerializedSearchSourceFields,
// errors
IEsError,
Reason,
WaitUntilNextSessionCompletesOptions,
SearchResponseWarning,
SearchResponseIncompleteWarning,
Expand Down
27 changes: 15 additions & 12 deletions src/plugins/data/public/search/errors/es_error.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { EsError } from './es_error';
import { IEsError } from './types';

describe('EsError', () => {
it('contains the same body as the wrapped error', () => {
Expand All @@ -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');
Expand All @@ -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.'
Expand Down
20 changes: 11 additions & 9 deletions src/plugins/data/public/search/errors/es_error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -20,26 +20,28 @@ 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' })
}`
);
this.attributes = err.attributes;
}

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>
</>
);
}
Expand Down
6 changes: 4 additions & 2 deletions src/plugins/data/public/search/errors/painless_error.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/data/public/search/errors/painless_error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
34 changes: 4 additions & 30 deletions src/plugins/data/public/search/errors/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { 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;
};
}
import { estypes } from '@elastic/elasticsearch';
import { KibanaServerError } from '@kbn/kibana-utils-plugin/common';

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>;
Expand Down
29 changes: 12 additions & 17 deletions src/plugins/data/public/search/errors/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 = {
Expand All @@ -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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading