mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: replace old ErrorSummary component (#4417)
* replaced error summary * fixed tests * positioning caret
This commit is contained in:
@@ -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,
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -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)}
|
||||
|
||||
@@ -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")}
|
||||
|
||||
@@ -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]}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
Reference in New Issue
Block a user