diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 8c7641ef7d..71065f4ad1 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -2294,6 +2294,12 @@ const docTemplate = `{ "name": "after_id", "in": "query" }, + { + "type": "boolean", + "description": "Include archived versions in the list", + "name": "include_archived", + "in": "query" + }, { "type": "integer", "description": "Page limit", diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index aaf77fdcf0..12b388130c 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -2002,6 +2002,12 @@ "name": "after_id", "in": "query" }, + { + "type": "boolean", + "description": "Include archived versions in the list", + "name": "include_archived", + "in": "query" + }, { "type": "integer", "description": "Page limit", diff --git a/coderd/templateversions.go b/coderd/templateversions.go index f99eff5645..314570f9db 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -704,6 +704,7 @@ func (api *API) fetchTemplateVersionDryRunJob(rw http.ResponseWriter, r *http.Re // @Tags Templates // @Param template path string true "Template ID" format(uuid) // @Param after_id query string false "After ID" format(uuid) +// @Param include_archived query bool false "Include archived versions in the list" // @Param limit query int false "Page limit" // @Param offset query int false "Page offset" // @Success 200 {array} codersdk.TemplateVersion diff --git a/docs/api/templates.md b/docs/api/templates.md index 1068d84fcf..6fa7270e37 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -827,12 +827,13 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions \ ### Parameters -| Name | In | Type | Required | Description | -| ---------- | ----- | ------------ | -------- | ----------- | -| `template` | path | string(uuid) | true | Template ID | -| `after_id` | query | string(uuid) | false | After ID | -| `limit` | query | integer | false | Page limit | -| `offset` | query | integer | false | Page offset | +| Name | In | Type | Required | Description | +| ------------------ | ----- | ------------ | -------- | ------------------------------------- | +| `template` | path | string(uuid) | true | Template ID | +| `after_id` | query | string(uuid) | false | After ID | +| `include_archived` | query | boolean | false | Include archived versions in the list | +| `limit` | query | integer | false | Page limit | +| `offset` | query | integer | false | Page offset | ### Example responses diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 8ff9169c16..e688a9c78d 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -387,6 +387,20 @@ export const patchTemplateVersion = async ( return response.data; }; +export const archiveTemplateVersion = async (templateVersionId: string) => { + const response = await axios.post( + `/api/v2/templateversions/${templateVersionId}/archive`, + ); + return response.data; +}; + +export const unarchiveTemplateVersion = async (templateVersionId: string) => { + const response = await axios.post( + `/api/v2/templateversions/${templateVersionId}/unarchive`, + ); + return response.data; +}; + export const updateTemplateMeta = async ( templateId: string, data: TypesGen.UpdateTemplateMeta, diff --git a/site/src/pages/TemplatePage/TemplateVersionsPage/TemplateVersionsPage.tsx b/site/src/pages/TemplatePage/TemplateVersionsPage/TemplateVersionsPage.tsx index c46b033605..86bed4cf70 100644 --- a/site/src/pages/TemplatePage/TemplateVersionsPage/TemplateVersionsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateVersionsPage/TemplateVersionsPage.tsx @@ -1,5 +1,9 @@ import { useMutation, useQuery } from "react-query"; -import { getTemplateVersions, updateActiveTemplateVersion } from "api/api"; +import { + archiveTemplateVersion, + getTemplateVersions, + updateActiveTemplateVersion, +} from "api/api"; import { getErrorMessage } from "api/errors"; import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; @@ -34,9 +38,31 @@ const TemplateVersionsPage = () => { displayError(getErrorMessage(error, "Failed to promote version")); }, }); + + const { mutate: archiveVersion, isLoading: isArchiving } = useMutation({ + mutationFn: (templateVersionId: string) => { + return archiveTemplateVersion(templateVersionId); + }, + onSuccess: async () => { + // The reload is unfortunate. When a version is archived, we should hide + // the row. I do not know an easy way to do that, so a reload makes the API call + // resend and now the version is omitted. + // TODO: Improve this to not reload the page. + location.reload(); + setSelectedVersionIdToArchive(undefined); + displaySuccess("Version archived successfully"); + }, + onError: (error) => { + displayError(getErrorMessage(error, "Failed to archive version")); + }, + }); + const [selectedVersionIdToPromote, setSelectedVersionIdToPromote] = useState< string | undefined >(); + const [selectedVersionIdToArchive, setSelectedVersionIdToArchive] = useState< + string | undefined + >(); return ( <> @@ -50,8 +76,14 @@ const TemplateVersionsPage = () => { ? setSelectedVersionIdToPromote : undefined } + onArchiveClick={ + permissions.canUpdateTemplate + ? setSelectedVersionIdToArchive + : undefined + } activeVersionId={latestActiveVersion} /> + {/* Promote confirm */} { confirmText="Promote" description="Are you sure you want to promote this version? Workspaces will be prompted to “Update” to this version once promoted." /> + {/* Archive Confirm */} + { + archiveVersion(selectedVersionIdToArchive as string); + }} + onClose={() => setSelectedVersionIdToArchive(undefined)} + title="Archive version" + confirmLoading={isArchiving} + confirmText="Archive" + description="Are you sure you want to archive this version (this is reversible)? Archived versions cannot be used by workspaces." + /> ); }; diff --git a/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx b/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx index fb78a55a30..0ad02ffd2b 100644 --- a/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx +++ b/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx @@ -17,6 +17,7 @@ export interface VersionRowProps { isActive: boolean; isLatest: boolean; onPromoteClick?: (templateVersionId: string) => void; + onArchiveClick?: (templateVersionId: string) => void; } export const VersionRow: React.FC = ({ @@ -24,6 +25,7 @@ export const VersionRow: React.FC = ({ isActive, isLatest, onPromoteClick, + onArchiveClick, }) => { const styles = useStyles(); const navigate = useNavigate(); @@ -90,14 +92,30 @@ export const VersionRow: React.FC = ({ )} {jobStatus === "failed" && } - {onPromoteClick && ( + {jobStatus === "failed" ? ( + + ) : (