feat: add locked TTL field to template meta (#8020)

This commit is contained in:
Jon Ayers
2023-06-19 22:37:55 -04:00
committed by GitHub
parent 1ecc371ade
commit c3aef9363b
27 changed files with 291 additions and 90 deletions
+3
View File
@@ -179,6 +179,7 @@ export interface CreateTemplateRequest {
readonly allow_user_autostop?: boolean
readonly failure_ttl_ms?: number
readonly inactivity_ttl_ms?: number
readonly locked_ttl_ms?: number
readonly disable_everyone_group_access: boolean
}
@@ -805,6 +806,7 @@ export interface Template {
readonly allow_user_cancel_workspace_jobs: boolean
readonly failure_ttl_ms: number
readonly inactivity_ttl_ms: number
readonly locked_ttl_ms: number
}
// From codersdk/templates.go
@@ -972,6 +974,7 @@ export interface UpdateTemplateMeta {
readonly allow_user_cancel_workspace_jobs?: boolean
readonly failure_ttl_ms?: number
readonly inactivity_ttl_ms?: number
readonly locked_ttl_ms?: number
}
// From codersdk/users.go
@@ -25,6 +25,9 @@
"inactivityTTLHelperText_zero": "Coder will not automatically delete inactive workspaces",
"inactivityTTLHelperText_one": "Coder will automatically delete inactive workspaces after {{count}} day.",
"inactivityTTLHelperText_other": "Coder will automatically delete inactive workspaces after {{count}} days.",
"lockedTTLHelperText_zero": "Coder will not automatically delete locked workspaces",
"lockedTTLHelperText_one": "Coder will automatically delete locked workspaces after {{count}} day.",
"lockedTTLHelperText_other": "Coder will automatically delete locked workspaces after {{count}} days.",
"allowUserCancelWorkspaceJobsLabel": "Allow users to cancel in-progress workspace jobs.",
"allowUserCancelWorkspaceJobsNotice": "Depending on your template, canceling builds may leave workspaces in an unhealthy state. This option isn't recommended for most use cases.",
"allowUsersCancelHelperText": "If checked, users may be able to corrupt their workspace.",
@@ -28,6 +28,7 @@ const validFormValues: FormValues = {
allow_user_autostop: false,
failure_ttl_ms: 0,
inactivity_ttl_ms: 0,
locked_ttl_ms: 0,
}
const renderTemplateSettingsPage = async () => {
@@ -25,6 +25,7 @@ const MS_HOUR_CONVERSION = 3600000
const MS_DAY_CONVERSION = 86400000
const FAILURE_CLEANUP_DEFAULT = 7
const INACTIVITY_CLEANUP_DEFAULT = 180
const LOCKED_CLEANUP_DEFAULT = 30
export interface TemplateScheduleForm {
template: Template
@@ -65,6 +66,9 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
inactivity_ttl_ms: allowAdvancedScheduling
? template.inactivity_ttl_ms / MS_DAY_CONVERSION
: 0,
locked_ttl_ms: allowAdvancedScheduling
? template.locked_ttl_ms / MS_DAY_CONVERSION
: 0,
allow_user_autostart: template.allow_user_autostart,
allow_user_autostop: template.allow_user_autostop,
@@ -72,6 +76,8 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
allowAdvancedScheduling && Boolean(template.failure_ttl_ms),
inactivity_cleanup_enabled:
allowAdvancedScheduling && Boolean(template.inactivity_ttl_ms),
locked_cleanup_enabled:
allowAdvancedScheduling && Boolean(template.locked_ttl_ms),
},
validationSchema,
onSubmit: () => {
@@ -114,6 +120,9 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
inactivity_ttl_ms: form.values.inactivity_ttl_ms
? form.values.inactivity_ttl_ms * MS_DAY_CONVERSION
: undefined,
locked_ttl_ms: form.values.locked_ttl_ms
? form.values.locked_ttl_ms * MS_DAY_CONVERSION
: undefined,
allow_user_autostart: form.values.allow_user_autostart,
allow_user_autostop: form.values.allow_user_autostop,
@@ -158,6 +167,25 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
}
}
const handleToggleLockedCleanup = async (e: ChangeEvent) => {
form.handleChange(e)
if (!form.values.locked_cleanup_enabled) {
// fill failure_ttl_ms with defaults
await form.setValues({
...form.values,
locked_cleanup_enabled: true,
locked_ttl_ms: LOCKED_CLEANUP_DEFAULT,
})
} else {
// clear failure_ttl_ms
await form.setValues({
...form.values,
locked_cleanup_enabled: false,
locked_ttl_ms: 0,
})
}
}
return (
<HorizontalForm
onSubmit={form.handleSubmit}
@@ -298,7 +326,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
</FormSection>
<FormSection
title="Inactivity Cleanup"
description="When enabled, Coder will automatically delete workspaces that are in an inactive state after a specified number of days."
description="When enabled, Coder will lock workspaces that have not been accessed after a specified number of days."
>
<FormFields>
<FormControlLabel
@@ -330,6 +358,38 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
/>
</FormFields>
</FormSection>
<FormSection
title="Locked Cleanup"
description="When enabled, Coder will permanently delete workspaces that have been locked for a specified number of days."
>
<FormFields>
<FormControlLabel
control={
<Switch
name="lockedCleanupEnabled"
checked={form.values.locked_cleanup_enabled}
onChange={handleToggleLockedCleanup}
/>
}
label="Enable Locked Cleanup"
/>
<TextField
{...getFieldHelpers(
"locked_ttl_ms",
<TTLHelperText
translationName="lockedTTLHelperText"
ttl={form.values.locked_ttl_ms}
/>,
)}
disabled={isSubmitting || !form.values.locked_cleanup_enabled}
fullWidth
inputProps={{ min: 0, step: "any" }}
label="Time until cleanup (days)"
type="number"
aria-label="Locked Cleanup"
/>
</FormFields>
</FormSection>
</>
)}
<InactivityDialog
@@ -5,6 +5,7 @@ import i18next from "i18next"
export interface TemplateScheduleFormValues extends UpdateTemplateMeta {
failure_cleanup_enabled: boolean
inactivity_cleanup_enabled: boolean
locked_cleanup_enabled: boolean
}
const MAX_TTL_DAYS = 7
@@ -63,6 +64,20 @@ export const getValidationSchema = (): Yup.AnyObjectSchema =>
}
},
),
locked_ttl_ms: Yup.number()
.min(0, "Locked cleanup days must not be less than 0.")
.test(
"positive-if-enabled",
"Locked cleanup days must be greater than zero when enabled.",
function (value) {
const parent = this.parent as TemplateScheduleFormValues
if (parent.locked_cleanup_enabled) {
return Boolean(value)
} else {
return true
}
},
),
allow_user_autostart: Yup.boolean(),
allow_user_autostop: Yup.boolean(),
})
@@ -22,6 +22,7 @@ const validFormValues = {
max_ttl_ms: 2,
failure_ttl_ms: 7,
inactivity_ttl_ms: 180,
locked_ttl_ms: 30,
}
const renderTemplateSchedulePage = async () => {
@@ -37,11 +38,13 @@ const fillAndSubmitForm = async ({
max_ttl_ms,
failure_ttl_ms,
inactivity_ttl_ms,
locked_ttl_ms,
}: {
default_ttl_ms: number
max_ttl_ms: number
failure_ttl_ms: number
inactivity_ttl_ms: number
locked_ttl_ms: number
}) => {
const user = userEvent.setup()
const defaultTtlLabel = t("defaultTtlLabel", { ns: "templateSettingsPage" })
@@ -64,6 +67,11 @@ const fillAndSubmitForm = async ({
})
await user.type(inactivityTtlField, inactivity_ttl_ms.toString())
const lockedTtlField = screen.getByRole("checkbox", {
name: /Locked Cleanup/i,
})
await user.type(lockedTtlField, locked_ttl_ms.toString())
const submitButton = await screen.findByText(
FooterFormLanguage.defaultSubmitLabel,
)
@@ -111,7 +119,7 @@ describe("TemplateSchedulePage", () => {
)
})
test("failure and inactivity ttl converted to and from days", async () => {
test("failure, inactivity, and locked ttl converted to and from days", async () => {
await renderTemplateSchedulePage()
jest.spyOn(API, "updateTemplateMeta").mockResolvedValueOnce({
@@ -127,6 +135,7 @@ describe("TemplateSchedulePage", () => {
expect.objectContaining({
failure_ttl_ms: validFormValues.failure_ttl_ms * 86400000,
inactivity_ttl_ms: validFormValues.inactivity_ttl_ms * 86400000,
locked_ttl_ms: validFormValues.locked_ttl_ms * 86400000,
}),
),
)
@@ -218,4 +227,33 @@ describe("TemplateSchedulePage", () => {
"Inactivity cleanup days must not be less than 0.",
)
})
it("allows a locked ttl of 7 days", () => {
const values: UpdateTemplateMeta = {
...validFormValues,
locked_ttl_ms: 86400000 * 7,
}
const validate = () => getValidationSchema().validateSync(values)
expect(validate).not.toThrowError()
})
it("allows a locked ttl of 0", () => {
const values: UpdateTemplateMeta = {
...validFormValues,
locked_ttl_ms: 0,
}
const validate = () => getValidationSchema().validateSync(values)
expect(validate).not.toThrowError()
})
it("disallows a negative inactivity ttl", () => {
const values: UpdateTemplateMeta = {
...validFormValues,
locked_ttl_ms: -1,
}
const validate = () => getValidationSchema().validateSync(values)
expect(validate).toThrowError(
"Locked cleanup days must not be less than 0.",
)
})
})
+1
View File
@@ -388,6 +388,7 @@ export const MockTemplate: TypesGen.Template = {
allow_user_cancel_workspace_jobs: true,
failure_ttl_ms: 0,
inactivity_ttl_ms: 0,
locked_ttl_ms: 0,
allow_user_autostart: false,
allow_user_autostop: false,
}