Skip to content

Commit ccdf82d

Browse files
authored
feat: show template versions (coder#3003)
1 parent 9a5fa3f commit ccdf82d

File tree

10 files changed

+171
-4
lines changed

10 files changed

+171
-4
lines changed

site/src/api/api.ts

+9
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,15 @@ export const getTemplateVersionResources = async (
136136
return response.data
137137
}
138138

139+
export const getTemplateVersions = async (
140+
templateId: string,
141+
): Promise<TypesGen.TemplateVersion[]> => {
142+
const response = await axios.get<TypesGen.TemplateVersion[]>(
143+
`/api/v2/templates/${templateId}/versions`,
144+
)
145+
return response.data
146+
}
147+
139148
export const getWorkspace = async (
140149
workspaceId: string,
141150
params?: TypesGen.WorkspaceOptions,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { ComponentMeta, Story } from "@storybook/react"
2+
import { MockTemplateVersion } from "../../testHelpers/entities"
3+
import { VersionsTable, VersionsTableProps } from "./VersionsTable"
4+
5+
export default {
6+
title: "components/VersionsTable",
7+
component: VersionsTable,
8+
} as ComponentMeta<typeof VersionsTable>
9+
10+
const Template: Story<VersionsTableProps> = (args) => <VersionsTable {...args} />
11+
12+
export const Example = Template.bind({})
13+
Example.args = {
14+
versions: [
15+
MockTemplateVersion,
16+
{
17+
...MockTemplateVersion,
18+
name: "test-template-version-2",
19+
created_at: "2022-05-18T18:39:01.382927298Z",
20+
},
21+
],
22+
}
23+
24+
export const Empty = Template.bind({})
25+
Empty.args = {
26+
versions: [],
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import Box from "@material-ui/core/Box"
2+
import { Theme } from "@material-ui/core/styles"
3+
import Table from "@material-ui/core/Table"
4+
import TableBody from "@material-ui/core/TableBody"
5+
import TableCell from "@material-ui/core/TableCell"
6+
import TableHead from "@material-ui/core/TableHead"
7+
import TableRow from "@material-ui/core/TableRow"
8+
import useTheme from "@material-ui/styles/useTheme"
9+
import { FC } from "react"
10+
import * as TypesGen from "../../api/typesGenerated"
11+
import { EmptyState } from "../EmptyState/EmptyState"
12+
import { TableLoader } from "../TableLoader/TableLoader"
13+
14+
export const Language = {
15+
emptyMessage: "No versions found",
16+
nameLabel: "Version name",
17+
createdAtLabel: "Created at",
18+
createdByLabel: "Created by",
19+
}
20+
21+
export interface VersionsTableProps {
22+
versions?: TypesGen.TemplateVersion[]
23+
}
24+
25+
export const VersionsTable: FC<VersionsTableProps> = ({ versions }) => {
26+
const isLoading = !versions
27+
const theme: Theme = useTheme()
28+
29+
return (
30+
<Table data-testid="versions-table">
31+
<TableHead>
32+
<TableRow>
33+
<TableCell width="30%">{Language.nameLabel}</TableCell>
34+
<TableCell width="30%">{Language.createdAtLabel}</TableCell>
35+
<TableCell width="40%">{Language.createdByLabel}</TableCell>
36+
</TableRow>
37+
</TableHead>
38+
<TableBody>
39+
{isLoading && <TableLoader />}
40+
{versions &&
41+
versions
42+
.slice()
43+
.reverse()
44+
.map((version) => {
45+
return (
46+
<TableRow key={version.id} data-testid={`version-${version.id}`}>
47+
<TableCell>{version.name}</TableCell>
48+
<TableCell>
49+
<span style={{ color: theme.palette.text.secondary }}>
50+
{new Date(version.created_at).toLocaleString()}
51+
</span>
52+
</TableCell>
53+
<TableCell>
54+
<span style={{ color: theme.palette.text.secondary }}>
55+
{version.created_by_name}
56+
</span>
57+
</TableCell>
58+
</TableRow>
59+
)
60+
})}
61+
62+
{versions && versions.length === 0 && (
63+
<TableRow>
64+
<TableCell colSpan={999}>
65+
<Box p={4}>
66+
<EmptyState message={Language.emptyMessage} />
67+
</Box>
68+
</TableCell>
69+
</TableRow>
70+
)}
71+
</TableBody>
72+
</Table>
73+
)
74+
}

site/src/pages/TemplatePage/TemplatePage.test.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { screen } from "@testing-library/react"
22
import {
33
MockTemplate,
4+
MockTemplateVersion,
45
MockWorkspaceResource,
56
renderWithAuth,
67
} from "../../testHelpers/renderHelpers"
@@ -15,5 +16,6 @@ describe("TemplatePage", () => {
1516
await screen.findByText(MockTemplate.name)
1617
screen.getByTestId("markdown")
1718
screen.getByText(MockWorkspaceResource.name)
19+
screen.getByTestId(`version-${MockTemplateVersion.id}`)
1820
})
1921
})

site/src/pages/TemplatePage/TemplatePage.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export const TemplatePage: FC = () => {
2727
organizationId,
2828
},
2929
})
30-
const { template, activeTemplateVersion, templateResources } = templateState.context
30+
const { template, activeTemplateVersion, templateResources, templateVersions } =
31+
templateState.context
3132
const isLoading = !template || !activeTemplateVersion || !templateResources
3233

3334
if (isLoading) {
@@ -43,6 +44,7 @@ export const TemplatePage: FC = () => {
4344
template={template}
4445
activeTemplateVersion={activeTemplateVersion}
4546
templateResources={templateResources}
47+
templateVersions={templateVersions}
4648
/>
4749
</>
4850
)

site/src/pages/TemplatePage/TemplatePageView.stories.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Example.args = {
1414
template: Mocks.MockTemplate,
1515
activeTemplateVersion: Mocks.MockTemplateVersion,
1616
templateResources: [Mocks.MockWorkspaceResource, Mocks.MockWorkspaceResource2],
17+
templateVersions: [Mocks.MockTemplateVersion],
1718
}
1819

1920
export const SmallViewport = Template.bind({})
@@ -33,6 +34,7 @@ You can add instructions here
3334
\`\`\``,
3435
},
3536
templateResources: [Mocks.MockWorkspaceResource, Mocks.MockWorkspaceResource2],
37+
templateVersions: [Mocks.MockTemplateVersion],
3638
}
3739
SmallViewport.parameters = {
3840
chromatic: { viewports: [600] },

site/src/pages/TemplatePage/TemplatePageView.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,29 @@ import {
1616
import { Stack } from "../../components/Stack/Stack"
1717
import { TemplateResourcesTable } from "../../components/TemplateResourcesTable/TemplateResourcesTable"
1818
import { TemplateStats } from "../../components/TemplateStats/TemplateStats"
19+
import { VersionsTable } from "../../components/VersionsTable/VersionsTable"
1920
import { WorkspaceSection } from "../../components/WorkspaceSection/WorkspaceSection"
2021

2122
const Language = {
2223
createButton: "Create workspace",
2324
noDescription: "",
2425
readmeTitle: "README",
2526
resourcesTitle: "Resources",
27+
versionsTitle: "Version history",
2628
}
2729

2830
export interface TemplatePageViewProps {
2931
template: Template
3032
activeTemplateVersion: TemplateVersion
3133
templateResources: WorkspaceResource[]
34+
templateVersions?: TemplateVersion[]
3235
}
3336

3437
export const TemplatePageView: FC<TemplatePageViewProps> = ({
3538
template,
3639
activeTemplateVersion,
3740
templateResources,
41+
templateVersions,
3842
}) => {
3943
const styles = useStyles()
4044
const readme = frontMatter(activeTemplateVersion.readme)
@@ -88,6 +92,12 @@ export const TemplatePageView: FC<TemplatePageViewProps> = ({
8892
</ReactMarkdown>
8993
</div>
9094
</WorkspaceSection>
95+
<WorkspaceSection
96+
title={Language.versionsTitle}
97+
contentsProps={{ className: styles.versionsTableContents }}
98+
>
99+
<VersionsTable versions={templateVersions} />
100+
</WorkspaceSection>
91101
</Stack>
92102
</Margins>
93103
)
@@ -111,5 +121,8 @@ export const useStyles = makeStyles((theme) => {
111121
resourcesTableContents: {
112122
margin: 0,
113123
},
124+
versionsTableContents: {
125+
margin: 0,
126+
},
114127
}
115128
})

site/src/testHelpers/entities.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,9 @@ export const MockRunningProvisionerJob: TypesGen.ProvisionerJob = {
101101

102102
export const MockTemplateVersion: TypesGen.TemplateVersion = {
103103
id: "test-template-version",
104-
created_at: "",
105-
updated_at: "",
104+
created_at: "2022-05-17T17:39:01.382927298Z",
105+
updated_at: "2022-05-17T17:39:01.382927298Z",
106+
template_id: "test-template",
106107
job: MockProvisionerJob,
107108
name: "test-version",
108109
readme: `---

site/src/testHelpers/handlers.ts

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export const handlers = [
2525
rest.get("/api/v2/templates/:templateId", async (req, res, ctx) => {
2626
return res(ctx.status(200), ctx.json(M.MockTemplate))
2727
}),
28+
rest.get("/api/v2/templates/:templateId/versions", async (req, res, ctx) => {
29+
return res(ctx.status(200), ctx.json([M.MockTemplateVersion]))
30+
}),
2831
rest.get("/api/v2/templateversions/:templateVersionId", async (req, res, ctx) => {
2932
return res(ctx.status(200), ctx.json(M.MockTemplateVersion))
3033
}),

site/src/xServices/template/templateXService.ts

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { assign, createMachine } from "xstate"
2-
import { getTemplateByName, getTemplateVersion, getTemplateVersionResources } from "../../api/api"
2+
import {
3+
getTemplateByName,
4+
getTemplateVersion,
5+
getTemplateVersionResources,
6+
getTemplateVersions,
7+
} from "../../api/api"
38
import { Template, TemplateVersion, WorkspaceResource } from "../../api/typesGenerated"
49

510
interface TemplateContext {
@@ -8,6 +13,7 @@ interface TemplateContext {
813
template?: Template
914
activeTemplateVersion?: TemplateVersion
1015
templateResources?: WorkspaceResource[]
16+
templateVersions?: TemplateVersion[]
1117
}
1218

1319
export const templateMachine = createMachine(
@@ -24,6 +30,9 @@ export const templateMachine = createMachine(
2430
getTemplateResources: {
2531
data: WorkspaceResource[]
2632
}
33+
getTemplateVersions: {
34+
data: TemplateVersion[]
35+
}
2736
},
2837
},
2938
tsTypes: {} as import("./templateXService.typegen").Typegen0,
@@ -72,6 +81,21 @@ export const templateMachine = createMachine(
7281
success: { type: "final" },
7382
},
7483
},
84+
templateVersions: {
85+
initial: "gettingTemplateVersions",
86+
states: {
87+
gettingTemplateVersions: {
88+
invoke: {
89+
src: "getTemplateVersions",
90+
onDone: {
91+
actions: ["assignTemplateVersions"],
92+
target: "success",
93+
},
94+
},
95+
},
96+
success: { type: "final" },
97+
},
98+
},
7599
},
76100
},
77101
loaded: {},
@@ -94,6 +118,13 @@ export const templateMachine = createMachine(
94118

95119
return getTemplateVersionResources(ctx.template.active_version_id)
96120
},
121+
getTemplateVersions: (ctx) => {
122+
if (!ctx.template) {
123+
throw new Error("Template not loaded")
124+
}
125+
126+
return getTemplateVersions(ctx.template.id)
127+
},
97128
},
98129
actions: {
99130
assignTemplate: assign({
@@ -105,6 +136,9 @@ export const templateMachine = createMachine(
105136
assignTemplateResources: assign({
106137
templateResources: (_, event) => event.data,
107138
}),
139+
assignTemplateVersions: assign({
140+
templateVersions: (_, event) => event.data,
141+
}),
108142
},
109143
},
110144
)

0 commit comments

Comments
 (0)