Skip to content

feat(site): Promote template version #6929

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion site/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ const CreateTokenPage = lazy(
const TemplateFilesPage = lazy(
() => import("./pages/TemplatePage/TemplateFilesPage/TemplateFilesPage"),
)
const TemplateVersionsPage = lazy(
() =>
import("./pages/TemplatePage/TemplateVersionsPage/TemplateVersionsPage"),
)
const TemplateSchedulePage = lazy(
() =>
import(
Expand Down Expand Up @@ -170,8 +174,8 @@ export const AppRouter: FC = () => {
<Route path=":template">
<Route element={<TemplateLayout />}>
<Route index element={<TemplateSummaryPage />} />

<Route path="files" element={<TemplateFilesPage />} />
<Route path="versions" element={<TemplateVersionsPage />} />
</Route>

<Route path="workspace" element={<CreateWorkspacePage />} />
Expand Down
11 changes: 11 additions & 0 deletions site/src/components/TemplateLayout/TemplateLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,17 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
Source Code
</NavLink>
)}
<NavLink
to={`/templates/${templateName}/versions`}
className={({ isActive }) =>
combineClasses([
styles.tabItem,
isActive ? styles.tabItemActive : undefined,
])
}
>
Versions
</NavLink>
</Stack>
</Margins>
</div>
Expand Down
66 changes: 61 additions & 5 deletions site/src/components/VersionsTable/VersionRow.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,51 @@
import Button from "@material-ui/core/Button"
import { makeStyles } from "@material-ui/core/styles"
import TableCell from "@material-ui/core/TableCell"
import { TemplateVersion } from "api/typesGenerated"
import { Pill } from "components/Pill/Pill"
import { Stack } from "components/Stack/Stack"
import { TimelineEntry } from "components/Timeline/TimelineEntry"
import { UserAvatar } from "components/UserAvatar/UserAvatar"
import { useClickable } from "hooks/useClickable"
import { useClickableTableRow } from "hooks/useClickableTableRow"
import { useTranslation } from "react-i18next"
import { useNavigate } from "react-router-dom"
import { colors } from "theme/colors"
import { combineClasses } from "util/combineClasses"

export interface VersionRowProps {
version: TemplateVersion
isActive: boolean
onPromoteClick?: (templateVersionId: string) => void
}

export const VersionRow: React.FC<VersionRowProps> = ({ version }) => {
export const VersionRow: React.FC<VersionRowProps> = ({
version,
isActive,
onPromoteClick,
}) => {
const styles = useStyles()
const { t } = useTranslation("templatePage")
const navigate = useNavigate()
const clickableProps = useClickable(() => {
navigate(`versions/${version.name}`)
const clickableProps = useClickableTableRow(() => {
navigate(version.name)
})

return (
<TimelineEntry data-testid={`version-${version.id}`} {...clickableProps}>
<TimelineEntry
data-testid={`version-${version.id}`}
{...clickableProps}
className={combineClasses({
[clickableProps.className]: true,
[styles.row]: true,
[styles.active]: isActive,
})}
>
<TableCell className={styles.versionCell}>
<Stack
direction="row"
alignItems="center"
className={styles.versionWrapper}
justifyContent="space-between"
>
<Stack direction="row" alignItems="center">
<UserAvatar
Expand All @@ -49,17 +68,54 @@ export const VersionRow: React.FC<VersionRowProps> = ({ version }) => {
</span>
</Stack>
</Stack>
{isActive ? (
<Pill text="Active version" type="success" />
) : (
onPromoteClick && (
<Button
size="small"
variant="outlined"
className={styles.promoteButton}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
onPromoteClick(version.id)
}}
>
Promote version
</Button>
)
)}
</Stack>
</TableCell>
</TimelineEntry>
)
}

const useStyles = makeStyles((theme) => ({
row: {
"&:hover $promoteButton": {
color: theme.palette.text.primary,
borderColor: colors.gray[11],
"&:hover": {
borderColor: theme.palette.text.primary,
},
},
},

promoteButton: {
color: theme.palette.text.secondary,
transition: "none",
},

versionWrapper: {
padding: theme.spacing(2, 4),
},

active: {
backgroundColor: theme.palette.background.paperLight,
},

versionCell: {
padding: "0 !important",
position: "relative",
Expand Down
18 changes: 18 additions & 0 deletions site/src/components/VersionsTable/VersionsTable.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { action } from "@storybook/addon-actions"
import { ComponentMeta, Story } from "@storybook/react"
import { MockTemplateVersion } from "../../testHelpers/entities"
import { VersionsTable, VersionsTableProps } from "./VersionsTable"
Expand All @@ -13,13 +14,30 @@ const Template: Story<VersionsTableProps> = (args) => (

export const Example = Template.bind({})
Example.args = {
activeVersionId: MockTemplateVersion.id,
versions: [
{
...MockTemplateVersion,
id: "2",
name: "test-template-version-2",
created_at: "2022-05-18T18:39:01.382927298Z",
},
MockTemplateVersion,
],
}

export const CanPromote = Template.bind({})
CanPromote.args = {
activeVersionId: MockTemplateVersion.id,
onPromoteClick: action("onPromoteClick"),
versions: [
{
...MockTemplateVersion,
id: "2",
name: "test-template-version-2",
created_at: "2022-05-18T18:39:01.382927298Z",
},
MockTemplateVersion,
],
}

Expand Down
11 changes: 10 additions & 1 deletion site/src/components/VersionsTable/VersionsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ export const Language = {
}

export interface VersionsTableProps {
activeVersionId: string
onPromoteClick?: (templateVersionId: string) => void
versions?: TypesGen.TemplateVersion[]
}

export const VersionsTable: FC<React.PropsWithChildren<VersionsTableProps>> = ({
versions,
onPromoteClick,
activeVersionId,
}) => {
return (
<TableContainer>
Expand All @@ -34,7 +38,12 @@ export const VersionsTable: FC<React.PropsWithChildren<VersionsTableProps>> = ({
items={versions.slice().reverse()}
getDate={(version) => new Date(version.created_at)}
row={(version) => (
<VersionRow version={version} key={version.id} />
<VersionRow
onPromoteClick={onPromoteClick}
version={version}
key={version.id}
isActive={activeVersionId === version.id}
/>
)}
/>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { useOrganizationId } from "hooks/useOrganizationId"
import { useTab } from "hooks/useTab"
import { FC, useEffect } from "react"
import { Helmet } from "react-helmet-async"
import { pageTitle } from "util/page"
import {
getTemplateVersionFiles,
TemplateVersionFiles,
} from "util/templateVersion"
import { getTemplatePageTitle } from "../utils"

const fetchTemplateFiles = async (
organizationId: string,
Expand Down Expand Up @@ -80,7 +80,7 @@ const TemplateFilesPage: FC = () => {
return (
<>
<Helmet>
<title>{pageTitle(`${template?.name} · Source Code`)}</title>
<title>{getTemplatePageTitle("Source Code", template)}</title>
</Helmet>

{templateFiles && tab.isLoaded ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useTemplateLayoutContext } from "components/TemplateLayout/TemplateLayout"
import { FC } from "react"
import { Helmet } from "react-helmet-async"
import { pageTitle } from "util/page"
import { getTemplatePageTitle } from "../utils"
import { useTemplateSummaryData } from "./data"
import { TemplateSummaryPageView } from "./TemplateSummaryPageView"

Expand All @@ -15,15 +15,7 @@ export const TemplateSummaryPage: FC = () => {
return (
<>
<Helmet>
<title>
{pageTitle(
`${
template.display_name.length > 0
? template.display_name
: template.name
} · Template`,
)}
</title>
<title>{getTemplatePageTitle("Template", template)}</title>
</Helmet>
<TemplateSummaryPageView
data={data}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { MemoizedMarkdown } from "components/Markdown/Markdown"
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 frontMatter from "front-matter"
import { FC } from "react"
import { DAUChart } from "../../../components/DAUChart/DAUChart"
Expand All @@ -32,7 +31,7 @@ export const TemplateSummaryPageView: FC<TemplateSummaryPageViewProps> = ({
return <Loader />
}

const { daus, resources, versions } = data
const { daus, resources } = data
const readme = frontMatter(activeVersion.readme)

const getStartedResources = (resources: WorkspaceResource[]) => {
Expand All @@ -53,8 +52,6 @@ export const TemplateSummaryPageView: FC<TemplateSummaryPageViewProps> = ({
<MemoizedMarkdown>{readme.body}</MemoizedMarkdown>
</div>
</div>

<VersionsTable versions={versions} />
</Stack>
)
}
Expand Down
10 changes: 2 additions & 8 deletions site/src/pages/TemplatePage/TemplateSummaryPage/data.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import { useQuery } from "@tanstack/react-query"
import {
getTemplateVersionResources,
getTemplateVersions,
getTemplateDAUs,
} from "api/api"
import { getTemplateVersionResources, getTemplateDAUs } from "api/api"

const fetchTemplateSummary = async (
templateId: string,
activeVersionId: string,
) => {
const [resources, versions, daus] = await Promise.all([
const [resources, daus] = await Promise.all([
getTemplateVersionResources(activeVersionId),
getTemplateVersions(templateId),
getTemplateDAUs(templateId),
])

return {
resources,
versions,
daus,
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useMutation, useQuery } from "@tanstack/react-query"
import { getTemplateVersions, updateActiveTemplateVersion } from "api/api"
import { getErrorMessage } from "api/errors"
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"
import { useTemplateLayoutContext } from "components/TemplateLayout/TemplateLayout"
import { VersionsTable } from "components/VersionsTable/VersionsTable"
import { useState } from "react"
import { Helmet } from "react-helmet-async"
import { getTemplatePageTitle } from "../utils"
import { useDashboard } from "components/Dashboard/DashboardProvider"

const TemplateVersionsPage = () => {
const dashboard = useDashboard()
const { template, permissions } = useTemplateLayoutContext()
const { data } = useQuery({
queryKey: ["template", "versions", template.id],
queryFn: () => getTemplateVersions(template.id),
})
// We use this to update the active version in the UI without having to refetch the template
const [latestActiveVersion, setLatestActiveVersion] = useState(
template.active_version_id,
)
const { mutate: promoteVersion, isLoading: isPromoting } = useMutation({
mutationFn: (templateVersionId: string) => {
return updateActiveTemplateVersion(template.id, {
id: templateVersionId,
})
},
onSuccess: async () => {
setLatestActiveVersion(selectedVersionIdToPromote as string)
setSelectedVersionIdToPromote(undefined)
displaySuccess("Version promoted successfully")
},
onError: (error) => {
displayError(getErrorMessage(error, "Failed to promote version"))
},
})
const [selectedVersionIdToPromote, setSelectedVersionIdToPromote] = useState<
string | undefined
>()
const canPromoteVersion =
dashboard.experiments.includes("template_editor") &&
permissions.canUpdateTemplate

return (
<>
<Helmet>
<title>{getTemplatePageTitle("Versions", template)}</title>
</Helmet>
<VersionsTable
versions={data}
onPromoteClick={
canPromoteVersion ? setSelectedVersionIdToPromote : undefined
}
activeVersionId={latestActiveVersion}
/>
<ConfirmDialog
type="info"
hideCancel={false}
open={selectedVersionIdToPromote !== undefined}
onConfirm={() => {
promoteVersion(selectedVersionIdToPromote as string)
}}
onClose={() => setSelectedVersionIdToPromote(undefined)}
title="Promote version"
confirmLoading={isPromoting}
confirmText="Promote"
description="Are you sure you want to promote this version? Workspaces will be prompted to “Update” to this version once promoted."
/>
</>
)
}

export default TemplateVersionsPage
Loading