diff --git a/client/app/components/ApplicationArea/ErrorMessage.jsx b/client/app/components/ApplicationArea/ErrorMessage.jsx index cc8e5454b..44dcfdaab 100644 --- a/client/app/components/ApplicationArea/ErrorMessage.jsx +++ b/client/app/components/ApplicationArea/ErrorMessage.jsx @@ -14,29 +14,19 @@ function getErrorMessageByStatus(status, defaultMessage) { } } -export function getErrorMessage( - error, - defaultMessage = "It seems like we encountered an error. Try refreshing this page or contact your administrator." -) { +function getErrorMessage(error) { + const message = "It seems like we encountered an error. Try refreshing this page or contact your administrator."; if (isObject(error)) { // HTTP errors if (error.isAxiosError && isObject(error.response)) { - const errorData = get(error, "response.data", {}); - - // handle cases where the message is an object as { "message": msg } or { "error": msg } - const errorMessage = errorData.message || errorData.error || defaultMessage; - return getErrorMessageByStatus(error.response.status, errorMessage); + return getErrorMessageByStatus(error.response.status, get(error, "response.data.message", message)); } // Router errors if (error.status) { - return getErrorMessageByStatus(error.status, defaultMessage); - } - // Other Error instances - if (error.message) { - return error.message; + return getErrorMessageByStatus(error.status, message); } } - return defaultMessage; + return message; } export default function ErrorMessage({ error }) { diff --git a/client/app/components/ApplicationArea/ErrorMessage.test.js b/client/app/components/ApplicationArea/ErrorMessage.test.js new file mode 100644 index 000000000..048532c5c --- /dev/null +++ b/client/app/components/ApplicationArea/ErrorMessage.test.js @@ -0,0 +1,51 @@ +import React from "react"; +import { mount } from "enzyme"; +import ErrorMessage from "./ErrorMessage"; + +const ErrorMessages = { + UNAUTHORIZED: "It seems like you don’t have permission to see this page.", + NOT_FOUND: "It seems like the page you're looking for cannot be found.", + GENERIC: "It seems like we encountered an error. Try refreshing this page or contact your administrator.", +}; + +function mockAxiosError(status = 500, response = {}) { + const error = new Error(`Failed with code ${status}.`); + error.isAxiosError = true; + error.response = { status, ...response }; + return error; +} + +describe("Error Message", () => { + const spyError = jest.spyOn(console, "error"); + + beforeEach(() => { + spyError.mockReset(); + }); + + function expectErrorMessageToBe(error, errorMessage) { + const component = mount(); + + expect(component.find(".error-state__details h4").text()).toBe(errorMessage); + expect(spyError).toHaveBeenCalledWith(error); + } + + test("displays a generic message on adhoc errors", () => { + expectErrorMessageToBe(new Error("technical information"), ErrorMessages.GENERIC); + }); + + test("displays a not found message on axios errors with 404 code", () => { + expectErrorMessageToBe(mockAxiosError(404), ErrorMessages.NOT_FOUND); + }); + + test("displays a unauthorized message on axios errors with 401 code", () => { + expectErrorMessageToBe(mockAxiosError(401), ErrorMessages.UNAUTHORIZED); + }); + + test("displays a unauthorized message on axios errors with 403 code", () => { + expectErrorMessageToBe(mockAxiosError(403), ErrorMessages.UNAUTHORIZED); + }); + + test("displays a generic message on axios errors with 500 code", () => { + expectErrorMessageToBe(mockAxiosError(500), ErrorMessages.GENERIC); + }); +}); diff --git a/client/app/components/CreateSourceDialog.jsx b/client/app/components/CreateSourceDialog.jsx index a49601b4f..cebf2e1a3 100644 --- a/client/app/components/CreateSourceDialog.jsx +++ b/client/app/components/CreateSourceDialog.jsx @@ -1,12 +1,11 @@ import React from "react"; import PropTypes from "prop-types"; -import { isEmpty, toUpper, includes } from "lodash"; +import { isEmpty, toUpper, includes, get } from "lodash"; import Button from "antd/lib/button"; import List from "antd/lib/list"; import Modal from "antd/lib/modal"; import Input from "antd/lib/input"; import Steps from "antd/lib/steps"; -import { getErrorMessage } from "@/components/ApplicationArea/ErrorMessage"; import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper"; import { PreviewCard } from "@/components/PreviewCard"; import EmptyState from "@/components/items-list/components/EmptyState"; @@ -67,7 +66,7 @@ class CreateSourceDialog extends React.Component { }) .catch(error => { this.setState({ savingSource: false, currentStep: StepEnum.CONFIGURE_IT }); - errorCallback(getErrorMessage(error, "Failed saving.")); + errorCallback(get(error, "response.data.message", "Failed saving.")); }); } };