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
|
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 (
|
export const getWorkspace = async (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
params?: TypesGen.WorkspaceOptions,
|
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 { screen } from "@testing-library/react"
|
||||||
import {
|
import {
|
||||||
MockTemplate,
|
MockTemplate,
|
||||||
|
MockTemplateVersion,
|
||||||
MockWorkspaceResource,
|
MockWorkspaceResource,
|
||||||
renderWithAuth,
|
renderWithAuth,
|
||||||
} from "../../testHelpers/renderHelpers"
|
} from "../../testHelpers/renderHelpers"
|
||||||
@ -15,5 +16,6 @@ describe("TemplatePage", () => {
|
|||||||
await screen.findByText(MockTemplate.name)
|
await screen.findByText(MockTemplate.name)
|
||||||
screen.getByTestId("markdown")
|
screen.getByTestId("markdown")
|
||||||
screen.getByText(MockWorkspaceResource.name)
|
screen.getByText(MockWorkspaceResource.name)
|
||||||
|
screen.getByTestId(`version-${MockTemplateVersion.id}`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -27,7 +27,8 @@ export const TemplatePage: FC = () => {
|
|||||||
organizationId,
|
organizationId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const { template, activeTemplateVersion, templateResources } = templateState.context
|
const { template, activeTemplateVersion, templateResources, templateVersions } =
|
||||||
|
templateState.context
|
||||||
const isLoading = !template || !activeTemplateVersion || !templateResources
|
const isLoading = !template || !activeTemplateVersion || !templateResources
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@ -43,6 +44,7 @@ export const TemplatePage: FC = () => {
|
|||||||
template={template}
|
template={template}
|
||||||
activeTemplateVersion={activeTemplateVersion}
|
activeTemplateVersion={activeTemplateVersion}
|
||||||
templateResources={templateResources}
|
templateResources={templateResources}
|
||||||
|
templateVersions={templateVersions}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -14,6 +14,7 @@ Example.args = {
|
|||||||
template: Mocks.MockTemplate,
|
template: Mocks.MockTemplate,
|
||||||
activeTemplateVersion: Mocks.MockTemplateVersion,
|
activeTemplateVersion: Mocks.MockTemplateVersion,
|
||||||
templateResources: [Mocks.MockWorkspaceResource, Mocks.MockWorkspaceResource2],
|
templateResources: [Mocks.MockWorkspaceResource, Mocks.MockWorkspaceResource2],
|
||||||
|
templateVersions: [Mocks.MockTemplateVersion],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SmallViewport = Template.bind({})
|
export const SmallViewport = Template.bind({})
|
||||||
@ -33,6 +34,7 @@ You can add instructions here
|
|||||||
\`\`\``,
|
\`\`\``,
|
||||||
},
|
},
|
||||||
templateResources: [Mocks.MockWorkspaceResource, Mocks.MockWorkspaceResource2],
|
templateResources: [Mocks.MockWorkspaceResource, Mocks.MockWorkspaceResource2],
|
||||||
|
templateVersions: [Mocks.MockTemplateVersion],
|
||||||
}
|
}
|
||||||
SmallViewport.parameters = {
|
SmallViewport.parameters = {
|
||||||
chromatic: { viewports: [600] },
|
chromatic: { viewports: [600] },
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
import { Stack } from "../../components/Stack/Stack"
|
import { Stack } from "../../components/Stack/Stack"
|
||||||
import { TemplateResourcesTable } from "../../components/TemplateResourcesTable/TemplateResourcesTable"
|
import { TemplateResourcesTable } from "../../components/TemplateResourcesTable/TemplateResourcesTable"
|
||||||
import { TemplateStats } from "../../components/TemplateStats/TemplateStats"
|
import { TemplateStats } from "../../components/TemplateStats/TemplateStats"
|
||||||
|
import { VersionsTable } from "../../components/VersionsTable/VersionsTable"
|
||||||
import { WorkspaceSection } from "../../components/WorkspaceSection/WorkspaceSection"
|
import { WorkspaceSection } from "../../components/WorkspaceSection/WorkspaceSection"
|
||||||
|
|
||||||
const Language = {
|
const Language = {
|
||||||
@ -23,18 +24,21 @@ const Language = {
|
|||||||
noDescription: "",
|
noDescription: "",
|
||||||
readmeTitle: "README",
|
readmeTitle: "README",
|
||||||
resourcesTitle: "Resources",
|
resourcesTitle: "Resources",
|
||||||
|
versionsTitle: "Version history",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TemplatePageViewProps {
|
export interface TemplatePageViewProps {
|
||||||
template: Template
|
template: Template
|
||||||
activeTemplateVersion: TemplateVersion
|
activeTemplateVersion: TemplateVersion
|
||||||
templateResources: WorkspaceResource[]
|
templateResources: WorkspaceResource[]
|
||||||
|
templateVersions?: TemplateVersion[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TemplatePageView: FC<TemplatePageViewProps> = ({
|
export const TemplatePageView: FC<TemplatePageViewProps> = ({
|
||||||
template,
|
template,
|
||||||
activeTemplateVersion,
|
activeTemplateVersion,
|
||||||
templateResources,
|
templateResources,
|
||||||
|
templateVersions,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles()
|
const styles = useStyles()
|
||||||
const readme = frontMatter(activeTemplateVersion.readme)
|
const readme = frontMatter(activeTemplateVersion.readme)
|
||||||
@ -88,6 +92,12 @@ export const TemplatePageView: FC<TemplatePageViewProps> = ({
|
|||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
</WorkspaceSection>
|
</WorkspaceSection>
|
||||||
|
<WorkspaceSection
|
||||||
|
title={Language.versionsTitle}
|
||||||
|
contentsProps={{ className: styles.versionsTableContents }}
|
||||||
|
>
|
||||||
|
<VersionsTable versions={templateVersions} />
|
||||||
|
</WorkspaceSection>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Margins>
|
</Margins>
|
||||||
)
|
)
|
||||||
@ -111,5 +121,8 @@ export const useStyles = makeStyles((theme) => {
|
|||||||
resourcesTableContents: {
|
resourcesTableContents: {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
},
|
},
|
||||||
|
versionsTableContents: {
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -101,8 +101,9 @@ export const MockRunningProvisionerJob: TypesGen.ProvisionerJob = {
|
|||||||
|
|
||||||
export const MockTemplateVersion: TypesGen.TemplateVersion = {
|
export const MockTemplateVersion: TypesGen.TemplateVersion = {
|
||||||
id: "test-template-version",
|
id: "test-template-version",
|
||||||
created_at: "",
|
created_at: "2022-05-17T17:39:01.382927298Z",
|
||||||
updated_at: "",
|
updated_at: "2022-05-17T17:39:01.382927298Z",
|
||||||
|
template_id: "test-template",
|
||||||
job: MockProvisionerJob,
|
job: MockProvisionerJob,
|
||||||
name: "test-version",
|
name: "test-version",
|
||||||
readme: `---
|
readme: `---
|
||||||
|
@ -25,6 +25,9 @@ export const handlers = [
|
|||||||
rest.get("/api/v2/templates/:templateId", async (req, res, ctx) => {
|
rest.get("/api/v2/templates/:templateId", async (req, res, ctx) => {
|
||||||
return res(ctx.status(200), ctx.json(M.MockTemplate))
|
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) => {
|
rest.get("/api/v2/templateversions/:templateVersionId", async (req, res, ctx) => {
|
||||||
return res(ctx.status(200), ctx.json(M.MockTemplateVersion))
|
return res(ctx.status(200), ctx.json(M.MockTemplateVersion))
|
||||||
}),
|
}),
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { assign, createMachine } from "xstate"
|
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"
|
import { Template, TemplateVersion, WorkspaceResource } from "../../api/typesGenerated"
|
||||||
|
|
||||||
interface TemplateContext {
|
interface TemplateContext {
|
||||||
@ -8,6 +13,7 @@ interface TemplateContext {
|
|||||||
template?: Template
|
template?: Template
|
||||||
activeTemplateVersion?: TemplateVersion
|
activeTemplateVersion?: TemplateVersion
|
||||||
templateResources?: WorkspaceResource[]
|
templateResources?: WorkspaceResource[]
|
||||||
|
templateVersions?: TemplateVersion[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const templateMachine = createMachine(
|
export const templateMachine = createMachine(
|
||||||
@ -24,6 +30,9 @@ export const templateMachine = createMachine(
|
|||||||
getTemplateResources: {
|
getTemplateResources: {
|
||||||
data: WorkspaceResource[]
|
data: WorkspaceResource[]
|
||||||
}
|
}
|
||||||
|
getTemplateVersions: {
|
||||||
|
data: TemplateVersion[]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tsTypes: {} as import("./templateXService.typegen").Typegen0,
|
tsTypes: {} as import("./templateXService.typegen").Typegen0,
|
||||||
@ -72,6 +81,21 @@ export const templateMachine = createMachine(
|
|||||||
success: { type: "final" },
|
success: { type: "final" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
templateVersions: {
|
||||||
|
initial: "gettingTemplateVersions",
|
||||||
|
states: {
|
||||||
|
gettingTemplateVersions: {
|
||||||
|
invoke: {
|
||||||
|
src: "getTemplateVersions",
|
||||||
|
onDone: {
|
||||||
|
actions: ["assignTemplateVersions"],
|
||||||
|
target: "success",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
success: { type: "final" },
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
loaded: {},
|
loaded: {},
|
||||||
@ -94,6 +118,13 @@ export const templateMachine = createMachine(
|
|||||||
|
|
||||||
return getTemplateVersionResources(ctx.template.active_version_id)
|
return getTemplateVersionResources(ctx.template.active_version_id)
|
||||||
},
|
},
|
||||||
|
getTemplateVersions: (ctx) => {
|
||||||
|
if (!ctx.template) {
|
||||||
|
throw new Error("Template not loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
return getTemplateVersions(ctx.template.id)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
assignTemplate: assign({
|
assignTemplate: assign({
|
||||||
@ -105,6 +136,9 @@ export const templateMachine = createMachine(
|
|||||||
assignTemplateResources: assign({
|
assignTemplateResources: assign({
|
||||||
templateResources: (_, event) => event.data,
|
templateResources: (_, event) => event.data,
|
||||||
}),
|
}),
|
||||||
|
assignTemplateVersions: assign({
|
||||||
|
templateVersions: (_, event) => event.data,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user