mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: change template max_ttl to default_ttl (#4843)
This commit is contained in:
@@ -179,8 +179,7 @@ export interface CreateTemplateRequest {
|
||||
readonly icon?: string
|
||||
readonly template_version_id: string
|
||||
readonly parameter_values?: CreateParameterRequest[]
|
||||
readonly max_ttl_ms?: number
|
||||
readonly min_autostart_interval_ms?: number
|
||||
readonly default_ttl_ms?: number
|
||||
}
|
||||
|
||||
// From codersdk/templateversions.go
|
||||
@@ -620,8 +619,7 @@ export interface Template {
|
||||
readonly build_time_stats: TemplateBuildTimeStats
|
||||
readonly description: string
|
||||
readonly icon: string
|
||||
readonly max_ttl_ms: number
|
||||
readonly min_autostart_interval_ms: number
|
||||
readonly default_ttl_ms: number
|
||||
readonly created_by_id: string
|
||||
readonly created_by_name: string
|
||||
}
|
||||
@@ -700,8 +698,7 @@ export interface UpdateTemplateMeta {
|
||||
readonly name?: string
|
||||
readonly description?: string
|
||||
readonly icon?: string
|
||||
readonly max_ttl_ms?: number
|
||||
readonly min_autostart_interval_ms?: number
|
||||
readonly default_ttl_ms?: number
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
|
||||
@@ -19,7 +19,7 @@ import * as Yup from "yup"
|
||||
export const Language = {
|
||||
nameLabel: "Name",
|
||||
descriptionLabel: "Description",
|
||||
maxTtlLabel: "Auto-stop limit",
|
||||
defaultTtlLabel: "Auto-stop default",
|
||||
iconLabel: "Icon",
|
||||
formAriaLabel: "Template settings form",
|
||||
selectEmoji: "Select emoji",
|
||||
@@ -28,7 +28,7 @@ export const Language = {
|
||||
descriptionMaxError:
|
||||
"Please enter a description that is less than or equal to 128 characters.",
|
||||
ttlHelperText: (ttl: number): string =>
|
||||
`Workspaces created from this template may not remain running longer than ${ttl} hours.`,
|
||||
`Workspaces created from this template will default to stopping after ${ttl} hours.`,
|
||||
}
|
||||
|
||||
const MAX_DESCRIPTION_CHAR_LIMIT = 128
|
||||
@@ -41,7 +41,7 @@ export const validationSchema = Yup.object({
|
||||
MAX_DESCRIPTION_CHAR_LIMIT,
|
||||
Language.descriptionMaxError,
|
||||
),
|
||||
max_ttl_ms: Yup.number()
|
||||
default_ttl_ms: Yup.number()
|
||||
.integer()
|
||||
.min(0)
|
||||
.max(24 * MAX_TTL_DAYS /* 7 days in hours */, Language.ttlMaxError),
|
||||
@@ -72,7 +72,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
||||
name: template.name,
|
||||
description: template.description,
|
||||
// on display, convert from ms => hours
|
||||
max_ttl_ms: template.max_ttl_ms / MS_HOUR_CONVERSION,
|
||||
default_ttl_ms: template.default_ttl_ms / MS_HOUR_CONVERSION,
|
||||
icon: template.icon,
|
||||
},
|
||||
validationSchema,
|
||||
@@ -80,8 +80,8 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
||||
// on submit, convert from hours => ms
|
||||
onSubmit({
|
||||
...formData,
|
||||
max_ttl_ms: formData.max_ttl_ms
|
||||
? formData.max_ttl_ms * MS_HOUR_CONVERSION
|
||||
default_ttl_ms: formData.default_ttl_ms
|
||||
? formData.default_ttl_ms * MS_HOUR_CONVERSION
|
||||
: undefined,
|
||||
})
|
||||
},
|
||||
@@ -176,20 +176,20 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
{...getFieldHelpers("max_ttl_ms")}
|
||||
{...getFieldHelpers("default_ttl_ms")}
|
||||
disabled={isSubmitting}
|
||||
fullWidth
|
||||
inputProps={{ min: 0, step: 1 }}
|
||||
label={Language.maxTtlLabel}
|
||||
label={Language.defaultTtlLabel}
|
||||
variant="outlined"
|
||||
type="number"
|
||||
/>
|
||||
{/* If a value for max_ttl_ms has been entered and
|
||||
{/* If a value for default_ttl_ms has been entered and
|
||||
there are no validation errors for that field, display helper text.
|
||||
We do not use the MUI helper-text prop because it overrides the validation error */}
|
||||
{form.values.max_ttl_ms && !form.errors.max_ttl_ms && (
|
||||
{form.values.default_ttl_ms && !form.errors.default_ttl_ms && (
|
||||
<Typography variant="subtitle2">
|
||||
{Language.ttlHelperText(form.values.max_ttl_ms)}
|
||||
{Language.ttlHelperText(form.values.default_ttl_ms)}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -26,15 +26,15 @@ const validFormValues = {
|
||||
name: "Name",
|
||||
description: "A description",
|
||||
icon: "A string",
|
||||
max_ttl_ms: 1,
|
||||
default_ttl_ms: 1,
|
||||
}
|
||||
|
||||
const fillAndSubmitForm = async ({
|
||||
name,
|
||||
description,
|
||||
max_ttl_ms,
|
||||
default_ttl_ms,
|
||||
icon,
|
||||
}: Omit<Required<UpdateTemplateMeta>, "min_autostart_interval_ms">) => {
|
||||
}: Required<UpdateTemplateMeta>) => {
|
||||
const nameField = await screen.findByLabelText(FormLanguage.nameLabel)
|
||||
await userEvent.clear(nameField)
|
||||
await userEvent.type(nameField, name)
|
||||
@@ -49,9 +49,9 @@ const fillAndSubmitForm = async ({
|
||||
await userEvent.clear(iconField)
|
||||
await userEvent.type(iconField, icon)
|
||||
|
||||
const maxTtlField = await screen.findByLabelText(FormLanguage.maxTtlLabel)
|
||||
const maxTtlField = await screen.findByLabelText(FormLanguage.defaultTtlLabel)
|
||||
await userEvent.clear(maxTtlField)
|
||||
await userEvent.type(maxTtlField, max_ttl_ms.toString())
|
||||
await userEvent.type(maxTtlField, default_ttl_ms.toString())
|
||||
|
||||
const submitButton = await screen.findByText(
|
||||
FooterFormLanguage.defaultSubmitLabel,
|
||||
@@ -87,7 +87,7 @@ describe("TemplateSettingsPage", () => {
|
||||
})
|
||||
|
||||
await fillAndSubmitForm(validFormValues)
|
||||
expect(screen.getByDisplayValue(1)).toBeInTheDocument() // the max_ttl_ms
|
||||
expect(screen.getByDisplayValue(1)).toBeInTheDocument() // the default_ttl_ms
|
||||
await waitFor(() => expect(API.updateTemplateMeta).toBeCalledTimes(1))
|
||||
|
||||
await waitFor(() =>
|
||||
@@ -95,7 +95,7 @@ describe("TemplateSettingsPage", () => {
|
||||
"test-template",
|
||||
expect.objectContaining({
|
||||
...validFormValues,
|
||||
max_ttl_ms: 3600000, // the max_ttl_ms to ms
|
||||
default_ttl_ms: 3600000, // the default_ttl_ms to ms
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -104,7 +104,7 @@ describe("TemplateSettingsPage", () => {
|
||||
it("allows a ttl of 7 days", () => {
|
||||
const values: UpdateTemplateMeta = {
|
||||
...validFormValues,
|
||||
max_ttl_ms: 24 * 7,
|
||||
default_ttl_ms: 24 * 7,
|
||||
}
|
||||
const validate = () => validationSchema.validateSync(values)
|
||||
expect(validate).not.toThrowError()
|
||||
@@ -113,7 +113,7 @@ describe("TemplateSettingsPage", () => {
|
||||
it("allows ttl of 0", () => {
|
||||
const values: UpdateTemplateMeta = {
|
||||
...validFormValues,
|
||||
max_ttl_ms: 0,
|
||||
default_ttl_ms: 0,
|
||||
}
|
||||
const validate = () => validationSchema.validateSync(values)
|
||||
expect(validate).not.toThrowError()
|
||||
@@ -122,7 +122,7 @@ describe("TemplateSettingsPage", () => {
|
||||
it("disallows a ttl of 7 days + 1 hour", () => {
|
||||
const values: UpdateTemplateMeta = {
|
||||
...validFormValues,
|
||||
max_ttl_ms: 24 * 7 + 1,
|
||||
default_ttl_ms: 24 * 7 + 1,
|
||||
}
|
||||
const validate = () => validationSchema.validateSync(values)
|
||||
expect(validate).toThrowError(FormLanguage.ttlMaxError)
|
||||
|
||||
@@ -198,8 +198,7 @@ export const MockTemplate: TypesGen.Template = {
|
||||
delete_ms: 3000,
|
||||
},
|
||||
description: "This is a test description.",
|
||||
max_ttl_ms: 24 * 60 * 60 * 1000,
|
||||
min_autostart_interval_ms: 60 * 60 * 1000,
|
||||
default_ttl_ms: 24 * 60 * 60 * 1000,
|
||||
created_by_id: "test-creator-id",
|
||||
created_by_name: "test_creator",
|
||||
icon: "/icon/code.svg",
|
||||
|
||||
@@ -52,7 +52,7 @@ describe("maxDeadline", () => {
|
||||
it("should be never be greater than global max deadline", () => {
|
||||
const template: Template = {
|
||||
...Mocks.MockTemplate,
|
||||
max_ttl_ms: 25 * 60 * 60 * 1000,
|
||||
default_ttl_ms: 25 * 60 * 60 * 1000,
|
||||
}
|
||||
|
||||
// Then: deadlineMinusDisabled should be falsy
|
||||
@@ -65,7 +65,7 @@ describe("maxDeadline", () => {
|
||||
it("should be never be greater than global max deadline", () => {
|
||||
const template: Template = {
|
||||
...Mocks.MockTemplate,
|
||||
max_ttl_ms: 4 * 60 * 60 * 1000,
|
||||
default_ttl_ms: 4 * 60 * 60 * 1000,
|
||||
}
|
||||
|
||||
// Then: deadlineMinusDisabled should be falsy
|
||||
@@ -95,7 +95,7 @@ describe("canExtendDeadline", () => {
|
||||
|
||||
it("should be falsy if the deadline is more than the template max_ttl", () => {
|
||||
const tooFarAhead = dayjs().add(
|
||||
dayjs.duration(Mocks.MockTemplate.max_ttl_ms, "milliseconds"),
|
||||
dayjs.duration(Mocks.MockTemplate.default_ttl_ms, "milliseconds"),
|
||||
)
|
||||
expect(
|
||||
canExtendDeadline(tooFarAhead, Mocks.MockWorkspace, Mocks.MockTemplate),
|
||||
@@ -104,7 +104,7 @@ describe("canExtendDeadline", () => {
|
||||
|
||||
it("should be truth if the deadline is within the template max_ttl", () => {
|
||||
const okDeadline = dayjs().add(
|
||||
dayjs.duration(Mocks.MockTemplate.max_ttl_ms / 2, "milliseconds"),
|
||||
dayjs.duration(Mocks.MockTemplate.default_ttl_ms / 2, "milliseconds"),
|
||||
)
|
||||
expect(
|
||||
canExtendDeadline(okDeadline, Mocks.MockWorkspace, Mocks.MockTemplate),
|
||||
|
||||
@@ -139,7 +139,7 @@ export function getMaxDeadline(
|
||||
}
|
||||
const startedAt = dayjs(ws.latest_build.updated_at)
|
||||
const maxTemplateDeadline = startedAt.add(
|
||||
dayjs.duration(tpl.max_ttl_ms, "milliseconds"),
|
||||
dayjs.duration(tpl.default_ttl_ms, "milliseconds"),
|
||||
)
|
||||
const maxGlobalDeadline = startedAt.add(deadlineExtensionMax)
|
||||
return dayjs.min(maxTemplateDeadline, maxGlobalDeadline)
|
||||
|
||||
Reference in New Issue
Block a user