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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 5 additions & 4 deletions
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

Lines changed: 6 additions & 1 deletion
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

Lines changed: 109 additions & 83 deletions
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)
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