diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 8517dab9e8d26..73c0a17b96521 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -136,6 +136,15 @@ export const getTemplateVersionResources = async ( return response.data } +export const getTemplateVersions = async ( + templateId: string, +): Promise => { + const response = await axios.get( + `/api/v2/templates/${templateId}/versions`, + ) + return response.data +} + export const getWorkspace = async ( workspaceId: string, params?: TypesGen.WorkspaceOptions, diff --git a/site/src/components/VersionsTable/VersionsTable.stories.tsx b/site/src/components/VersionsTable/VersionsTable.stories.tsx new file mode 100644 index 0000000000000..6067f9c4208aa --- /dev/null +++ b/site/src/components/VersionsTable/VersionsTable.stories.tsx @@ -0,0 +1,27 @@ +import { ComponentMeta, Story } from "@storybook/react" +import { MockTemplateVersion } from "../../testHelpers/entities" +import { VersionsTable, VersionsTableProps } from "./VersionsTable" + +export default { + title: "components/VersionsTable", + component: VersionsTable, +} as ComponentMeta + +const Template: Story = (args) => + +export const Example = Template.bind({}) +Example.args = { + versions: [ + MockTemplateVersion, + { + ...MockTemplateVersion, + name: "test-template-version-2", + created_at: "2022-05-18T18:39:01.382927298Z", + }, + ], +} + +export const Empty = Template.bind({}) +Empty.args = { + versions: [], +} diff --git a/site/src/components/VersionsTable/VersionsTable.tsx b/site/src/components/VersionsTable/VersionsTable.tsx new file mode 100644 index 0000000000000..8e6801fcb200c --- /dev/null +++ b/site/src/components/VersionsTable/VersionsTable.tsx @@ -0,0 +1,74 @@ +import Box from "@material-ui/core/Box" +import { Theme } from "@material-ui/core/styles" +import Table from "@material-ui/core/Table" +import TableBody from "@material-ui/core/TableBody" +import TableCell from "@material-ui/core/TableCell" +import TableHead from "@material-ui/core/TableHead" +import TableRow from "@material-ui/core/TableRow" +import useTheme from "@material-ui/styles/useTheme" +import { FC } from "react" +import * as TypesGen from "../../api/typesGenerated" +import { EmptyState } from "../EmptyState/EmptyState" +import { TableLoader } from "../TableLoader/TableLoader" + +export const Language = { + emptyMessage: "No versions found", + nameLabel: "Version name", + createdAtLabel: "Created at", + createdByLabel: "Created by", +} + +export interface VersionsTableProps { + versions?: TypesGen.TemplateVersion[] +} + +export const VersionsTable: FC = ({ versions }) => { + const isLoading = !versions + const theme: Theme = useTheme() + + return ( + + + + {Language.nameLabel} + {Language.createdAtLabel} + {Language.createdByLabel} + + + + {isLoading && } + {versions && + versions + .slice() + .reverse() + .map((version) => { + return ( + + {version.name} + + + {new Date(version.created_at).toLocaleString()} + + + + + {version.created_by_name} + + + + ) + })} + + {versions && versions.length === 0 && ( + + + + + + + + )} + +
+ ) +} diff --git a/site/src/pages/TemplatePage/TemplatePage.test.tsx b/site/src/pages/TemplatePage/TemplatePage.test.tsx index 19fd0f418a8a3..d1d86d5b9b74a 100644 --- a/site/src/pages/TemplatePage/TemplatePage.test.tsx +++ b/site/src/pages/TemplatePage/TemplatePage.test.tsx @@ -1,6 +1,7 @@ import { screen } from "@testing-library/react" import { MockTemplate, + MockTemplateVersion, MockWorkspaceResource, renderWithAuth, } from "../../testHelpers/renderHelpers" @@ -15,5 +16,6 @@ describe("TemplatePage", () => { await screen.findByText(MockTemplate.name) screen.getByTestId("markdown") screen.getByText(MockWorkspaceResource.name) + screen.getByTestId(`version-${MockTemplateVersion.id}`) }) }) diff --git a/site/src/pages/TemplatePage/TemplatePage.tsx b/site/src/pages/TemplatePage/TemplatePage.tsx index 2d38665bdb834..056ee92a02dbc 100644 --- a/site/src/pages/TemplatePage/TemplatePage.tsx +++ b/site/src/pages/TemplatePage/TemplatePage.tsx @@ -27,7 +27,8 @@ export const TemplatePage: FC = () => { organizationId, }, }) - const { template, activeTemplateVersion, templateResources } = templateState.context + const { template, activeTemplateVersion, templateResources, templateVersions } = + templateState.context const isLoading = !template || !activeTemplateVersion || !templateResources if (isLoading) { @@ -43,6 +44,7 @@ export const TemplatePage: FC = () => { template={template} activeTemplateVersion={activeTemplateVersion} templateResources={templateResources} + templateVersions={templateVersions} /> ) diff --git a/site/src/pages/TemplatePage/TemplatePageView.stories.tsx b/site/src/pages/TemplatePage/TemplatePageView.stories.tsx index e553b1844aa3c..43277e81ead25 100644 --- a/site/src/pages/TemplatePage/TemplatePageView.stories.tsx +++ b/site/src/pages/TemplatePage/TemplatePageView.stories.tsx @@ -14,6 +14,7 @@ Example.args = { template: Mocks.MockTemplate, activeTemplateVersion: Mocks.MockTemplateVersion, templateResources: [Mocks.MockWorkspaceResource, Mocks.MockWorkspaceResource2], + templateVersions: [Mocks.MockTemplateVersion], } export const SmallViewport = Template.bind({}) @@ -33,6 +34,7 @@ You can add instructions here \`\`\``, }, templateResources: [Mocks.MockWorkspaceResource, Mocks.MockWorkspaceResource2], + templateVersions: [Mocks.MockTemplateVersion], } SmallViewport.parameters = { chromatic: { viewports: [600] }, diff --git a/site/src/pages/TemplatePage/TemplatePageView.tsx b/site/src/pages/TemplatePage/TemplatePageView.tsx index 19b097609febd..6eab044d0f688 100644 --- a/site/src/pages/TemplatePage/TemplatePageView.tsx +++ b/site/src/pages/TemplatePage/TemplatePageView.tsx @@ -16,6 +16,7 @@ import { import { Stack } from "../../components/Stack/Stack" import { TemplateResourcesTable } from "../../components/TemplateResourcesTable/TemplateResourcesTable" import { TemplateStats } from "../../components/TemplateStats/TemplateStats" +import { VersionsTable } from "../../components/VersionsTable/VersionsTable" import { WorkspaceSection } from "../../components/WorkspaceSection/WorkspaceSection" const Language = { @@ -23,18 +24,21 @@ const Language = { noDescription: "", readmeTitle: "README", resourcesTitle: "Resources", + versionsTitle: "Version history", } export interface TemplatePageViewProps { template: Template activeTemplateVersion: TemplateVersion templateResources: WorkspaceResource[] + templateVersions?: TemplateVersion[] } export const TemplatePageView: FC = ({ template, activeTemplateVersion, templateResources, + templateVersions, }) => { const styles = useStyles() const readme = frontMatter(activeTemplateVersion.readme) @@ -88,6 +92,12 @@ export const TemplatePageView: FC = ({ + + + ) @@ -111,5 +121,8 @@ export const useStyles = makeStyles((theme) => { resourcesTableContents: { margin: 0, }, + versionsTableContents: { + margin: 0, + }, } }) diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 769a5d547c0c4..acf05c0c09f6f 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -101,8 +101,9 @@ export const MockRunningProvisionerJob: TypesGen.ProvisionerJob = { export const MockTemplateVersion: TypesGen.TemplateVersion = { id: "test-template-version", - created_at: "", - updated_at: "", + created_at: "2022-05-17T17:39:01.382927298Z", + updated_at: "2022-05-17T17:39:01.382927298Z", + template_id: "test-template", job: MockProvisionerJob, name: "test-version", readme: `--- diff --git a/site/src/testHelpers/handlers.ts b/site/src/testHelpers/handlers.ts index 5f5db2d901718..7cf968fe8d00d 100644 --- a/site/src/testHelpers/handlers.ts +++ b/site/src/testHelpers/handlers.ts @@ -25,6 +25,9 @@ export const handlers = [ rest.get("/api/v2/templates/:templateId", async (req, res, ctx) => { return res(ctx.status(200), ctx.json(M.MockTemplate)) }), + rest.get("/api/v2/templates/:templateId/versions", async (req, res, ctx) => { + return res(ctx.status(200), ctx.json([M.MockTemplateVersion])) + }), rest.get("/api/v2/templateversions/:templateVersionId", async (req, res, ctx) => { return res(ctx.status(200), ctx.json(M.MockTemplateVersion)) }), diff --git a/site/src/xServices/template/templateXService.ts b/site/src/xServices/template/templateXService.ts index 3561c013b292c..1db743f0f8afa 100644 --- a/site/src/xServices/template/templateXService.ts +++ b/site/src/xServices/template/templateXService.ts @@ -1,5 +1,10 @@ import { assign, createMachine } from "xstate" -import { getTemplateByName, getTemplateVersion, getTemplateVersionResources } from "../../api/api" +import { + getTemplateByName, + getTemplateVersion, + getTemplateVersionResources, + getTemplateVersions, +} from "../../api/api" import { Template, TemplateVersion, WorkspaceResource } from "../../api/typesGenerated" interface TemplateContext { @@ -8,6 +13,7 @@ interface TemplateContext { template?: Template activeTemplateVersion?: TemplateVersion templateResources?: WorkspaceResource[] + templateVersions?: TemplateVersion[] } export const templateMachine = createMachine( @@ -24,6 +30,9 @@ export const templateMachine = createMachine( getTemplateResources: { data: WorkspaceResource[] } + getTemplateVersions: { + data: TemplateVersion[] + } }, }, tsTypes: {} as import("./templateXService.typegen").Typegen0, @@ -72,6 +81,21 @@ export const templateMachine = createMachine( success: { type: "final" }, }, }, + templateVersions: { + initial: "gettingTemplateVersions", + states: { + gettingTemplateVersions: { + invoke: { + src: "getTemplateVersions", + onDone: { + actions: ["assignTemplateVersions"], + target: "success", + }, + }, + }, + success: { type: "final" }, + }, + }, }, }, loaded: {}, @@ -94,6 +118,13 @@ export const templateMachine = createMachine( return getTemplateVersionResources(ctx.template.active_version_id) }, + getTemplateVersions: (ctx) => { + if (!ctx.template) { + throw new Error("Template not loaded") + } + + return getTemplateVersions(ctx.template.id) + }, }, actions: { assignTemplate: assign({ @@ -105,6 +136,9 @@ export const templateMachine = createMachine( assignTemplateResources: assign({ templateResources: (_, event) => event.data, }), + assignTemplateVersions: assign({ + templateVersions: (_, event) => event.data, + }), }, }, )