Skip to content

Commit 83c35bb

Browse files
authored
feat: display specific errors if templates page fails (#4023)
* Surface templates page errors * Format * Separate error messages * Fix story * Format * Format * Fix imports * Remove unnecessary check * Format
1 parent 21e8fb2 commit 83c35bb

File tree

8 files changed

+217
-175
lines changed

8 files changed

+217
-175
lines changed

site/src/AppRouter.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { NotFoundPage } from "./pages/404Page/404Page"
1616
import { CliAuthenticationPage } from "./pages/CliAuthPage/CliAuthPage"
1717
import { HealthzPage } from "./pages/HealthzPage/HealthzPage"
1818
import { LoginPage } from "./pages/LoginPage/LoginPage"
19-
import TemplatesPage from "./pages/TemplatesPage/TemplatesPage"
19+
import { TemplatesPage } from "./pages/TemplatesPage/TemplatesPage"
2020
import { AccountPage } from "./pages/UserSettingsPage/AccountPage/AccountPage"
2121
import { SecurityPage } from "./pages/UserSettingsPage/SecurityPage/SecurityPage"
2222
import { SSHKeysPage } from "./pages/UserSettingsPage/SSHKeysPage/SSHKeysPage"

site/src/i18n/en/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import auditLog from "./auditLog.json"
22
import common from "./common.json"
33
import templatePage from "./templatePage.json"
4+
import templatesPage from "./templatesPage.json"
45
import workspacePage from "./workspacePage.json"
56

67
export const en = {
78
common,
89
workspacePage,
910
auditLog,
1011
templatePage,
12+
templatesPage,
1113
}

site/src/i18n/en/templatesPage.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"errors": {
3+
"getOrganizationError": "Something went wrong fetching organizations.",
4+
"getTemplatesError": "Something went wrong fetching templates."
5+
}
6+
}

site/src/pages/TemplatesPage/TemplatesPage.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as CreateDayString from "util/createDayString"
44
import { MockTemplate } from "../../testHelpers/entities"
55
import { history, render } from "../../testHelpers/renderHelpers"
66
import { server } from "../../testHelpers/server"
7-
import TemplatesPage from "./TemplatesPage"
7+
import { TemplatesPage } from "./TemplatesPage"
88
import { Language } from "./TemplatesPageView"
99

1010
describe("TemplatesPage", () => {

site/src/pages/TemplatesPage/TemplatesPage.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,24 @@ import { XServiceContext } from "../../xServices/StateContext"
66
import { templatesMachine } from "../../xServices/templates/templatesXService"
77
import { TemplatesPageView } from "./TemplatesPageView"
88

9-
const TemplatesPage: React.FC = () => {
9+
export const TemplatesPage: React.FC = () => {
1010
const xServices = useContext(XServiceContext)
1111
const [authState] = useActor(xServices.authXService)
1212
const [templatesState] = useMachine(templatesMachine)
13+
const { templates, getOrganizationsError, getTemplatesError } = templatesState.context
1314

1415
return (
1516
<>
1617
<Helmet>
1718
<title>{pageTitle("Templates")}</title>
1819
</Helmet>
1920
<TemplatesPageView
20-
templates={templatesState.context.templates}
21+
templates={templates}
2122
canCreateTemplate={authState.context.permissions?.createTemplates}
2223
loading={templatesState.hasTag("loading")}
24+
getOrganizationsError={getOrganizationsError}
25+
getTemplatesError={getTemplatesError}
2326
/>
2427
</>
2528
)
2629
}
27-
28-
export default TemplatesPage

site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ComponentMeta, Story } from "@storybook/react"
2-
import { MockTemplate } from "../../testHelpers/entities"
2+
import { makeMockApiError, MockTemplate } from "../../testHelpers/entities"
33
import { TemplatesPageView, TemplatesPageViewProps } from "./TemplatesPageView"
44

55
export default {
@@ -49,3 +49,8 @@ EmptyCanCreate.args = {
4949

5050
export const EmptyCannotCreate = Template.bind({})
5151
EmptyCannotCreate.args = {}
52+
53+
export const Error = Template.bind({})
54+
Error.args = {
55+
getTemplatesError: makeMockApiError({ message: "Something went wrong fetching templates." }),
56+
}

site/src/pages/TemplatesPage/TemplatesPageView.tsx

+109-83
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import TableHead from "@material-ui/core/TableHead"
88
import TableRow from "@material-ui/core/TableRow"
99
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
1010
import useTheme from "@material-ui/styles/useTheme"
11+
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
1112
import { FC } from "react"
13+
import { useTranslation } from "react-i18next"
1214
import { useNavigate } from "react-router-dom"
1315
import { createDayString } from "util/createDayString"
1416
import { formatTemplateActiveDevelopers } from "util/templates"
@@ -77,12 +79,20 @@ export interface TemplatesPageViewProps {
7779
loading?: boolean
7880
canCreateTemplate?: boolean
7981
templates?: TypesGen.Template[]
82+
getOrganizationsError?: Error | unknown
83+
getTemplatesError?: Error | unknown
8084
}
8185

8286
export const TemplatesPageView: FC<React.PropsWithChildren<TemplatesPageViewProps>> = (props) => {
8387
const styles = useStyles()
8488
const navigate = useNavigate()
89+
const { t } = useTranslation("templatesPage")
8590
const theme: Theme = useTheme()
91+
const empty =
92+
!props.loading &&
93+
!props.getOrganizationsError &&
94+
!props.getTemplatesError &&
95+
!props.templates?.length
8696

8797
return (
8898
<Margins>
@@ -114,94 +124,110 @@ export const TemplatesPageView: FC<React.PropsWithChildren<TemplatesPageViewProp
114124
)}
115125
</PageHeader>
116126

117-
<TableContainer>
118-
<Table>
119-
<TableHead>
120-
<TableRow>
121-
<TableCell width="50%">{Language.nameLabel}</TableCell>
122-
<TableCell width="16%">{Language.usedByLabel}</TableCell>
123-
<TableCell width="16%">{Language.lastUpdatedLabel}</TableCell>
124-
<TableCell width="16%">{Language.createdByLabel}</TableCell>
125-
<TableCell width="1%"></TableCell>
126-
</TableRow>
127-
</TableHead>
128-
<TableBody>
129-
{props.loading && <TableLoader />}
130-
{!props.loading && !props.templates?.length && (
127+
{props.getOrganizationsError ? (
128+
<ErrorSummary
129+
error={props.getOrganizationsError}
130+
defaultMessage={t("errors.getOrganizationsError")}
131+
/>
132+
) : props.getTemplatesError ? (
133+
<ErrorSummary
134+
error={props.getTemplatesError}
135+
defaultMessage={t("errors.getTemplatesError")}
136+
/>
137+
) : (
138+
<TableContainer>
139+
<Table>
140+
<TableHead>
131141
<TableRow>
132-
<TableCell colSpan={999}>
133-
<EmptyState
134-
message={Language.emptyMessage}
135-
description={
136-
props.canCreateTemplate
137-
? Language.emptyDescription
138-
: Language.emptyViewNoPerms
139-
}
140-
descriptionClassName={styles.emptyDescription}
141-
cta={<CodeExample code="coder templates init" />}
142-
/>
143-
</TableCell>
142+
<TableCell width="50%">{Language.nameLabel}</TableCell>
143+
<TableCell width="16%">{Language.usedByLabel}</TableCell>
144+
<TableCell width="16%">{Language.lastUpdatedLabel}</TableCell>
145+
<TableCell width="16%">{Language.createdByLabel}</TableCell>
146+
<TableCell width="1%"></TableCell>
144147
</TableRow>
145-
)}
146-
{props.templates?.map((template) => {
147-
const templatePageLink = `/templates/${template.name}`
148-
const hasIcon = template.icon && template.icon !== ""
149-
150-
return (
151-
<TableRow
152-
key={template.id}
153-
hover
154-
data-testid={`template-${template.id}`}
155-
tabIndex={0}
156-
onKeyDown={(event) => {
157-
if (event.key === "Enter") {
158-
navigate(templatePageLink)
159-
}
160-
}}
161-
className={styles.clickableTableRow}
162-
>
163-
<TableCellLink to={templatePageLink}>
164-
<AvatarData
165-
title={template.name}
166-
subtitle={template.description}
167-
highlightTitle
168-
avatar={
169-
hasIcon ? (
170-
<div className={styles.templateIconWrapper}>
171-
<img alt="" src={template.icon} />
172-
</div>
173-
) : undefined
148+
</TableHead>
149+
<TableBody>
150+
{props.loading && <TableLoader />}
151+
152+
{empty ? (
153+
<TableRow>
154+
<TableCell colSpan={999}>
155+
<EmptyState
156+
message={Language.emptyMessage}
157+
description={
158+
props.canCreateTemplate
159+
? Language.emptyDescription
160+
: Language.emptyViewNoPerms
174161
}
162+
descriptionClassName={styles.emptyDescription}
163+
cta={<CodeExample code="coder templates init" />}
175164
/>
176-
</TableCellLink>
177-
178-
<TableCellLink to={templatePageLink}>
179-
<span style={{ color: theme.palette.text.secondary }}>
180-
{Language.developerCount(template.active_user_count)}
181-
</span>
182-
</TableCellLink>
183-
184-
<TableCellLink data-chromatic="ignore" to={templatePageLink}>
185-
<span style={{ color: theme.palette.text.secondary }}>
186-
{createDayString(template.updated_at)}
187-
</span>
188-
</TableCellLink>
189-
<TableCellLink to={templatePageLink}>
190-
<span style={{ color: theme.palette.text.secondary }}>
191-
{template.created_by_name}
192-
</span>
193-
</TableCellLink>
194-
<TableCellLink to={templatePageLink}>
195-
<div className={styles.arrowCell}>
196-
<KeyboardArrowRight className={styles.arrowRight} />
197-
</div>
198-
</TableCellLink>
165+
</TableCell>
199166
</TableRow>
200-
)
201-
})}
202-
</TableBody>
203-
</Table>
204-
</TableContainer>
167+
) : (
168+
props.templates?.map((template) => {
169+
const templatePageLink = `/templates/${template.name}`
170+
const hasIcon = template.icon && template.icon !== ""
171+
172+
return (
173+
<TableRow
174+
key={template.id}
175+
hover
176+
data-testid={`template-${template.id}`}
177+
tabIndex={0}
178+
onKeyDown={(event) => {
179+
if (event.key === "Enter") {
180+
navigate(templatePageLink)
181+
}
182+
}}
183+
className={styles.clickableTableRow}
184+
>
185+
<TableCellLink to={templatePageLink}>
186+
<AvatarData
187+
title={template.name}
188+
subtitle={template.description}
189+
highlightTitle
190+
avatar={
191+
hasIcon && (
192+
<div className={styles.templateIconWrapper}>
193+
<img alt="" src={template.icon} />
194+
</div>
195+
)
196+
}
197+
/>
198+
</TableCellLink>
199+
200+
<TableCellLink to={templatePageLink}>
201+
<span style={{ color: theme.palette.text.secondary }}>
202+
{Language.developerCount(template.active_user_count)}
203+
</span>
204+
</TableCellLink>
205+
206+
<TableCellLink data-chromatic="ignore" to={templatePageLink}>
207+
<span style={{ color: theme.palette.text.secondary }}>
208+
{createDayString(template.updated_at)}
209+
</span>
210+
</TableCellLink>
211+
212+
<TableCellLink to={templatePageLink}>
213+
<span style={{ color: theme.palette.text.secondary }}>
214+
{template.created_by_name}
215+
</span>
216+
</TableCellLink>
217+
218+
<TableCellLink to={templatePageLink}>
219+
<div className={styles.arrowCell}>
220+
<KeyboardArrowRight className={styles.arrowRight} />
221+
</div>
222+
</TableCellLink>
223+
</TableRow>
224+
)
225+
})
226+
)}
227+
</TableBody>
228+
</Table>
229+
</TableContainer>
230+
)}
205231
</Margins>
206232
)
207233
}

0 commit comments

Comments
 (0)