chore: replace old ErrorSummary component (#4417)

* replaced error summary

* fixed tests

* positioning caret
This commit is contained in:
Kira Pilot
2022-10-07 13:14:32 -04:00
committed by GitHub
parent 2b6586d542
commit 257df81667
21 changed files with 103 additions and 342 deletions
@@ -85,7 +85,8 @@ const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
backgroundColor: `${colors.gray[16]}`,
"& svg": {
// targeting the alert icon rather than the expander icon
"& svg:nth-child(2)": {
marginTop: props.hasDetail ? `${theme.spacing(1)}px` : "inherit",
marginRight: `${theme.spacing(1)}px`,
},
@@ -1,61 +0,0 @@
import { action } from "@storybook/addon-actions"
import { ComponentMeta, Story } from "@storybook/react"
import { ErrorSummary, ErrorSummaryProps } from "./ErrorSummary"
export default {
title: "components/ErrorSummary",
component: ErrorSummary,
} as ComponentMeta<typeof ErrorSummary>
const Template: Story<ErrorSummaryProps> = (args) => <ErrorSummary {...args} />
export const WithError = Template.bind({})
WithError.args = {
error: new Error("Something went wrong!"),
}
export const WithRetry = Template.bind({})
WithRetry.args = {
error: new Error("Failed to fetch something!"),
retry: () => {
action("retry")
},
}
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,
}
@@ -1,81 +0,0 @@
import { fireEvent, render, screen } from "@testing-library/react"
import { ErrorSummary } from "./ErrorSummary"
import { i18n } from "i18n"
const { t } = i18n
describe("ErrorSummary", () => {
it("renders", async () => {
// When
const error = new Error("test error message")
render(<ErrorSummary error={error} />)
// Then
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(<ErrorSummary error={error} />)
// Then
const expandText = t("ctas.expand", { ns: "common" })
fireEvent.click(screen.getByText(expandText))
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(<ErrorSummary error={error} />)
// Then
const expandText = t("ctas.expand", { ns: "common" })
const collapseText = t("ctas.collapse", { ns: "common" })
fireEvent.click(screen.getByText(expandText))
fireEvent.click(screen.getByText(collapseText))
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(<ErrorSummary error={error} dismissible />)
// 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()
})
})
@@ -1,129 +0,0 @@
import Button from "@material-ui/core/Button"
import Collapse from "@material-ui/core/Collapse"
import IconButton from "@material-ui/core/IconButton"
import { darken, lighten, 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 { Expander } from "components/Expander/Expander"
import { Stack } from "components/Stack/Stack"
import { FC, useState } from "react"
export const Language = {
retryMessage: "Retry",
unknownErrorMessage: "An unknown error has occurred",
moreDetails: "More",
lessDetails: "Less",
}
export interface ErrorSummaryProps {
error: ApiError | Error | unknown
retry?: () => void
dismissible?: boolean
defaultMessage?: string
}
export const ErrorSummary: FC<React.PropsWithChildren<ErrorSummaryProps>> = ({
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 closeError = () => {
setOpen(false)
}
if (!isOpen) {
return null
}
return (
<Stack className={styles.root}>
<Stack direction="row" alignItems="center" className={styles.messageBox}>
<Stack direction="row" spacing={0}>
<span className={styles.errorMessage}>{message}</span>
{Boolean(detail) && <Expander expanded={showDetails} setExpanded={setShowDetails} />}
</Stack>
{dismissible && (
<IconButton onClick={closeError} className={styles.iconButton}>
<CloseIcon className={styles.closeIcon} />
</IconButton>
)}
</Stack>
<Collapse in={showDetails}>
<div className={styles.details}>{detail}</div>
</Collapse>
{retry && (
<div className={styles.retry}>
<Button
size="small"
onClick={retry}
startIcon={<RefreshIcon />}
variant="outlined"
className={styles.retryButton}
>
{Language.retryMessage}
</Button>
</div>
)}
</Stack>
)
}
interface StyleProps {
showDetails?: boolean
}
const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
root: {
background: darken(theme.palette.error.main, 0.6),
padding: `${theme.spacing(2)}px`,
borderRadius: theme.shape.borderRadius,
gap: 0,
},
flex: {
display: "flex",
},
messageBox: {
justifyContent: "space-between",
},
errorMessage: {
marginRight: `${theme.spacing(1)}px`,
},
detailsLink: {
cursor: "pointer",
color: `${lighten(theme.palette.primary.light, 0.2)}`,
},
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,
},
closeIcon: {
width: 25,
height: 25,
color: theme.palette.primary.contrastText,
},
retry: {
marginTop: `${theme.spacing(2)}px`,
},
retryButton: {
color: theme.palette.error.contrastText,
borderColor: theme.palette.error.contrastText,
"&:hover": {
backgroundColor: theme.palette.error.dark,
},
},
}))
+2 -2
View File
@@ -9,7 +9,6 @@ import TableRow from "@material-ui/core/TableRow"
import { Skeleton } from "@material-ui/lab"
import useTheme from "@material-ui/styles/useTheme"
import { CloseDropdown, OpenDropdown } from "components/DropdownArrows/DropdownArrows"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { PortForwardButton } from "components/PortForwardButton/PortForwardButton"
import { TableCellDataPrimary } from "components/TableCellData/TableCellData"
import { FC, useState } from "react"
@@ -25,6 +24,7 @@ import { AgentOutdatedTooltip } from "../Tooltips/AgentOutdatedTooltip"
import { ResourcesHelpTooltip } from "../Tooltips/ResourcesHelpTooltip"
import { ResourceAgentLatency } from "./ResourceAgentLatency"
import { ResourceAvatarData } from "./ResourceAvatarData"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
const Language = {
resources: "Resources",
@@ -68,7 +68,7 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
<Stack direction="column" spacing={1}>
<div aria-label={Language.resources} className={styles.wrapper}>
{getResourcesError ? (
<ErrorSummary error={getResourcesError} />
<AlertBanner severity="error" error={getResourcesError} />
) : (
<TableContainer className={styles.tableContainer}>
<Table>
@@ -1,11 +1,11 @@
import TextField from "@material-ui/core/TextField"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { FormikContextType, FormikTouched, useFormik } from "formik"
import { FC } from "react"
import * as Yup from "yup"
import { getFormHelpers, nameValidator, onChangeTrimmed } from "../../util/formUtils"
import { LoadingButton } from "../LoadingButton/LoadingButton"
import { Stack } from "../Stack/Stack"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
export interface AccountFormValues {
username: string
@@ -53,7 +53,9 @@ export const AccountForm: FC<React.PropsWithChildren<AccountFormProps>> = ({
<>
<form onSubmit={form.handleSubmit}>
<Stack>
{updateProfileError ? <ErrorSummary error={updateProfileError} /> : <></>}
{Boolean(updateProfileError) && (
<AlertBanner severity="error" error={updateProfileError} />
)}
<TextField
disabled
fullWidth
@@ -1,11 +1,11 @@
import TextField from "@material-ui/core/TextField"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { FormikContextType, FormikTouched, useFormik } from "formik"
import React from "react"
import * as Yup from "yup"
import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils"
import { LoadingButton } from "../LoadingButton/LoadingButton"
import { Stack } from "../Stack/Stack"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
interface SecurityFormValues {
old_password: string
@@ -68,7 +68,9 @@ export const SecurityForm: React.FC<SecurityFormProps> = ({
<>
<form onSubmit={form.handleSubmit}>
<Stack>
{updateSecurityError ? <ErrorSummary error={updateSecurityError} /> : <></>}
{Boolean(updateSecurityError) && (
<AlertBanner severity="error" error={updateSecurityError} />
)}
<TextField
{...getFieldHelpers("old_password")}
onChange={onChangeTrimmed(form)}
+11 -9
View File
@@ -5,7 +5,6 @@ import { makeStyles } from "@material-ui/core/styles"
import TextField from "@material-ui/core/TextField"
import GitHubIcon from "@material-ui/icons/GitHub"
import KeyIcon from "@material-ui/icons/VpnKey"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { Stack } from "components/Stack/Stack"
import { FormikContextType, FormikTouched, useFormik } from "formik"
import { FC } from "react"
@@ -14,6 +13,7 @@ import { AuthMethods } from "../../api/typesGenerated"
import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils"
import { Welcome } from "../Welcome/Welcome"
import { LoadingButton } from "./../LoadingButton/LoadingButton"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
/**
* BuiltInAuthFormValues describes a form using built-in (email/password)
@@ -120,14 +120,16 @@ export const SignInForm: FC<React.PropsWithChildren<SignInFormProps>> = ({
<Welcome />
<form onSubmit={form.handleSubmit}>
<Stack>
{Object.keys(loginErrors).map((errorKey: string) =>
loginErrors[errorKey as LoginErrors] ? (
<ErrorSummary
key={errorKey}
error={loginErrors[errorKey as LoginErrors]}
defaultMessage={Language.errorMessages[errorKey as LoginErrors]}
/>
) : null,
{Object.keys(loginErrors).map(
(errorKey: string) =>
Boolean(loginErrors[errorKey as LoginErrors]) && (
<AlertBanner
key={errorKey}
severity="error"
error={loginErrors[errorKey as LoginErrors]}
text={Language.errorMessages[errorKey as LoginErrors]}
/>
),
)}
<TextField
{...getFieldHelpers("email")}
+15 -5
View File
@@ -1,5 +1,4 @@
import { makeStyles } from "@material-ui/core/styles"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"
import { FC } from "react"
import { useNavigate } from "react-router-dom"
@@ -79,17 +78,25 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
const hasTemplateIcon = workspace.template_icon && workspace.template_icon !== ""
const buildError = Boolean(workspaceErrors[WorkspaceErrors.BUILD_ERROR]) && (
<ErrorSummary error={workspaceErrors[WorkspaceErrors.BUILD_ERROR]} dismissible />
<AlertBanner
severity="error"
error={workspaceErrors[WorkspaceErrors.BUILD_ERROR]}
dismissible
/>
)
const cancellationError = Boolean(workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR]) && (
<ErrorSummary error={workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR]} dismissible />
<AlertBanner
severity="error"
error={workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR]}
dismissible
/>
)
const workspaceRefreshWarning = Boolean(workspaceErrors[WorkspaceErrors.GET_RESOURCES_ERROR]) && (
<AlertBanner
text={t("warningsAndErrors.workspaceRefreshWarning")}
severity="warning"
text={t("warningsAndErrors.workspaceRefreshWarning")}
dismissible
/>
)
@@ -161,7 +168,10 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
<WorkspaceSection title="Logs" contentsProps={{ className: styles.timelineContents }}>
{workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? (
<ErrorSummary error={workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR]} />
<AlertBanner
severity="error"
error={workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR]}
/>
) : (
<BuildsTable builds={builds} className={styles.timelineTable} />
)}
@@ -2,7 +2,7 @@ import Box from "@material-ui/core/Box"
import LinearProgress from "@material-ui/core/LinearProgress"
import { makeStyles } from "@material-ui/core/styles"
import Skeleton from "@material-ui/lab/Skeleton"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { Stack } from "components/Stack/Stack"
import { FC } from "react"
import * as TypesGen from "../../api/typesGenerated"
@@ -28,7 +28,7 @@ export const WorkspaceQuota: FC<WorkspaceQuotaProps> = ({ quota, error }) => {
<Box>
<Stack spacing={1} className={styles.stack}>
<span className={styles.title}>Workspace Quota</span>
<ErrorSummary error={error} />
<AlertBanner severity="error" error={error} />
</Stack>
</Box>
)
@@ -8,7 +8,7 @@ import MenuItem from "@material-ui/core/MenuItem"
import makeStyles from "@material-ui/core/styles/makeStyles"
import Switch from "@material-ui/core/Switch"
import TextField from "@material-ui/core/TextField"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { Section } from "components/Section/Section"
import dayjs from "dayjs"
import advancedFormat from "dayjs/plugin/advancedFormat"
@@ -227,7 +227,9 @@ export const WorkspaceScheduleForm: FC<React.PropsWithChildren<WorkspaceSchedule
<FullPageForm onCancel={onCancel} title={Language.formTitle}>
<form onSubmit={form.handleSubmit} className={styles.form}>
<Stack>
{submitScheduleError ? <ErrorSummary error={submitScheduleError} /> : <></>}
{Boolean(submitScheduleError) && (
<AlertBanner severity="error" error={submitScheduleError} />
)}
<Section title={Language.startSection}>
<FormControlLabel
control={
@@ -1,6 +1,5 @@
import TextField from "@material-ui/core/TextField"
import * as TypesGen from "api/typesGenerated"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { FormFooter } from "components/FormFooter/FormFooter"
import { FullPageForm } from "components/FullPageForm/FullPageForm"
import { Loader } from "components/Loader/Loader"
@@ -14,6 +13,7 @@ import { FC, useState } from "react"
import { useTranslation } from "react-i18next"
import { getFormHelpers, nameValidator, onChangeTrimmed } from "util/formUtils"
import * as Yup from "yup"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
export enum CreateWorkspaceErrors {
GET_TEMPLATES_ERROR = "getTemplatesError",
@@ -98,16 +98,18 @@ export const CreateWorkspacePageView: FC<React.PropsWithChildren<CreateWorkspace
if (props.hasTemplateErrors) {
return (
<Stack>
{props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR] ? (
<ErrorSummary
{Boolean(props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR]) && (
<AlertBanner
severity="error"
error={props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATES_ERROR]}
/>
) : null}
{props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR] ? (
<ErrorSummary
)}
{Boolean(props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]) && (
<AlertBanner
severity="error"
error={props.createWorkspaceErrors[CreateWorkspaceErrors.GET_TEMPLATE_SCHEMA_ERROR]}
/>
) : null}
)}
</Stack>
)
}
@@ -122,7 +124,8 @@ export const CreateWorkspacePageView: FC<React.PropsWithChildren<CreateWorkspace
<form onSubmit={form.handleSubmit}>
<Stack>
{Boolean(props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]) && (
<ErrorSummary
<AlertBanner
severity="error"
error={props.createWorkspaceErrors[CreateWorkspaceErrors.CREATE_WORKSPACE_ERROR]}
/>
)}
+2 -2
View File
@@ -1,7 +1,7 @@
import { makeStyles } from "@material-ui/core/styles"
import { useMachine, useSelector } from "@xstate/react"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { Margins } from "components/Margins/Margins"
import { FC, useContext } from "react"
import { Helmet } from "react-helmet-async"
@@ -57,7 +57,7 @@ export const TemplatePage: FC<React.PropsWithChildren<unknown>> = () => {
return (
<Margins>
<div className={styles.errorBox}>
<ErrorSummary error={getTemplateError} />
<AlertBanner severity="error" error={getTemplateError} />
</div>
</Margins>
)
@@ -6,7 +6,7 @@ import AddCircleOutline from "@material-ui/icons/AddCircleOutline"
import SettingsOutlined from "@material-ui/icons/SettingsOutlined"
import { DeleteButton } from "components/DropdownButton/ActionCtas"
import { DropdownButton } from "components/DropdownButton/DropdownButton"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { Markdown } from "components/Markdown/Markdown"
import frontMatter from "front-matter"
import { FC } from "react"
@@ -65,10 +65,8 @@ export const TemplatePageView: FC<React.PropsWithChildren<TemplatePageViewProps>
const readme = frontMatter(activeTemplateVersion.readme)
const hasIcon = template.icon && template.icon !== ""
const deleteError = deleteTemplateError ? (
<ErrorSummary error={deleteTemplateError} dismissible />
) : (
<></>
const deleteError = Boolean(deleteTemplateError) && (
<AlertBanner severity="error" error={deleteTemplateError} dismissible />
)
const getStartedResources = (resources: WorkspaceResource[]) => {
@@ -1,5 +1,5 @@
import { Template, UpdateTemplateMeta } from "api/typesGenerated"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { FullPageForm } from "components/FullPageForm/FullPageForm"
import { Loader } from "components/Loader/Loader"
import { ComponentProps, FC } from "react"
@@ -33,7 +33,9 @@ export const TemplateSettingsPageView: FC<TemplateSettingsPageViewProps> = ({
return (
<FullPageForm title={Language.title} onCancel={onCancel}>
{Boolean(errors.getTemplateError) && <ErrorSummary error={errors.getTemplateError} />}
{Boolean(errors.getTemplateError) && (
<AlertBanner severity="error" error={errors.getTemplateError} />
)}
{isLoading && <Loader />}
{template && (
<TemplateSettingsForm
@@ -8,9 +8,9 @@ import TableHead from "@material-ui/core/TableHead"
import TableRow from "@material-ui/core/TableRow"
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
import useTheme from "@material-ui/styles/useTheme"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import { Maybe } from "components/Conditionals/Maybe"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { FC } from "react"
import { useTranslation } from "react-i18next"
import { useNavigate } from "react-router-dom"
@@ -128,15 +128,17 @@ export const TemplatesPageView: FC<React.PropsWithChildren<TemplatesPageViewProp
<ChooseOne>
<Cond condition={Boolean(props.getOrganizationsError)}>
<ErrorSummary
<AlertBanner
severity="error"
error={props.getOrganizationsError}
defaultMessage={t("errors.getOrganizationsError")}
text={t("errors.getOrganizationsError")}
/>
</Cond>
<Cond condition={Boolean(props.getTemplatesError)}>
<ErrorSummary
<AlertBanner
severity="error"
error={props.getTemplatesError}
defaultMessage={t("errors.getTemplatesError")}
text={t("errors.getTemplatesError")}
/>
</Cond>
<Cond>
@@ -1,11 +1,13 @@
import { fireEvent, screen, waitFor } from "@testing-library/react"
import { Language as ErrorSummaryLanguage } from "components/ErrorSummary/ErrorSummary"
import * as API from "../../../api/api"
import { GlobalSnackbar } from "../../../components/GlobalSnackbar/GlobalSnackbar"
import * as AccountForm from "../../../components/SettingsAccountForm/SettingsAccountForm"
import { renderWithAuth } from "../../../testHelpers/renderHelpers"
import * as AuthXService from "../../../xServices/auth/authXService"
import { AccountPage } from "./AccountPage"
import i18next from "i18next"
const { t } = i18next
const renderPage = () => {
return renderWithAuth(
@@ -83,7 +85,8 @@ describe("AccountPage", () => {
const { user } = renderPage()
await fillAndSubmitForm()
const errorMessage = await screen.findByText(ErrorSummaryLanguage.unknownErrorMessage)
const errorText = t("warningsAndErrors.somethingWentWrong", { ns: "common" })
const errorMessage = await screen.findByText(errorText)
expect(errorMessage).toBeDefined()
expect(API.updateProfile).toBeCalledTimes(1)
expect(API.updateProfile).toBeCalledWith(user.id, newData)
@@ -2,8 +2,8 @@ import Box from "@material-ui/core/Box"
import Button from "@material-ui/core/Button"
import CircularProgress from "@material-ui/core/CircularProgress"
import { GitSSHKey } from "api/typesGenerated"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { CodeExample } from "components/CodeExample/CodeExample"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { Stack } from "components/Stack/Stack"
import { FC } from "react"
@@ -42,15 +42,14 @@ export const SSHKeysPageView: FC<React.PropsWithChildren<SSHKeysPageViewProps>>
{/* Regenerating the key is not an option if getSSHKey fails.
Only one of the error messages will exist at a single time */}
{getSSHKeyError ? <ErrorSummary error={getSSHKeyError} /> : <></>}
{regenerateSSHKeyError ? (
<ErrorSummary
{Boolean(getSSHKeyError) && <AlertBanner severity="error" error={getSSHKeyError} />}
{Boolean(regenerateSSHKeyError) && (
<AlertBanner
severity="error"
error={regenerateSSHKeyError}
defaultMessage={Language.errorRegenerateSSHKey}
text={Language.errorRegenerateSSHKey}
dismissible
/>
) : (
<></>
)}
{hasLoaded && sshKey && (
<>
@@ -1,11 +1,13 @@
import { fireEvent, screen, waitFor } from "@testing-library/react"
import { Language as ErrorSummaryLanguage } from "components/ErrorSummary/ErrorSummary"
import * as API from "../../../api/api"
import { GlobalSnackbar } from "../../../components/GlobalSnackbar/GlobalSnackbar"
import * as SecurityForm from "../../../components/SettingsSecurityForm/SettingsSecurityForm"
import { renderWithAuth } from "../../../testHelpers/renderHelpers"
import * as AuthXService from "../../../xServices/auth/authXService"
import { SecurityPage } from "./SecurityPage"
import i18next from "i18next"
const { t } = i18next
const renderPage = () => {
return renderWithAuth(
@@ -105,7 +107,8 @@ describe("SecurityPage", () => {
const { user } = renderPage()
await fillAndSubmitForm()
const errorMessage = await screen.findByText(ErrorSummaryLanguage.unknownErrorMessage)
const errorText = t("warningsAndErrors.somethingWentWrong", { ns: "common" })
const errorMessage = await screen.findByText(errorText)
expect(errorMessage).toBeDefined()
expect(API.updateUserPassword).toBeCalledTimes(1)
expect(API.updateUserPassword).toBeCalledWith(user.id, newData)
+11 -7
View File
@@ -1,12 +1,12 @@
import { makeStyles } from "@material-ui/core/styles"
import { useMachine } from "@xstate/react"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import { FC, useEffect } from "react"
import { useParams } from "react-router-dom"
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary"
import { FullScreenLoader } from "../../components/Loader/FullScreenLoader"
import { firstOrItem } from "../../util/array"
import { workspaceMachine } from "../../xServices/workspace/workspaceXService"
import { FullScreenLoader } from "components/Loader/FullScreenLoader"
import { firstOrItem } from "util/array"
import { workspaceMachine } from "xServices/workspace/workspaceXService"
import { WorkspaceReadyPage } from "./WorkspaceReadyPage"
export const WorkspacePage: FC = () => {
@@ -30,9 +30,13 @@ export const WorkspacePage: FC = () => {
<ChooseOne>
<Cond condition={workspaceState.matches("error")}>
<div className={styles.error}>
{Boolean(getWorkspaceError) && <ErrorSummary error={getWorkspaceError} />}
{Boolean(getTemplateWarning) && <ErrorSummary error={getTemplateWarning} />}
{Boolean(checkPermissionsError) && <ErrorSummary error={checkPermissionsError} />}
{Boolean(getWorkspaceError) && <AlertBanner severity="error" error={getWorkspaceError} />}
{Boolean(getTemplateWarning) && (
<AlertBanner severity="error" error={getTemplateWarning} />
)}
{Boolean(checkPermissionsError) && (
<AlertBanner severity="error" error={checkPermissionsError} />
)}
</div>
</Cond>
<Cond condition={Boolean(workspace) && workspaceState.matches("ready")}>
@@ -1,10 +1,10 @@
import { useMachine } from "@xstate/react"
import { AlertBanner } from "components/AlertBanner/AlertBanner"
import { scheduleToAutoStart } from "pages/WorkspaceSchedulePage/schedule"
import { ttlMsToAutoStop } from "pages/WorkspaceSchedulePage/ttl"
import React, { useEffect, useState } from "react"
import { Navigate, useNavigate, useParams } from "react-router-dom"
import * as TypesGen from "../../api/typesGenerated"
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary"
import { FullScreenLoader } from "../../components/Loader/FullScreenLoader"
import { WorkspaceScheduleForm } from "../../components/WorkspaceScheduleForm/WorkspaceScheduleForm"
import { firstOrItem } from "../../util/array"
@@ -59,18 +59,17 @@ export const WorkspaceSchedulePage: React.FC = () => {
if (scheduleState.matches("error")) {
return (
<ErrorSummary
<AlertBanner
severity="error"
error={getWorkspaceError || checkPermissionsError}
defaultMessage={
getWorkspaceError ? Language.getWorkspaceError : Language.checkPermissionsError
}
text={getWorkspaceError ? Language.getWorkspaceError : Language.checkPermissionsError}
retry={() => scheduleSend({ type: "GET_WORKSPACE", username, workspaceName })}
/>
)
}
if (!permissions?.updateWorkspace) {
return <ErrorSummary error={Error(Language.forbiddenError)} />
return <AlertBanner severity="error" error={Error(Language.forbiddenError)} />
}
if (scheduleState.matches("presentForm") || scheduleState.matches("submittingSchedule")) {