diff --git a/codersdk/templates.go b/codersdk/templates.go index 541eb4eaca..2d523cf58e 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -232,7 +232,7 @@ type UpdateTemplateMeta struct { // RequireActiveVersion mandates workspaces built using this template // use the active version of the template. This option has no // effect on template admins. - RequireActiveVersion bool `json:"require_active_version"` + RequireActiveVersion bool `json:"require_active_version,omitempty"` // DeprecationMessage if set, will mark the template as deprecated and block // any new workspaces from using this template. // If passed an empty string, will remove the deprecated message, making diff --git a/site/e2e/tests/listTemplates.spec.ts b/site/e2e/tests/templates/listTemplates.spec.ts similarity index 86% rename from site/e2e/tests/listTemplates.spec.ts rename to site/e2e/tests/templates/listTemplates.spec.ts index 87d87109a8..71fdf6a3ed 100644 --- a/site/e2e/tests/listTemplates.spec.ts +++ b/site/e2e/tests/templates/listTemplates.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from "@playwright/test"; -import { beforeCoderTest } from "../hooks"; +import { beforeCoderTest } from "../../hooks"; test.beforeEach(({ page }) => beforeCoderTest(page)); diff --git a/site/e2e/tests/templates/updateTemplateSchedule.spec.ts b/site/e2e/tests/templates/updateTemplateSchedule.spec.ts new file mode 100644 index 0000000000..1eb272a665 --- /dev/null +++ b/site/e2e/tests/templates/updateTemplateSchedule.spec.ts @@ -0,0 +1,45 @@ +import { expect, test } from "@playwright/test"; +import { createTemplate, createTemplateVersion, getTemplate } from "api/api"; +import { getCurrentOrgId, setupApiCalls } from "../../api"; +import { beforeCoderTest } from "../../hooks"; + +test.beforeEach(({ page }) => beforeCoderTest(page)); + +test("update template schedule settings without override other settings", async ({ + page, + baseURL, +}) => { + await setupApiCalls(page); + const orgId = await getCurrentOrgId(); + const templateVersion = await createTemplateVersion(orgId, { + storage_method: "file" as const, + provisioner: "echo", + user_variable_values: [], + example_id: "docker", + tags: {}, + }); + const template = await createTemplate(orgId, { + name: "test-template", + display_name: "Test Template", + template_version_id: templateVersion.id, + disable_everyone_group_access: false, + require_active_version: true, + }); + + await page.goto(`${baseURL}/templates/${template.name}/settings/schedule`, { + waitUntil: "domcontentloaded", + }); + await page.getByLabel("Default autostop (hours)").fill("48"); + await page.getByRole("button", { name: "Submit" }).click(); + await expect(page.getByText("Template updated successfully")).toBeVisible(); + + const updatedTemplate = await getTemplate(template.id); + // Validate that the template data remains consistent, with the exception of + // the 'default_ttl_ms' field (updated during the test) and the 'updated at' + // field (automatically updated by the backend). + expect({ + ...template, + default_ttl_ms: 48 * 60 * 60 * 1000, + updated_at: updatedTemplate.updated_at, + }).toStrictEqual(updatedTemplate); +}); diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index af1635ccce..2fc3b5ea7a 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1320,7 +1320,7 @@ export interface UpdateTemplateMeta { readonly time_til_dormant_autodelete_ms?: number; readonly update_workspace_last_used_at: boolean; readonly update_workspace_dormant_at: boolean; - readonly require_active_version: boolean; + readonly require_active_version?: boolean; readonly deprecation_message?: string; readonly disable_everyone_group_access: boolean; readonly max_port_share_level?: WorkspaceAgentPortShareLevel; diff --git a/site/src/components/StackLabel/StackLabel.tsx b/site/src/components/StackLabel/StackLabel.tsx index dd6ae1760f..b4afcb4eba 100644 --- a/site/src/components/StackLabel/StackLabel.tsx +++ b/site/src/components/StackLabel/StackLabel.tsx @@ -22,9 +22,13 @@ export const StackLabel: FC> = (props) => { export const StackLabelHelperText: FC = (props) => { return ( ({ marginTop: 0, - }} + + "& strong": { + color: theme.palette.text.primary, + }, + })} {...props} /> ); diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx index 22d74729a9..98600c08cb 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx @@ -1,5 +1,6 @@ -import type { Interpolation, Theme } from "@emotion/react"; import Checkbox from "@mui/material/Checkbox"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import FormHelperText from "@mui/material/FormHelperText"; import MenuItem from "@mui/material/MenuItem"; import TextField from "@mui/material/TextField"; import { type FormikContextType, type FormikTouched, useFormik } from "formik"; @@ -17,14 +18,12 @@ import { HorizontalForm, FormFooter, } from "components/Form/Form"; -import { - HelpTooltip, - HelpTooltipContent, - HelpTooltipText, - HelpTooltipTrigger, -} from "components/HelpTooltip/HelpTooltip"; import { IconField } from "components/IconField/IconField"; import { Stack } from "components/Stack/Stack"; +import { + StackLabel, + StackLabelHelperText, +} from "components/StackLabel/StackLabel"; import { getFormHelpers, nameValidator, @@ -160,92 +159,74 @@ export const TemplateSettingsForm: FC = ({ title="Operations" description="Regulate actions allowed on workspaces created from this template." > - - - - - {!advancedSchedulingEnabled && ( - - - - Enterprise license required to enabled. - - - )} - - + {!advancedSchedulingEnabled && ( + + + Enterprise license required to enabled. + + )} + + + } + /> + = ({ label="Deprecation Message" /> {!accessControlEnabled && ( - + - + Enterprise license required to deprecate templates. {template.deprecated && " You cannot change the message, but you may remove it to mark this template as no longer deprecated."} - + )} @@ -306,11 +287,11 @@ export const TemplateSettingsForm: FC = ({ Public {!portSharingControlsEnabled && ( - + - + Enterprise license required to control max port sharing level. - + )} @@ -321,15 +302,3 @@ export const TemplateSettingsForm: FC = ({ ); }; - -const styles = { - optionText: (theme) => ({ - fontSize: 16, - color: theme.palette.text.primary, - }), - - optionHelperText: (theme) => ({ - fontSize: 12, - color: theme.palette.text.secondary, - }), -} satisfies Record>; diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx index 1ae261eba2..47e31f0549 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx @@ -234,7 +234,6 @@ export const TemplateScheduleForm: FC = ({ allow_user_autostop: form.values.allow_user_autostop, update_workspace_last_used_at: form.values.update_workspace_last_used_at, update_workspace_dormant_at: form.values.update_workspace_dormant_at, - require_active_version: false, disable_everyone_group_access: false, }); }; @@ -533,14 +532,9 @@ export const TemplateScheduleForm: FC = ({ When enabled, Coder will permanently delete dormant workspaces after a period of time.{" "} - ({ - fontWeight: 500, - color: theme.palette.text.primary, - })} - > + Once a workspace is deleted it cannot be recovered. - + }