mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
feat: show template versions (#3003)
This commit is contained in:
@ -136,6 +136,15 @@ export const getTemplateVersionResources = async (
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const getTemplateVersions = async (
|
||||
templateId: string,
|
||||
): Promise<TypesGen.TemplateVersion[]> => {
|
||||
const response = await axios.get<TypesGen.TemplateVersion[]>(
|
||||
`/api/v2/templates/${templateId}/versions`,
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const getWorkspace = async (
|
||||
workspaceId: string,
|
||||
params?: TypesGen.WorkspaceOptions,
|
||||
|
27
site/src/components/VersionsTable/VersionsTable.stories.tsx
Normal file
27
site/src/components/VersionsTable/VersionsTable.stories.tsx
Normal file
@ -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<typeof VersionsTable>
|
||||
|
||||
const Template: Story<VersionsTableProps> = (args) => <VersionsTable {...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: [],
|
||||
}
|
74
site/src/components/VersionsTable/VersionsTable.tsx
Normal file
74
site/src/components/VersionsTable/VersionsTable.tsx
Normal file
@ -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<VersionsTableProps> = ({ versions }) => {
|
||||
const isLoading = !versions
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
return (
|
||||
<Table data-testid="versions-table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="30%">{Language.nameLabel}</TableCell>
|
||||
<TableCell width="30%">{Language.createdAtLabel}</TableCell>
|
||||
<TableCell width="40%">{Language.createdByLabel}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading && <TableLoader />}
|
||||
{versions &&
|
||||
versions
|
||||
.slice()
|
||||
.reverse()
|
||||
.map((version) => {
|
||||
return (
|
||||
<TableRow key={version.id} data-testid={`version-${version.id}`}>
|
||||
<TableCell>{version.name}</TableCell>
|
||||
<TableCell>
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{new Date(version.created_at).toLocaleString()}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{version.created_by_name}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
|
||||
{versions && versions.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<Box p={4}>
|
||||
<EmptyState message={Language.emptyMessage} />
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)
|
||||
}
|
@ -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}`)
|
||||
})
|
||||
})
|
||||
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -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] },
|
||||
|
@ -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<TemplatePageViewProps> = ({
|
||||
template,
|
||||
activeTemplateVersion,
|
||||
templateResources,
|
||||
templateVersions,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
const readme = frontMatter(activeTemplateVersion.readme)
|
||||
@ -88,6 +92,12 @@ export const TemplatePageView: FC<TemplatePageViewProps> = ({
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</WorkspaceSection>
|
||||
<WorkspaceSection
|
||||
title={Language.versionsTitle}
|
||||
contentsProps={{ className: styles.versionsTableContents }}
|
||||
>
|
||||
<VersionsTable versions={templateVersions} />
|
||||
</WorkspaceSection>
|
||||
</Stack>
|
||||
</Margins>
|
||||
)
|
||||
@ -111,5 +121,8 @@ export const useStyles = makeStyles((theme) => {
|
||||
resourcesTableContents: {
|
||||
margin: 0,
|
||||
},
|
||||
versionsTableContents: {
|
||||
margin: 0,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -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: `---
|
||||
|
@ -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))
|
||||
}),
|
||||
|
@ -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,
|
||||
}),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
Reference in New Issue
Block a user