From 3c3f3e46afa7f88dc380e1917d427f2a9d32f2ae Mon Sep 17 00:00:00 2001 From: Anastasia Beglova Date: Tue, 24 Sep 2024 15:03:44 -0400 Subject: [PATCH] Add error handling --- .../ErrorPage/ForbiddenPage.test.tsx | 30 +---- .../app-pages}/ErrorPage/ForbiddenPage.tsx | 10 +- .../app-pages/ErrorPage/NotFoundPage.test.tsx | 9 -- .../src/app/dashboard/[tab]/[id]/page.tsx | 8 +- .../main/src/app/dashboard/[tab]/page.tsx | 8 +- frontends/main/src/app/dashboard/page.tsx | 9 +- frontends/main/src/app/getQueryClient.ts | 9 ++ frontends/main/src/app/layout.tsx | 5 +- .../main/src/app/learningpaths/[id]/page.tsx | 8 +- frontends/main/src/app/learningpaths/page.tsx | 9 +- frontends/main/src/app/onboarding/page.tsx | 8 +- .../ErrorBoundary/ErrorBoundary.tsx | 81 +++++++++++++ .../RestrictedRoute/RestrictedRoute.test.tsx | 42 +++++++ .../RestrictedRoute/RestrictedRoute.tsx | 17 ++- .../RestrictedRoute/RestrictedRoute.test.tsx | 99 --------------- .../src/pages/ErrorPage/ErrorPage.test.tsx | 113 ------------------ .../src/pages/ErrorPage/ErrorPage.tsx | 51 -------- 17 files changed, 194 insertions(+), 322 deletions(-) rename frontends/{mit-learn/src/pages => main/src/app-pages}/ErrorPage/ForbiddenPage.test.tsx (57%) rename frontends/{mit-learn/src/pages => main/src/app-pages}/ErrorPage/ForbiddenPage.tsx (77%) create mode 100644 frontends/main/src/components/ErrorBoundary/ErrorBoundary.tsx create mode 100644 frontends/main/src/components/RestrictedRoute/RestrictedRoute.test.tsx rename frontends/{mit-learn => main}/src/components/RestrictedRoute/RestrictedRoute.tsx (80%) delete mode 100644 frontends/mit-learn/src/components/RestrictedRoute/RestrictedRoute.test.tsx delete mode 100644 frontends/mit-learn/src/pages/ErrorPage/ErrorPage.test.tsx delete mode 100644 frontends/mit-learn/src/pages/ErrorPage/ErrorPage.tsx diff --git a/frontends/mit-learn/src/pages/ErrorPage/ForbiddenPage.test.tsx b/frontends/main/src/app-pages/ErrorPage/ForbiddenPage.test.tsx similarity index 57% rename from frontends/mit-learn/src/pages/ErrorPage/ForbiddenPage.test.tsx rename to frontends/main/src/app-pages/ErrorPage/ForbiddenPage.test.tsx index f8c0383b7c..5ba855929f 100644 --- a/frontends/mit-learn/src/pages/ErrorPage/ForbiddenPage.test.tsx +++ b/frontends/main/src/app-pages/ErrorPage/ForbiddenPage.test.tsx @@ -1,7 +1,6 @@ import React from "react" -import { waitFor } from "@testing-library/react" import { renderWithProviders, screen } from "../../test-utils" -import { HOME, login } from "@/common/urls" +import { HOME } from "@/common/urls" import ForbiddenPage from "./ForbiddenPage" import { setMockResponse, urls } from "api/test-utils" import { Permissions } from "@/common/permissions" @@ -25,19 +24,6 @@ afterAll(() => { window.location = oldWindowLocation }) -test("The ForbiddenPage loads with meta", async () => { - setMockResponse.get(urls.userMe.get(), { - [Permissions.Authenticated]: true, - }) - renderWithProviders() - await waitFor(() => { - expect(document.title).toBe("Not Allowed | MIT Learn") - }) - - const meta = document.head.querySelector('meta[name="robots"]') - expect(meta).toHaveProperty("content", "noindex,noarchive") -}) - test("The ForbiddenPage loads with Correct Title", () => { setMockResponse.get(urls.userMe.get(), { [Permissions.Authenticated]: true, @@ -54,17 +40,3 @@ test("The ForbiddenPage loads with a link that directs to HomePage", () => { const homeLink = screen.getByRole("link", { name: "Home" }) expect(homeLink).toHaveAttribute("href", HOME) }) - -test("Redirects unauthenticated users to login", async () => { - setMockResponse.get(urls.userMe.get(), { - [Permissions.Authenticated]: false, - }) - renderWithProviders(, { url: "/some/url?foo=bar#baz" }) - - const expectedUrl = login({ - pathname: "/some/url", - search: "?foo=bar", - hash: "#baz", - }) - expect(window.location.assign).toHaveBeenCalledWith(expectedUrl) -}) diff --git a/frontends/mit-learn/src/pages/ErrorPage/ForbiddenPage.tsx b/frontends/main/src/app-pages/ErrorPage/ForbiddenPage.tsx similarity index 77% rename from frontends/mit-learn/src/pages/ErrorPage/ForbiddenPage.tsx rename to frontends/main/src/app-pages/ErrorPage/ForbiddenPage.tsx index df8efb636b..4079ba5603 100644 --- a/frontends/mit-learn/src/pages/ErrorPage/ForbiddenPage.tsx +++ b/frontends/main/src/app-pages/ErrorPage/ForbiddenPage.tsx @@ -2,18 +2,18 @@ import React, { useEffect } from "react" import ErrorPageTemplate from "./ErrorPageTemplate" import { useUserMe } from "api/hooks/user" import { Typography } from "ol-components" -import { login } from "@/common/urls" -import { useLocation } from "react-router" +import { redirect } from "next/navigation" +import * as urls from "@/common/urls" const ForbiddenPage: React.FC = () => { - const location = useLocation() const { data: user } = useUserMe() useEffect(() => { if (!user?.is_authenticated) { - window.location.assign(login(location)) + const loginUrl = urls.login() + redirect(loginUrl) } - }) + }, [user]) return ( diff --git a/frontends/main/src/app-pages/ErrorPage/NotFoundPage.test.tsx b/frontends/main/src/app-pages/ErrorPage/NotFoundPage.test.tsx index ae832d7aba..83213c82b8 100644 --- a/frontends/main/src/app-pages/ErrorPage/NotFoundPage.test.tsx +++ b/frontends/main/src/app-pages/ErrorPage/NotFoundPage.test.tsx @@ -1,17 +1,8 @@ import React from "react" -import { waitFor } from "@testing-library/react" import { renderWithProviders, screen } from "@/test-utils" import { HOME } from "@/common/urls" import NotFoundPage from "./NotFoundPage" -test.skip("The NotFoundPage loads with meta", async () => { - renderWithProviders(, {}) - await waitFor(() => { - const meta = document.head.querySelector('meta[name="robots"]') - expect(meta).toHaveProperty("content", "noindex,noarchive") - }) -}) - test("The NotFoundPage loads with Correct Title", () => { renderWithProviders(, {}) screen.getByRole("heading", { name: "404 Not Found Error" }) diff --git a/frontends/main/src/app/dashboard/[tab]/[id]/page.tsx b/frontends/main/src/app/dashboard/[tab]/[id]/page.tsx index 6cfd91673f..082710c443 100644 --- a/frontends/main/src/app/dashboard/[tab]/[id]/page.tsx +++ b/frontends/main/src/app/dashboard/[tab]/[id]/page.tsx @@ -3,6 +3,8 @@ import DashboardPage from "@/app-pages/DashboardPage/DashboardPage" import { Metadata } from "next" import { standardizeMetadata } from "@/common/metadata" +import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" +import { Permissions } from "@/common/permissions" export const metadata: Metadata = standardizeMetadata({ title: "Your MIT Learning Journey", @@ -10,7 +12,11 @@ export const metadata: Metadata = standardizeMetadata({ }) const Page: React.FC = () => { - return + return ( + + + + ) } export default Page diff --git a/frontends/main/src/app/dashboard/[tab]/page.tsx b/frontends/main/src/app/dashboard/[tab]/page.tsx index d89e7d5b3e..3586bb6208 100644 --- a/frontends/main/src/app/dashboard/[tab]/page.tsx +++ b/frontends/main/src/app/dashboard/[tab]/page.tsx @@ -3,13 +3,19 @@ import DashboardPage from "@/app-pages/DashboardPage/DashboardPage" import { Metadata } from "next" import { standardizeMetadata } from "@/common/metadata" +import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" +import { Permissions } from "@/common/permissions" export const metadata: Metadata = standardizeMetadata({ title: "Your MIT Learning Journey", social: false, }) const Page: React.FC = () => { - return + return ( + + + + ) } export default Page diff --git a/frontends/main/src/app/dashboard/page.tsx b/frontends/main/src/app/dashboard/page.tsx index 05c7f8aefc..151f80f864 100644 --- a/frontends/main/src/app/dashboard/page.tsx +++ b/frontends/main/src/app/dashboard/page.tsx @@ -2,13 +2,20 @@ import React from "react" import { Metadata } from "next" import DashboardPage from "@/app-pages/DashboardPage/DashboardPage" import { standardizeMetadata } from "@/common/metadata" +import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" +import { Permissions } from "@/common/permissions" + export const metadata: Metadata = standardizeMetadata({ title: "Your MIT Learning Journey", social: false, }) const Page: React.FC = () => { - return + return ( + + + + ) } export default Page diff --git a/frontends/main/src/app/getQueryClient.ts b/frontends/main/src/app/getQueryClient.ts index 03e1626b8f..91be906e05 100644 --- a/frontends/main/src/app/getQueryClient.ts +++ b/frontends/main/src/app/getQueryClient.ts @@ -10,6 +10,7 @@ type MaybeHasStatus = { const RETRY_STATUS_CODES = [408, 429, 502, 503, 504] const MAX_RETRIES = 3 +const THROW_ERROR_CODES: (number | undefined)[] = [404, 403, 401] const makeQueryClient = (): QueryClient => { return new QueryClient({ @@ -17,6 +18,14 @@ const makeQueryClient = (): QueryClient => { queries: { refetchOnWindowFocus: false, staleTime: Infinity, + // Throw runtime errors instead of marking query as errored. + // The runtime error will be caught by an error boundary. + // For now, only do this for 404s, 403s, and 401s. Other errors should + // be handled locally by components. + useErrorBoundary: (error) => { + const status = (error as MaybeHasStatus)?.response?.status + return THROW_ERROR_CODES.includes(status) + }, retry: (failureCount, error) => { const status = (error as MaybeHasStatus)?.response?.status /** diff --git a/frontends/main/src/app/layout.tsx b/frontends/main/src/app/layout.tsx index 2f3f280abe..3580696819 100644 --- a/frontends/main/src/app/layout.tsx +++ b/frontends/main/src/app/layout.tsx @@ -7,6 +7,7 @@ import { PageWrapper, PageWrapperInner } from "./styled" import Providers from "./providers" import { MITLearnGlobalStyles } from "ol-components" import Script from "next/script" +import ErrorBoundary from "@/components/ErrorBoundary/ErrorBoundary" import "./GlobalStyles" @@ -22,7 +23,9 @@ export default function RootLayout({
- {children} + + {children} +