Skip to content

Commit ac623b4

Browse files
authored
feat: implement basic archive ui to make archiving failed versions easy (#10182)
* feat: implement basic archive ui to make archiving failed versions easy.
1 parent 1e950fa commit ac623b4

File tree

8 files changed

+105
-10
lines changed

8 files changed

+105
-10
lines changed

coderd/apidoc/docs.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/templateversions.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,7 @@ func (api *API) fetchTemplateVersionDryRunJob(rw http.ResponseWriter, r *http.Re
704704
// @Tags Templates
705705
// @Param template path string true "Template ID" format(uuid)
706706
// @Param after_id query string false "After ID" format(uuid)
707+
// @Param include_archived query bool false "Include archived versions in the list"
707708
// @Param limit query int false "Page limit"
708709
// @Param offset query int false "Page offset"
709710
// @Success 200 {array} codersdk.TemplateVersion

docs/api/templates.md

Lines changed: 7 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/api/api.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,20 @@ export const patchTemplateVersion = async (
387387
return response.data;
388388
};
389389

390+
export const archiveTemplateVersion = async (templateVersionId: string) => {
391+
const response = await axios.post<TypesGen.TemplateVersion>(
392+
`/api/v2/templateversions/${templateVersionId}/archive`,
393+
);
394+
return response.data;
395+
};
396+
397+
export const unarchiveTemplateVersion = async (templateVersionId: string) => {
398+
const response = await axios.post<TypesGen.TemplateVersion>(
399+
`/api/v2/templateversions/${templateVersionId}/unarchive`,
400+
);
401+
return response.data;
402+
};
403+
390404
export const updateTemplateMeta = async (
391405
templateId: string,
392406
data: TypesGen.UpdateTemplateMeta,

site/src/pages/TemplatePage/TemplateVersionsPage/TemplateVersionsPage.tsx

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { useMutation, useQuery } from "react-query";
2-
import { getTemplateVersions, updateActiveTemplateVersion } from "api/api";
2+
import {
3+
archiveTemplateVersion,
4+
getTemplateVersions,
5+
updateActiveTemplateVersion,
6+
} from "api/api";
37
import { getErrorMessage } from "api/errors";
48
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
59
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
@@ -34,9 +38,31 @@ const TemplateVersionsPage = () => {
3438
displayError(getErrorMessage(error, "Failed to promote version"));
3539
},
3640
});
41+
42+
const { mutate: archiveVersion, isLoading: isArchiving } = useMutation({
43+
mutationFn: (templateVersionId: string) => {
44+
return archiveTemplateVersion(templateVersionId);
45+
},
46+
onSuccess: async () => {
47+
// The reload is unfortunate. When a version is archived, we should hide
48+
// the row. I do not know an easy way to do that, so a reload makes the API call
49+
// resend and now the version is omitted.
50+
// TODO: Improve this to not reload the page.
51+
location.reload();
52+
setSelectedVersionIdToArchive(undefined);
53+
displaySuccess("Version archived successfully");
54+
},
55+
onError: (error) => {
56+
displayError(getErrorMessage(error, "Failed to archive version"));
57+
},
58+
});
59+
3760
const [selectedVersionIdToPromote, setSelectedVersionIdToPromote] = useState<
3861
string | undefined
3962
>();
63+
const [selectedVersionIdToArchive, setSelectedVersionIdToArchive] = useState<
64+
string | undefined
65+
>();
4066

4167
return (
4268
<>
@@ -50,8 +76,14 @@ const TemplateVersionsPage = () => {
5076
? setSelectedVersionIdToPromote
5177
: undefined
5278
}
79+
onArchiveClick={
80+
permissions.canUpdateTemplate
81+
? setSelectedVersionIdToArchive
82+
: undefined
83+
}
5384
activeVersionId={latestActiveVersion}
5485
/>
86+
{/* Promote confirm */}
5587
<ConfirmDialog
5688
type="info"
5789
hideCancel={false}
@@ -65,6 +97,20 @@ const TemplateVersionsPage = () => {
6597
confirmText="Promote"
6698
description="Are you sure you want to promote this version? Workspaces will be prompted to “Update” to this version once promoted."
6799
/>
100+
{/* Archive Confirm */}
101+
<ConfirmDialog
102+
type="info"
103+
hideCancel={false}
104+
open={selectedVersionIdToArchive !== undefined}
105+
onConfirm={() => {
106+
archiveVersion(selectedVersionIdToArchive as string);
107+
}}
108+
onClose={() => setSelectedVersionIdToArchive(undefined)}
109+
title="Archive version"
110+
confirmLoading={isArchiving}
111+
confirmText="Archive"
112+
description="Are you sure you want to archive this version (this is reversible)? Archived versions cannot be used by workspaces."
113+
/>
68114
</>
69115
);
70116
};

site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ export interface VersionRowProps {
1717
isActive: boolean;
1818
isLatest: boolean;
1919
onPromoteClick?: (templateVersionId: string) => void;
20+
onArchiveClick?: (templateVersionId: string) => void;
2021
}
2122

2223
export const VersionRow: React.FC<VersionRowProps> = ({
2324
version,
2425
isActive,
2526
isLatest,
2627
onPromoteClick,
28+
onArchiveClick,
2729
}) => {
2830
const styles = useStyles();
2931
const navigate = useNavigate();
@@ -90,14 +92,30 @@ export const VersionRow: React.FC<VersionRowProps> = ({
9092
<Pill text="Canceled" type="neutral" lightBorder />
9193
)}
9294
{jobStatus === "failed" && <Pill text="Failed" type="error" />}
93-
{onPromoteClick && (
95+
{jobStatus === "failed" ? (
96+
<Button
97+
className={styles.promoteButton}
98+
disabled={isActive || version.archived}
99+
onClick={(e) => {
100+
e.preventDefault();
101+
e.stopPropagation();
102+
if (onArchiveClick) {
103+
onArchiveClick(version.id);
104+
}
105+
}}
106+
>
107+
Archive&hellip;
108+
</Button>
109+
) : (
94110
<Button
95111
className={styles.promoteButton}
96112
disabled={isActive || jobStatus !== "succeeded"}
97113
onClick={(e) => {
98114
e.preventDefault();
99115
e.stopPropagation();
100-
onPromoteClick(version.id);
116+
if (onPromoteClick) {
117+
onPromoteClick(version.id);
118+
}
101119
}}
102120
>
103121
Promote&hellip;

site/src/pages/TemplatePage/TemplateVersionsPage/VersionsTable.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ export const Language = {
2121
export interface VersionsTableProps {
2222
activeVersionId: string;
2323
onPromoteClick?: (templateVersionId: string) => void;
24+
onArchiveClick?: (templateVersionId: string) => void;
25+
2426
versions?: TypesGen.TemplateVersion[];
2527
}
2628

2729
export const VersionsTable: FC<VersionsTableProps> = (props) => {
28-
const { versions, onPromoteClick, activeVersionId } = props;
30+
const { versions, onArchiveClick, onPromoteClick, activeVersionId } = props;
2931

3032
const latestVersionId = versions?.reduce(
3133
(latestSoFar, against) => {
@@ -55,6 +57,7 @@ export const VersionsTable: FC<VersionsTableProps> = (props) => {
5557
getDate={(version) => new Date(version.created_at)}
5658
row={(version) => (
5759
<VersionRow
60+
onArchiveClick={onArchiveClick}
5861
onPromoteClick={onPromoteClick}
5962
version={version}
6063
key={version.id}

0 commit comments

Comments
 (0)