From 2b949c5eb3bad3cab465c61d7c162c6e1ea26012 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 10 Sep 2020 17:51:36 -0700 Subject: [PATCH] Add handleAPIError helper - New and improved from ent-search - this one automatically sets flash messages for you so you don't have to pass in a callback! --- .../flash_messages/handle_api_errors.test.ts | 50 +++++++++++++++++++ .../flash_messages/handle_api_errors.ts | 46 +++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.test.ts new file mode 100644 index 0000000000000..b04c8aff772f1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.test.ts @@ -0,0 +1,50 @@ +/* + * 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. + */ + +jest.mock('./', () => ({ + FlashMessagesLogic: { actions: { setFlashMessages: jest.fn() } }, +})); +import { FlashMessagesLogic } from './'; + +import { handleAPIError } from './handle_api_errors'; + +describe('handleAPIError', () => { + const mockHttpError = { + body: { + statusCode: 404, + error: 'Not Found', + message: 'Could not find X,Could not find Y,Something else bad happened', + attributes: { + errors: ['Could not find X', 'Could not find Y', 'Something else bad happened'], + }, + }, + } as any; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('converts API errors into flash messages', () => { + handleAPIError(mockHttpError); + + expect(FlashMessagesLogic.actions.setFlashMessages).toHaveBeenCalledWith([ + { type: 'error', message: 'Could not find X' }, + { type: 'error', message: 'Could not find Y' }, + { type: 'error', message: 'Something else bad happened' }, + ]); + }); + + it('displays a generic error message and re-throws non-API errors', () => { + try { + handleAPIError(Error('whatever') as any); + } catch (e) { + expect(e.message).toEqual('whatever'); + expect(FlashMessagesLogic.actions.setFlashMessages).toHaveBeenCalledWith([ + { type: 'error', message: 'An unexpected error occurred' }, + ]); + } + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts new file mode 100644 index 0000000000000..52744d67bf36c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/handle_api_errors.ts @@ -0,0 +1,46 @@ +/* + * 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 { HttpResponse } from 'src/core/public'; + +import { FlashMessagesLogic, IFlashMessage } from './'; + +/** + * The API errors we are handling can come from one of two ways: + * - When our http calls recieve a response containing an error code, such as a 404 or 500 + * - Our own JS while handling a successful response + * + * In the first case, if it is a purposeful error (like a 404) we will receive an + * `errors` property in the response's data, which will contain messages we can + * display to the user. + */ +interface IErrorResponse { + statusCode: number; + error: string; + message: string; + attributes: { + errors: string[]; + }; +} + +/** + * Converts API/HTTP errors into user-facing Flash Messages + */ +export const handleAPIError = (error: HttpResponse) => { + const defaultErrorMessage = 'An unexpected error occurred'; + + const errorFlashMessages: IFlashMessage[] = Array.isArray(error?.body?.attributes?.errors) + ? error.body!.attributes.errors.map((message) => ({ type: 'error', message })) + : [{ type: 'error', message: defaultErrorMessage }]; + + FlashMessagesLogic.actions.setFlashMessages(errorFlashMessages); + + // If this was a programming error or a failed request (such as a CORS) error, + // we rethrow the error so it shows up in the developer console + if (!error?.body?.message) { + throw error; + } +};