From a1cbeeb5816c7cc1bcde0bbdeb597b4f92bea975 Mon Sep 17 00:00:00 2001 From: Abhineet Jain Date: Fri, 22 Jul 2022 04:54:29 +0000 Subject: [PATCH 1/4] create new static error summary component --- site/src/api/errors.ts | 3 + .../ErrorSummary/ErrorSummary.stories.tsx | 36 ++++++ .../ErrorSummary/ErrorSummary.test.tsx | 64 +++++++++- .../components/ErrorSummary/ErrorSummary.tsx | 118 +++++++++++++++--- 4 files changed, 200 insertions(+), 21 deletions(-) diff --git a/site/src/api/errors.ts b/site/src/api/errors.ts index bc981bbafb256..189ea12f43c4b 100644 --- a/site/src/api/errors.ts +++ b/site/src/api/errors.ts @@ -83,3 +83,6 @@ export const getValidationErrorMessage = (error: Error | ApiError | unknown): st isApiError(error) && error.response.data.validations ? error.response.data.validations : [] return validationErrors.map((error) => error.detail).join("\n") } + +export const getErrorDetail = (error: Error | ApiError | unknown): string | undefined | null => + isApiError(error) ? error.response.data.detail : error instanceof Error ? error.stack : null diff --git a/site/src/components/ErrorSummary/ErrorSummary.stories.tsx b/site/src/components/ErrorSummary/ErrorSummary.stories.tsx index 205b08da908a2..02da50d462219 100644 --- a/site/src/components/ErrorSummary/ErrorSummary.stories.tsx +++ b/site/src/components/ErrorSummary/ErrorSummary.stories.tsx @@ -23,3 +23,39 @@ WithRetry.args = { } export const WithUndefined = Template.bind({}) + +export const WithDefaultMessage = Template.bind({}) +WithDefaultMessage.args = { + // Unknown error type + error: { + message: "Failed to fetch something!", + }, + defaultMessage: "This is a default error message", +} + +export const WithDismissible = Template.bind({}) +WithDismissible.args = { + error: { + response: { + data: { + message: "Failed to fetch something!", + }, + }, + isAxiosError: true, + }, + dismissible: true, +} + +export const WithDetails = Template.bind({}) +WithDetails.args = { + error: { + response: { + data: { + message: "Failed to fetch something!", + detail: "The resource you requested does not exist in the database.", + }, + }, + isAxiosError: true, + }, + dismissible: true, +} diff --git a/site/src/components/ErrorSummary/ErrorSummary.test.tsx b/site/src/components/ErrorSummary/ErrorSummary.test.tsx index 4b153f8ebfde5..a6d222e0e3860 100644 --- a/site/src/components/ErrorSummary/ErrorSummary.test.tsx +++ b/site/src/components/ErrorSummary/ErrorSummary.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@testing-library/react" +import { fireEvent, render, screen } from "@testing-library/react" import { ErrorSummary } from "./ErrorSummary" describe("ErrorSummary", () => { @@ -8,7 +8,67 @@ describe("ErrorSummary", () => { render() // Then - const element = await screen.findByText("test error message", { exact: false }) + const element = await screen.findByText("test error message") expect(element).toBeDefined() }) + + it("shows details on More click", async () => { + // When + const error = { + response: { + data: { + message: "Failed to fetch something!", + detail: "The resource you requested does not exist in the database.", + }, + }, + isAxiosError: true, + } + render() + + // Then + fireEvent.click(screen.getByText("More")) + const element = await screen.findByText( + "The resource you requested does not exist in the database.", + { exact: false }, + ) + expect(element.closest(".MuiCollapse-entered")).toBeDefined() + }) + + it("hides details on Less click", async () => { + // When + const error = { + response: { + data: { + message: "Failed to fetch something!", + detail: "The resource you requested does not exist in the database.", + }, + }, + isAxiosError: true, + } + render() + + // Then + fireEvent.click(screen.getByText("More")) + fireEvent.click(screen.getByText("Less")) + const element = await screen.findByText( + "The resource you requested does not exist in the database.", + { exact: false }, + ) + expect(element.closest(".MuiCollapse-hidden")).toBeDefined() + }) + + it("renders nothing on closing", async () => { + // When + const error = new Error("test error message") + render() + + // Then + const element = await screen.findByText("test error message") + expect(element).toBeDefined() + + const closeIcon = screen.getAllByRole("button")[0] + fireEvent.click(closeIcon) + const nullElement = screen.queryByText("test error message") + expect(nullElement).toBeNull() + }) }) diff --git a/site/src/components/ErrorSummary/ErrorSummary.tsx b/site/src/components/ErrorSummary/ErrorSummary.tsx index e47561a1641fc..8a84bd7ade9f4 100644 --- a/site/src/components/ErrorSummary/ErrorSummary.tsx +++ b/site/src/components/ErrorSummary/ErrorSummary.tsx @@ -1,32 +1,112 @@ import Button from "@material-ui/core/Button" +import Collapse from "@material-ui/core/Collapse" +import IconButton from "@material-ui/core/IconButton" +import Link from "@material-ui/core/Link" +import { makeStyles, Theme } from "@material-ui/core/styles" +import CloseIcon from "@material-ui/icons/Close" import RefreshIcon from "@material-ui/icons/Refresh" -import { FC } from "react" +import { ApiError, getErrorDetail, getErrorMessage } from "api/errors" +import { FC, useState } from "react" import { Stack } from "../Stack/Stack" const Language = { retryMessage: "Retry", unknownErrorMessage: "An unknown error has occurred", + moreDetails: "More", + lessDetails: "Less", } export interface ErrorSummaryProps { - error: Error | unknown + error: ApiError | Error | unknown retry?: () => void + dismissible?: boolean + defaultMessage?: string } -export const ErrorSummary: FC = ({ error, retry }) => ( - - {!(error instanceof Error) ? ( -
{Language.unknownErrorMessage}
- ) : ( -
{error.toString()}
- )} - - {retry && ( -
- -
- )} -
-) +export const ErrorSummary: FC = ({ + error, + retry, + dismissible, + defaultMessage, +}) => { + const message = getErrorMessage(error, defaultMessage || Language.unknownErrorMessage) + const detail = getErrorDetail(error) + const [showDetails, setShowDetails] = useState(false) + const [isOpen, setOpen] = useState(true) + + const styles = useStyles({ showDetails }) + + const toggleShowDetails = () => { + setShowDetails(!showDetails) + } + + const closeError = () => { + setOpen(false) + } + + if (!isOpen) { + return null + } + + return ( + + + +
+ {message} + {!!detail && ( + + {showDetails ? Language.lessDetails : Language.moreDetails} + + )} +
+ {dismissible && ( + + + + )} +
+ {detail} +
+ + {retry && ( +
+ +
+ )} +
+ ) +} + +interface StyleProps { + showDetails?: boolean +} + +const useStyles = makeStyles((theme) => ({ + root: { + background: `${theme.palette.error.main}60`, + margin: `${theme.spacing(2)}px`, + padding: `${theme.spacing(2)}px`, + borderRadius: theme.shape.borderRadius, + gap: (props) => (props.showDetails ? `${theme.spacing(2)}px` : 0), + }, + message: { + justifyContent: "space-between", + }, + errorMessage: { + marginRight: `${theme.spacing(1)}px`, + }, + detailsLink: { + cursor: "pointer", + }, + iconButton: { + padding: 0, + }, + closeIcon: { + width: 25, + height: 25, + color: theme.palette.primary.contrastText, + }, +})) From 935d4bf470cb70f7666c906ff88ba2cee1b720ff Mon Sep 17 00:00:00 2001 From: Abhineet Jain Date: Fri, 22 Jul 2022 08:34:59 +0000 Subject: [PATCH 2/4] fix import path --- site/src/components/ErrorSummary/ErrorSummary.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/ErrorSummary/ErrorSummary.tsx b/site/src/components/ErrorSummary/ErrorSummary.tsx index 8a84bd7ade9f4..d6c3eee4fe0ea 100644 --- a/site/src/components/ErrorSummary/ErrorSummary.tsx +++ b/site/src/components/ErrorSummary/ErrorSummary.tsx @@ -5,7 +5,7 @@ import Link from "@material-ui/core/Link" import { makeStyles, Theme } from "@material-ui/core/styles" import CloseIcon from "@material-ui/icons/Close" import RefreshIcon from "@material-ui/icons/Refresh" -import { ApiError, getErrorDetail, getErrorMessage } from "api/errors" +import { ApiError, getErrorDetail, getErrorMessage } from "../../api/errors" import { FC, useState } from "react" import { Stack } from "../Stack/Stack" From 793c689bb1291909829afecfd4972cfb20d35004 Mon Sep 17 00:00:00 2001 From: Abhineet Jain Date: Fri, 22 Jul 2022 16:59:46 +0000 Subject: [PATCH 3/4] add aria-expanded --- site/src/components/ErrorSummary/ErrorSummary.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/site/src/components/ErrorSummary/ErrorSummary.tsx b/site/src/components/ErrorSummary/ErrorSummary.tsx index 11f8629e3cb86..edf99eff7c422 100644 --- a/site/src/components/ErrorSummary/ErrorSummary.tsx +++ b/site/src/components/ErrorSummary/ErrorSummary.tsx @@ -55,7 +55,11 @@ export const ErrorSummary: FC = ({
{message} {!!detail && ( - + {showDetails ? Language.lessDetails : Language.moreDetails} )} From 8334a6c8b3dc9669b0b32947a2b937edeae70339 Mon Sep 17 00:00:00 2001 From: Abhineet Jain Date: Fri, 22 Jul 2022 18:57:36 +0000 Subject: [PATCH 4/4] add detail background, redesign retry button --- .../components/ErrorSummary/ErrorSummary.tsx | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/site/src/components/ErrorSummary/ErrorSummary.tsx b/site/src/components/ErrorSummary/ErrorSummary.tsx index edf99eff7c422..77bea7b056363 100644 --- a/site/src/components/ErrorSummary/ErrorSummary.tsx +++ b/site/src/components/ErrorSummary/ErrorSummary.tsx @@ -2,7 +2,7 @@ import Button from "@material-ui/core/Button" import Collapse from "@material-ui/core/Collapse" import IconButton from "@material-ui/core/IconButton" import Link from "@material-ui/core/Link" -import { makeStyles, Theme } from "@material-ui/core/styles" +import { darken, makeStyles, Theme } from "@material-ui/core/styles" import CloseIcon from "@material-ui/icons/Close" import RefreshIcon from "@material-ui/icons/Refresh" import { ApiError, getErrorDetail, getErrorMessage } from "api/errors" @@ -49,33 +49,33 @@ export const ErrorSummary: FC = ({ } return ( - - - -
- {message} - {!!detail && ( - - {showDetails ? Language.lessDetails : Language.moreDetails} - - )} -
- {dismissible && ( - - - + + +
+ {message} + {!!detail && ( + + {showDetails ? Language.lessDetails : Language.moreDetails} + )} - - {detail} +
+ {dismissible && ( + + + + )}
- + +
{detail}
+
{retry && ( -
-
@@ -90,13 +90,13 @@ interface StyleProps { const useStyles = makeStyles((theme) => ({ root: { - background: `${theme.palette.error.main}60`, + background: darken(theme.palette.error.main, 0.6), margin: `${theme.spacing(2)}px`, padding: `${theme.spacing(2)}px`, borderRadius: theme.shape.borderRadius, - gap: (props) => (props.showDetails ? `${theme.spacing(2)}px` : 0), + gap: 0, }, - message: { + messageBox: { justifyContent: "space-between", }, errorMessage: { @@ -105,6 +105,12 @@ const useStyles = makeStyles((theme) => ({ detailsLink: { cursor: "pointer", }, + details: { + marginTop: `${theme.spacing(2)}px`, + padding: `${theme.spacing(2)}px`, + background: darken(theme.palette.error.main, 0.7), + borderRadius: theme.shape.borderRadius, + }, iconButton: { padding: 0, }, @@ -113,4 +119,7 @@ const useStyles = makeStyles((theme) => ({ height: 25, color: theme.palette.primary.contrastText, }, + retry: { + marginTop: `${theme.spacing(2)}px`, + }, })) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy