From 2d6531b8dc6bb69888c37ff1155aff1c849ea5ed Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Thu, 19 May 2022 15:16:05 +0000 Subject: [PATCH 1/6] added error boundary and error ui components --- .vscode/settings.json | 5 +- site/package.json | 1 + site/src/app.tsx | 17 ++-- site/src/components/CodeBlock/CodeBlock.tsx | 28 +++++-- site/src/components/CopyButton/CopyButton.tsx | 25 ++++-- .../ErrorBoundary/ErrorBoundary.tsx | 31 +++++++ .../RuntimeErrorState/ReportButtons.tsx | 70 ++++++++++++++++ .../RuntimeErrorState/RuntimeErrorReport.tsx | 80 +++++++++++++++++++ .../RuntimeErrorState/RuntimeErrorState.tsx | 74 +++++++++++++++++ site/src/components/Section/Section.tsx | 14 +++- site/src/components/Stack/Stack.tsx | 2 + site/src/theme/palettes.ts | 3 + 12 files changed, 327 insertions(+), 23 deletions(-) create mode 100644 site/src/components/ErrorBoundary/ErrorBoundary.tsx create mode 100644 site/src/components/RuntimeErrorState/ReportButtons.tsx create mode 100644 site/src/components/RuntimeErrorState/RuntimeErrorReport.tsx create mode 100644 site/src/components/RuntimeErrorState/RuntimeErrorState.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index a04dc17791f5f..3564a8ef964df 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -51,6 +51,7 @@ "rpty", "sdkproto", "Signup", + "sourcemapped", "stretchr", "TCGETS", "tcpip", @@ -76,7 +77,7 @@ }, { "match": "provisionerd/proto/provisionerd.proto", - "cmd": "make provisionerd/proto/provisionerd.pb.go", + "cmd": "make provisionerd/proto/provisionerd.pb.go" } ] }, @@ -104,5 +105,5 @@ }, // We often use a version of TypeScript that's ahead of the version shipped // with VS Code. - "typescript.tsdk": "./site/node_modules/typescript/lib", + "typescript.tsdk": "./site/node_modules/typescript/lib" } diff --git a/site/package.json b/site/package.json index 50ad5f0c74a76..00b84da9d61b3 100644 --- a/site/package.json +++ b/site/package.json @@ -41,6 +41,7 @@ "react": "17.0.2", "react-dom": "17.0.2", "react-router-dom": "6.3.0", + "sourcemapped-stacktrace": "1.1.11", "swr": "1.2.2", "uuid": "8.3.2", "xstate": "4.32.1", diff --git a/site/src/app.tsx b/site/src/app.tsx index a4f0bfa5b5b69..dd210ef8f022a 100644 --- a/site/src/app.tsx +++ b/site/src/app.tsx @@ -4,6 +4,7 @@ import React from "react" import { BrowserRouter as Router } from "react-router-dom" import { SWRConfig } from "swr" import { AppRouter } from "./AppRouter" +import { ErrorBoundary } from "./components/ErrorBoundary/ErrorBoundary" import { GlobalSnackbar } from "./components/GlobalSnackbar/GlobalSnackbar" import { dark } from "./theme" import "./theme/globalFonts" @@ -30,13 +31,15 @@ export const App: React.FC = () => { }, }} > - - - - - - - + + + + + + + + + ) diff --git a/site/src/components/CodeBlock/CodeBlock.tsx b/site/src/components/CodeBlock/CodeBlock.tsx index 278315980237d..37a9e2b4e1b33 100644 --- a/site/src/components/CodeBlock/CodeBlock.tsx +++ b/site/src/components/CodeBlock/CodeBlock.tsx @@ -5,20 +5,30 @@ import { combineClasses } from "../../util/combineClasses" export interface CodeBlockProps { lines: string[] + ctas?: React.ReactElement[] className?: string } -export const CodeBlock: React.FC = ({ lines, className = "" }) => { +export const CodeBlock: React.FC = ({ lines, ctas, className = "" }) => { const styles = useStyles() return ( -
- {lines.map((line, idx) => ( -
- {line} + <> +
+ {lines.map((line, idx) => ( +
+ {line} +
+ ))} +
+ {ctas && ctas.length && ( +
+ {ctas.map((cta, i) => { + return {cta} + })}
- ))} -
+ )} + ) } @@ -36,4 +46,8 @@ const useStyles = makeStyles((theme) => ({ line: { whiteSpace: "pre-wrap", }, + ctaBar: { + display: "flex", + justifyContent: "space-between", + }, })) diff --git a/site/src/components/CopyButton/CopyButton.tsx b/site/src/components/CopyButton/CopyButton.tsx index 0ddd865f27023..fd16ed7dd487d 100644 --- a/site/src/components/CopyButton/CopyButton.tsx +++ b/site/src/components/CopyButton/CopyButton.tsx @@ -3,17 +3,25 @@ import { makeStyles } from "@material-ui/core/styles" import Tooltip from "@material-ui/core/Tooltip" import Check from "@material-ui/icons/Check" import React, { useState } from "react" +import { combineClasses } from "../../util/combineClasses" import { FileCopyIcon } from "../Icons/FileCopyIcon" interface CopyButtonProps { text: string - className?: string + ctaCopy?: string + wrapperClassName?: string + buttonClassName?: string } /** * Copy button used inside the CodeBlock component internally */ -export const CopyButton: React.FC = ({ className = "", text }) => { +export const CopyButton: React.FC = ({ + text, + ctaCopy, + wrapperClassName = "", + buttonClassName = "", +}) => { const styles = useStyles() const [isCopied, setIsCopied] = useState(false) @@ -36,9 +44,16 @@ export const CopyButton: React.FC = ({ className = "", text }) return ( -
-
diff --git a/site/src/components/ErrorBoundary/ErrorBoundary.tsx b/site/src/components/ErrorBoundary/ErrorBoundary.tsx new file mode 100644 index 0000000000000..1e54293b8ee87 --- /dev/null +++ b/site/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -0,0 +1,31 @@ +import React from "react" +import { RuntimeErrorState } from "../RuntimeErrorState/RuntimeErrorState" + +type ErrorBoundaryProps = Record + +interface ErrorBoundaryState { + error: Error | null +} + +/** + * Our app's Error Boundary + * Read more about React Error Boundaries: https://reactjs.org/docs/error-boundaries.html + */ +export class ErrorBoundary extends React.Component { + constructor(props: ErrorBoundaryProps) { + super(props) + this.state = { error: null } + } + + static getDerivedStateFromError(error: Error): { error: Error } { + return { error } + } + + render(): React.ReactNode { + if (this.state.error) { + return + } + + return this.props.children + } +} diff --git a/site/src/components/RuntimeErrorState/ReportButtons.tsx b/site/src/components/RuntimeErrorState/ReportButtons.tsx new file mode 100644 index 0000000000000..88466ab8944f7 --- /dev/null +++ b/site/src/components/RuntimeErrorState/ReportButtons.tsx @@ -0,0 +1,70 @@ +import Button from "@material-ui/core/Button" +import { makeStyles } from "@material-ui/core/styles" +import RefreshIcon from "@material-ui/icons/Refresh" +import React from "react" +import { CopyButton } from "../CopyButton/CopyButton" + +const Language = { + reloadApp: "Reload Application", + copyReport: "Copy Report", +} + +/** + * A wrapper component for a full-width copy button + */ +const CopyStackButton = ({ text }: { text: string }): React.ReactElement => { + const styles = useStyles() + + return ( + + ) +} + +/** + * A button that reloads our application + */ +const ReloadAppButton = (): React.ReactElement => { + const styles = useStyles() + + return ( + + ) +} + +/** + * createCtas generates an array of buttons to be used with our error boundary UI + */ +export const createCtas = (codeBlock: string[]): React.ReactElement[] => { + // REMARK: we don't have to worry about key order changing + // eslint-disable-next-line react/jsx-key + return [, ] +} + +const useStyles = makeStyles((theme) => ({ + buttonWrapper: { + marginTop: theme.spacing(1), + marginLeft: 0, + flex: theme.spacing(1), + textTransform: "uppercase", + }, + + copyButton: { + width: "100%", + marginRight: theme.spacing(1), + backgroundColor: theme.palette.primary.main, + textTransform: "uppercase", + }, +})) diff --git a/site/src/components/RuntimeErrorState/RuntimeErrorReport.tsx b/site/src/components/RuntimeErrorState/RuntimeErrorReport.tsx new file mode 100644 index 0000000000000..e9687d6be93d1 --- /dev/null +++ b/site/src/components/RuntimeErrorState/RuntimeErrorReport.tsx @@ -0,0 +1,80 @@ +import { makeStyles } from "@material-ui/core/styles" +import React from "react" +import { CodeBlock } from "../CodeBlock/CodeBlock" +import { createCtas } from "./ReportButtons" + +const Language = { + reportLoading: "Generating crash report...", +} + +interface ReportState { + error: Error + mappedStack: string[] | null +} + +interface StackTraceAvailableMsg { + type: "stackTraceAvailable" + stackTrace: string[] +} + +/** + * stackTraceUnavailable is a Msg describing a stack trace not being available + */ +export const stackTraceUnavailable = { + type: "stackTraceUnavailable", +} as const + +type ReportMessage = StackTraceAvailableMsg | typeof stackTraceUnavailable + +export const stackTraceAvailable = (stackTrace: string[]): StackTraceAvailableMsg => { + return { + type: "stackTraceAvailable", + stackTrace, + } +} + +const setStackTrace = (model: ReportState, mappedStack: string[]): ReportState => { + return { + ...model, + mappedStack, + } +} + +export const reducer = (model: ReportState, msg: ReportMessage): ReportState => { + switch (msg.type) { + case "stackTraceAvailable": + return setStackTrace(model, msg.stackTrace) + case "stackTraceUnavailable": + return setStackTrace(model, ["Unable to get stack trace"]) + } +} + +/** + * A code block component that contains the error stack resulting from an error boundary trigger + */ +export const RuntimeErrorReport = ({ error, mappedStack }: ReportState): React.ReactElement => { + const styles = useStyles() + + if (!mappedStack) { + return + } + + const codeBlock = [ + "======================= STACK TRACE ========================", + "", + error.message, + ...mappedStack, + "", + "============================================================", + ] + + return +} + +const useStyles = makeStyles(() => ({ + codeBlock: { + minHeight: "auto", + userSelect: "all", + width: "100%", + }, +})) diff --git a/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx b/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx new file mode 100644 index 0000000000000..3c44a4f2d7eb4 --- /dev/null +++ b/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx @@ -0,0 +1,74 @@ +import Box from "@material-ui/core/Box" +import { makeStyles } from "@material-ui/core/styles" +import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline" +import React, { useEffect, useReducer } from "react" +import { mapStackTrace } from "sourcemapped-stacktrace" +import { Margins } from "../Margins/Margins" +import { Section } from "../Section/Section" +import { reducer, RuntimeErrorReport, stackTraceAvailable, stackTraceUnavailable } from "./RuntimeErrorReport" + +const Language = { + title: "Coder encountered an error", +} + +interface RuntimeErrorStateProps { + error: Error +} + +/** + * A title for our error boundary UI + */ +const ErrorStateTitle = () => { + const styles = useStyles() + + return ( + + + {Language.title} + + ) +} + +/** + * An error UI that is displayed when our error boundary (ErrorBoundary.tsx) is triggered + */ +export const RuntimeErrorState: React.FC = ({ error }) => { + const styles = useStyles() + const [reportState, dispatch] = useReducer(reducer, { error, mappedStack: null }) + + useEffect(() => { + try { + mapStackTrace(error.stack, (mappedStack) => dispatch(stackTraceAvailable(mappedStack))) + } catch { + dispatch(stackTraceUnavailable) + } + }, [error]) + + return ( + + +
}> + +
+
+
+ ) +} + +const useStyles = makeStyles((theme) => ({ + title: { + "& span": { + paddingLeft: theme.spacing(1), + }, + + "& .MuiSvgIcon-root": { + color: theme.palette.error.main, + }, + }, + + reportContainer: { + display: "flex", + justifyContent: "center", + marginTop: theme.spacing(5), + }, +})) diff --git a/site/src/components/Section/Section.tsx b/site/src/components/Section/Section.tsx index 97dc042be944d..22bf0dd101a73 100644 --- a/site/src/components/Section/Section.tsx +++ b/site/src/components/Section/Section.tsx @@ -2,6 +2,7 @@ import { makeStyles } from "@material-ui/core/styles" import { fade } from "@material-ui/core/styles/colorManipulator" import Typography from "@material-ui/core/Typography" import React from "react" +import { combineClasses } from "../../util/combineClasses" import { SectionAction } from "../SectionAction/SectionAction" type SectionLayout = "fixed" | "fluid" @@ -12,15 +13,24 @@ export interface SectionProps { toolbar?: React.ReactNode alert?: React.ReactNode layout?: SectionLayout + className?: string children?: React.ReactNode } type SectionFC = React.FC & { Action: typeof SectionAction } -export const Section: SectionFC = ({ title, description, toolbar, alert, children, layout = "fixed" }) => { +export const Section: SectionFC = ({ + title, + description, + toolbar, + alert, + className = "", + children, + layout = "fixed", +}) => { const styles = useStyles({ layout }) return ( -
+
{(title || description) && (
diff --git a/site/src/components/Stack/Stack.tsx b/site/src/components/Stack/Stack.tsx index ed1015d9815de..82a231dc42a9c 100644 --- a/site/src/components/Stack/Stack.tsx +++ b/site/src/components/Stack/Stack.tsx @@ -14,6 +14,8 @@ const useStyles = makeStyles((theme) => ({ })) export const Stack: React.FC = ({ children, spacing = 2 }) => { + throw new Error("uh oh") + const styles = useStyles({ spacing }) return
{children}
} diff --git a/site/src/theme/palettes.ts b/site/src/theme/palettes.ts index ac740f446e5ae..6c223a4c85312 100644 --- a/site/src/theme/palettes.ts +++ b/site/src/theme/palettes.ts @@ -37,4 +37,7 @@ export const darkPalette: PaletteOptions = { success: { main: "#6BBE00", }, + error: { + main: "#DD4764", + }, } From 59be45a0a14419d95e8e5972fa8ad66216223971 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Thu, 19 May 2022 16:10:10 +0000 Subject: [PATCH 2/6] add body txt and standardize btn size --- site/src/components/CodeBlock/CodeBlock.tsx | 2 ++ .../RuntimeErrorState/ReportButtons.tsx | 2 ++ .../RuntimeErrorState/RuntimeErrorState.tsx | 34 +++++++++++++++++-- site/src/components/Stack/Stack.tsx | 2 -- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/site/src/components/CodeBlock/CodeBlock.tsx b/site/src/components/CodeBlock/CodeBlock.tsx index 37a9e2b4e1b33..eb5f6face2d62 100644 --- a/site/src/components/CodeBlock/CodeBlock.tsx +++ b/site/src/components/CodeBlock/CodeBlock.tsx @@ -35,6 +35,8 @@ export const CodeBlock: React.FC = ({ lines, ctas, className = " const useStyles = makeStyles((theme) => ({ root: { minHeight: 156, + maxHeight: 240, + overflowY: "scroll", background: theme.palette.background.default, color: theme.palette.text.primary, fontFamily: MONOSPACE_FONT_FAMILY, diff --git a/site/src/components/RuntimeErrorState/ReportButtons.tsx b/site/src/components/RuntimeErrorState/ReportButtons.tsx index 88466ab8944f7..a1192d3524a26 100644 --- a/site/src/components/RuntimeErrorState/ReportButtons.tsx +++ b/site/src/components/RuntimeErrorState/ReportButtons.tsx @@ -59,6 +59,7 @@ const useStyles = makeStyles((theme) => ({ marginLeft: 0, flex: theme.spacing(1), textTransform: "uppercase", + fontSize: theme.typography.button.fontSize, }, copyButton: { @@ -66,5 +67,6 @@ const useStyles = makeStyles((theme) => ({ marginRight: theme.spacing(1), backgroundColor: theme.palette.primary.main, textTransform: "uppercase", + fontSize: theme.typography.button.fontSize, }, })) diff --git a/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx b/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx index 3c44a4f2d7eb4..b78be9b3abbd7 100644 --- a/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx +++ b/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx @@ -2,13 +2,17 @@ import Box from "@material-ui/core/Box" import { makeStyles } from "@material-ui/core/styles" import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline" import React, { useEffect, useReducer } from "react" +import { Link } from "react-router-dom" import { mapStackTrace } from "sourcemapped-stacktrace" import { Margins } from "../Margins/Margins" import { Section } from "../Section/Section" +import { Typography } from "../Typography/Typography" import { reducer, RuntimeErrorReport, stackTraceAvailable, stackTraceUnavailable } from "./RuntimeErrorReport" const Language = { title: "Coder encountered an error", + body: "Please copy the crash log using the button below and", + link: "send it to us.", } interface RuntimeErrorStateProps { @@ -20,7 +24,6 @@ interface RuntimeErrorStateProps { */ const ErrorStateTitle = () => { const styles = useStyles() - return ( @@ -29,6 +32,28 @@ const ErrorStateTitle = () => { ) } +/** + * A description for our error boundary UI + */ +const ErrorStateDescription = () => { + const styles = useStyles() + return ( + + {Language.body}  + { + window.location.href = "mailto:support@coder.com" + e.preventDefault() + }} + className={styles.link} + > + {Language.link} + + + ) +} + /** * An error UI that is displayed when our error boundary (ErrorBoundary.tsx) is triggered */ @@ -47,7 +72,7 @@ export const RuntimeErrorState: React.FC = ({ error }) = return ( -
}> +
} description={}>
@@ -65,7 +90,10 @@ const useStyles = makeStyles((theme) => ({ color: theme.palette.error.main, }, }, - + link: { + textDecoration: "none", + color: theme.palette.primary.main, + }, reportContainer: { display: "flex", justifyContent: "center", diff --git a/site/src/components/Stack/Stack.tsx b/site/src/components/Stack/Stack.tsx index 82a231dc42a9c..ed1015d9815de 100644 --- a/site/src/components/Stack/Stack.tsx +++ b/site/src/components/Stack/Stack.tsx @@ -14,8 +14,6 @@ const useStyles = makeStyles((theme) => ({ })) export const Stack: React.FC = ({ children, spacing = 2 }) => { - throw new Error("uh oh") - const styles = useStyles({ spacing }) return
{children}
} From dfa43fb1a8f0f9e01ca47d144f7ae3317c1c7b48 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Thu, 19 May 2022 17:41:08 +0000 Subject: [PATCH 3/6] added story --- site/src/components/CopyButton/CopyButton.tsx | 15 +++++----- .../RuntimeErrorState/RuntimeErrorReport.tsx | 2 +- .../RuntimeErrorState.stories.tsx | 30 +++++++++++++++++++ .../RuntimeErrorState/RuntimeErrorState.tsx | 2 +- .../{ReportButtons.tsx => createCtas.tsx} | 4 +-- site/src/theme/palettes.ts | 3 -- 6 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 site/src/components/RuntimeErrorState/RuntimeErrorState.stories.tsx rename site/src/components/RuntimeErrorState/{ReportButtons.tsx => createCtas.tsx} (94%) diff --git a/site/src/components/CopyButton/CopyButton.tsx b/site/src/components/CopyButton/CopyButton.tsx index fd16ed7dd487d..80b53d1a7e413 100644 --- a/site/src/components/CopyButton/CopyButton.tsx +++ b/site/src/components/CopyButton/CopyButton.tsx @@ -1,4 +1,4 @@ -import Button from "@material-ui/core/Button" +import IconButton from "@material-ui/core/Button" import { makeStyles } from "@material-ui/core/styles" import Tooltip from "@material-ui/core/Tooltip" import Check from "@material-ui/icons/Check" @@ -45,16 +45,14 @@ export const CopyButton: React.FC = ({ return (
- + {isCopied ? : } + {ctaCopy &&
{ctaCopy}
} +
) @@ -80,4 +78,7 @@ const useStyles = makeStyles((theme) => ({ width: 20, height: 20, }, + buttonCopy: { + marginLeft: theme.spacing(1), + }, })) diff --git a/site/src/components/RuntimeErrorState/RuntimeErrorReport.tsx b/site/src/components/RuntimeErrorState/RuntimeErrorReport.tsx index e9687d6be93d1..0ab929d371acb 100644 --- a/site/src/components/RuntimeErrorState/RuntimeErrorReport.tsx +++ b/site/src/components/RuntimeErrorState/RuntimeErrorReport.tsx @@ -1,7 +1,7 @@ import { makeStyles } from "@material-ui/core/styles" import React from "react" import { CodeBlock } from "../CodeBlock/CodeBlock" -import { createCtas } from "./ReportButtons" +import { createCtas } from "./createCtas" const Language = { reportLoading: "Generating crash report...", diff --git a/site/src/components/RuntimeErrorState/RuntimeErrorState.stories.tsx b/site/src/components/RuntimeErrorState/RuntimeErrorState.stories.tsx new file mode 100644 index 0000000000000..5466459d91c3a --- /dev/null +++ b/site/src/components/RuntimeErrorState/RuntimeErrorState.stories.tsx @@ -0,0 +1,30 @@ +import { ComponentMeta, Story } from "@storybook/react" +import React from "react" +import { RuntimeErrorState, RuntimeErrorStateProps } from "./RuntimeErrorState" + +const error = new Error("An error occurred") + +export default { + title: "components/RuntimeErrorState", + component: RuntimeErrorState, + argTypes: { + error: { + defaultValue: error, + }, + }, +} as ComponentMeta + +const Template: Story = (args) => + +export const Errored = Template.bind({}) +Errored.parameters = { + // The RuntimeErrorState is noisy for chromatic, because it renders an actual error + // along with the stacktrace - and the stacktrace includes the full URL of + // scripts in the stack. This is problematic, because every deployment uses + // a different URL, causing the validation to fail. + chromatic: { disableSnapshot: true }, +} + +Errored.args = { + error, +} diff --git a/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx b/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx index b78be9b3abbd7..5d83eedc24e3d 100644 --- a/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx +++ b/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx @@ -15,7 +15,7 @@ const Language = { link: "send it to us.", } -interface RuntimeErrorStateProps { +export interface RuntimeErrorStateProps { error: Error } diff --git a/site/src/components/RuntimeErrorState/ReportButtons.tsx b/site/src/components/RuntimeErrorState/createCtas.tsx similarity index 94% rename from site/src/components/RuntimeErrorState/ReportButtons.tsx rename to site/src/components/RuntimeErrorState/createCtas.tsx index a1192d3524a26..3be72455ec4d8 100644 --- a/site/src/components/RuntimeErrorState/ReportButtons.tsx +++ b/site/src/components/RuntimeErrorState/createCtas.tsx @@ -59,7 +59,7 @@ const useStyles = makeStyles((theme) => ({ marginLeft: 0, flex: theme.spacing(1), textTransform: "uppercase", - fontSize: theme.typography.button.fontSize, + fontSize: theme.typography.fontSize, }, copyButton: { @@ -67,6 +67,6 @@ const useStyles = makeStyles((theme) => ({ marginRight: theme.spacing(1), backgroundColor: theme.palette.primary.main, textTransform: "uppercase", - fontSize: theme.typography.button.fontSize, + fontSize: theme.typography.fontSize, }, })) diff --git a/site/src/theme/palettes.ts b/site/src/theme/palettes.ts index 6c223a4c85312..ac740f446e5ae 100644 --- a/site/src/theme/palettes.ts +++ b/site/src/theme/palettes.ts @@ -37,7 +37,4 @@ export const darkPalette: PaletteOptions = { success: { main: "#6BBE00", }, - error: { - main: "#DD4764", - }, } From d12d8fc08db340c59a3b404321784eecd14fa464 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Thu, 19 May 2022 20:04:55 +0000 Subject: [PATCH 4/6] feat: added error boundary closes #1013 --- .../RuntimeErrorState.test.tsx | 43 +++++++++++++++++++ .../RuntimeErrorState/RuntimeErrorState.tsx | 13 ++---- .../RuntimeErrorState/createCtas.tsx | 2 +- 3 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 site/src/components/RuntimeErrorState/RuntimeErrorState.test.tsx diff --git a/site/src/components/RuntimeErrorState/RuntimeErrorState.test.tsx b/site/src/components/RuntimeErrorState/RuntimeErrorState.test.tsx new file mode 100644 index 0000000000000..0c923d43892f5 --- /dev/null +++ b/site/src/components/RuntimeErrorState/RuntimeErrorState.test.tsx @@ -0,0 +1,43 @@ +import { screen } from "@testing-library/react" +import React from "react" +import { render } from "../../testHelpers/renderHelpers" +import { Language as ButtonLanguage } from "./createCtas" +import { Language as RuntimeErrorStateLanguage, RuntimeErrorState } from "./RuntimeErrorState" + +describe("RuntimeErrorState", () => { + beforeEach(() => { + // Given + const errorText = "broken!" + const errorStateProps = { + error: new Error(errorText), + } + + // When + render() + }) + + it("should show stack when encountering runtime error", () => { + // Then + const reportError = screen.getByText("broken!") + expect(reportError).toBeDefined() + + // Despite appearances, this is the stack trace + const stackTrace = screen.getByText("Unable to get stack trace") + expect(stackTrace).toBeDefined() + }) + + it("should have a button bar", () => { + // Then + const copyCta = screen.getByText(ButtonLanguage.copyReport) + expect(copyCta).toBeDefined() + + const reloadCta = screen.getByText(ButtonLanguage.reloadApp) + expect(reloadCta).toBeDefined() + }) + + it("should have an email link", () => { + // Then + const emailLink = screen.getByText(RuntimeErrorStateLanguage.link) + expect(emailLink.closest("a")).toHaveAttribute("href", "mailto:support@coder.com") + }) +}) diff --git a/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx b/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx index 5d83eedc24e3d..bab209186d9d7 100644 --- a/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx +++ b/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx @@ -1,15 +1,15 @@ import Box from "@material-ui/core/Box" +import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline" import React, { useEffect, useReducer } from "react" -import { Link } from "react-router-dom" import { mapStackTrace } from "sourcemapped-stacktrace" import { Margins } from "../Margins/Margins" import { Section } from "../Section/Section" import { Typography } from "../Typography/Typography" import { reducer, RuntimeErrorReport, stackTraceAvailable, stackTraceUnavailable } from "./RuntimeErrorReport" -const Language = { +export const Language = { title: "Coder encountered an error", body: "Please copy the crash log using the button below and", link: "send it to us.", @@ -40,14 +40,7 @@ const ErrorStateDescription = () => { return ( {Language.body}  - { - window.location.href = "mailto:support@coder.com" - e.preventDefault() - }} - className={styles.link} - > + {Language.link} diff --git a/site/src/components/RuntimeErrorState/createCtas.tsx b/site/src/components/RuntimeErrorState/createCtas.tsx index 3be72455ec4d8..e41b5a4fdf9f1 100644 --- a/site/src/components/RuntimeErrorState/createCtas.tsx +++ b/site/src/components/RuntimeErrorState/createCtas.tsx @@ -4,7 +4,7 @@ import RefreshIcon from "@material-ui/icons/Refresh" import React from "react" import { CopyButton } from "../CopyButton/CopyButton" -const Language = { +export const Language = { reloadApp: "Reload Application", copyReport: "Copy Report", } From a5f1aa9a7c2572ca663fb04236714b6222462831 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Thu, 19 May 2022 21:07:09 +0000 Subject: [PATCH 5/6] committing lockfile --- site/yarn.lock | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/site/yarn.lock b/site/yarn.lock index cd898202a62f6..0ab541c8bc229 100644 --- a/site/yarn.lock +++ b/site/yarn.lock @@ -11991,6 +11991,11 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== +source-map@0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= + source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -12006,6 +12011,13 @@ source-map@^0.7.3, source-map@~0.7.2: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +sourcemapped-stacktrace@1.1.11: + version "1.1.11" + resolved "https://registry.yarnpkg.com/sourcemapped-stacktrace/-/sourcemapped-stacktrace-1.1.11.tgz#e2dede7fc148599c52a4f883276e527f8452657d" + integrity sha512-O0pcWjJqzQFVsisPlPXuNawJHHg9N9UgpJ/aDmvi9+vnS3x1C0NhwkVFzzZ1VN0Xo+bekyweoqYvBw5ZBKiNnQ== + dependencies: + source-map "0.5.6" + space-separated-tokens@^1.0.0: version "1.1.5" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" From bea96b41652c54cc8e29fd8c2687523a2c063b44 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Fri, 20 May 2022 14:30:13 +0000 Subject: [PATCH 6/6] added email body to help link --- .../RuntimeErrorState/RuntimeErrorReport.tsx | 23 +++++++++------- .../RuntimeErrorState.test.tsx | 2 +- .../RuntimeErrorState/RuntimeErrorState.tsx | 27 ++++++++++++++++--- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/site/src/components/RuntimeErrorState/RuntimeErrorReport.tsx b/site/src/components/RuntimeErrorState/RuntimeErrorReport.tsx index 0ab929d371acb..7206060e3906d 100644 --- a/site/src/components/RuntimeErrorState/RuntimeErrorReport.tsx +++ b/site/src/components/RuntimeErrorState/RuntimeErrorReport.tsx @@ -49,6 +49,17 @@ export const reducer = (model: ReportState, msg: ReportMessage): ReportState => } } +export const createFormattedStackTrace = (error: Error, mappedStack: string[] | null): string[] => { + return [ + "======================= STACK TRACE ========================", + "", + error.message, + ...(mappedStack ? mappedStack : []), + "", + "============================================================", + ] +} + /** * A code block component that contains the error stack resulting from an error boundary trigger */ @@ -59,16 +70,8 @@ export const RuntimeErrorReport = ({ error, mappedStack }: ReportState): React.R return } - const codeBlock = [ - "======================= STACK TRACE ========================", - "", - error.message, - ...mappedStack, - "", - "============================================================", - ] - - return + const formattedStackTrace = createFormattedStackTrace(error, mappedStack) + return } const useStyles = makeStyles(() => ({ diff --git a/site/src/components/RuntimeErrorState/RuntimeErrorState.test.tsx b/site/src/components/RuntimeErrorState/RuntimeErrorState.test.tsx index 0c923d43892f5..19a09cdde4d86 100644 --- a/site/src/components/RuntimeErrorState/RuntimeErrorState.test.tsx +++ b/site/src/components/RuntimeErrorState/RuntimeErrorState.test.tsx @@ -38,6 +38,6 @@ describe("RuntimeErrorState", () => { it("should have an email link", () => { // Then const emailLink = screen.getByText(RuntimeErrorStateLanguage.link) - expect(emailLink.closest("a")).toHaveAttribute("href", "mailto:support@coder.com") + expect(emailLink.closest("a")).toHaveAttribute("href", expect.stringContaining("mailto:support@coder.com")) }) }) diff --git a/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx b/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx index bab209186d9d7..9f2c7ec5e9790 100644 --- a/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx +++ b/site/src/components/RuntimeErrorState/RuntimeErrorState.tsx @@ -7,7 +7,13 @@ import { mapStackTrace } from "sourcemapped-stacktrace" import { Margins } from "../Margins/Margins" import { Section } from "../Section/Section" import { Typography } from "../Typography/Typography" -import { reducer, RuntimeErrorReport, stackTraceAvailable, stackTraceUnavailable } from "./RuntimeErrorReport" +import { + createFormattedStackTrace, + reducer, + RuntimeErrorReport, + stackTraceAvailable, + stackTraceUnavailable, +} from "./RuntimeErrorReport" export const Language = { title: "Coder encountered an error", @@ -35,12 +41,17 @@ const ErrorStateTitle = () => { /** * A description for our error boundary UI */ -const ErrorStateDescription = () => { +const ErrorStateDescription = ({ emailBody }: { emailBody?: string }) => { const styles = useStyles() return ( {Language.body}  - + {Language.link} @@ -65,7 +76,15 @@ export const RuntimeErrorState: React.FC = ({ error }) = return ( -
} description={}> +
} + description={ + + } + >
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