Skip to content

feat: add template max_ttl #6114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5509824
feat: add template max_ttl
deansheather Feb 8, 2023
4c6a501
chore: split enterprise code for max_ttl into interface
deansheather Feb 15, 2023
7892c5a
Merge branch 'main' into dean/schedule-max-ttl
deansheather Feb 20, 2023
9e37cc7
feat: add API for setting template max_ttl
deansheather Feb 20, 2023
ad64806
working API and dashboard
deansheather Feb 20, 2023
96fd840
working CLI
deansheather Feb 20, 2023
bc39570
Merge branch 'main' into dean/schedule-max-ttl
deansheather Feb 23, 2023
4e1d948
fixup! Merge branch 'main' into dean/schedule-max-ttl
deansheather Feb 23, 2023
69035a6
feat: block disabling auto off if template has max ttl
deansheather Feb 23, 2023
ce7ec39
Merge branch 'main' into dean/schedule-max-ttl
deansheather Mar 1, 2023
89ccfc8
fixup! Merge branch 'main' into dean/schedule-max-ttl
deansheather Mar 1, 2023
feb7b3c
feat: apply template max TTL to workspace TTL on update
deansheather Mar 1, 2023
d54d798
chore: fix tests and differences between sql and memory db
deansheather Mar 1, 2023
6caeb00
Few refactorings for ttl fields
BrunoQuaresma Mar 1, 2023
49a2ec7
Merge branch 'main' into dean/schedule-max-ttl
deansheather Mar 1, 2023
e28e32e
fixup! Merge branch 'main' into dean/schedule-max-ttl
deansheather Mar 1, 2023
651149b
fixup! Merge branch 'main' into dean/schedule-max-ttl
deansheather Mar 1, 2023
25f7d2c
chore: add test for activitybump max_ttl
deansheather Mar 2, 2023
e3d8557
chore: add tests for updating max_ttl on template
deansheather Mar 2, 2023
f62ba67
Merge branch 'main' into dean/schedule-max-ttl
deansheather Mar 2, 2023
2b029d8
fixup! Merge branch 'main' into dean/schedule-max-ttl
deansheather Mar 2, 2023
e60919e
chore: fix security.yaml not having protoc
deansheather Mar 2, 2023
ef3bebf
Merge branch 'main' into dean/schedule-max-ttl
deansheather Mar 2, 2023
351b708
fixup! Merge branch 'main' into dean/schedule-max-ttl
deansheather Mar 2, 2023
847bc4c
chore: move schedule code to new package
deansheather Mar 2, 2023
0611794
fixup! chore: move schedule code to new package
deansheather Mar 2, 2023
e44f589
chore: add alpha label to max_ttl
deansheather Mar 6, 2023
6bb65ac
Merge branch 'main' into dean/schedule-max-ttl
deansheather Mar 6, 2023
a972c57
Merge branch 'main' into dean/schedule-max-ttl
BrunoQuaresma Mar 6, 2023
3ce2bc3
Fix test
BrunoQuaresma Mar 6, 2023
4beff52
fixup! Fix test
deansheather Mar 6, 2023
5e97e96
Fix storybook
BrunoQuaresma Mar 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Few refactorings for ttl fields
  • Loading branch information
BrunoQuaresma committed Mar 1, 2023
commit 6caeb003cccb7940eb2040f8773c3053b732a2a5
4 changes: 3 additions & 1 deletion site/src/i18n/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@
"updateCheck": {
"message": "Coder {{version}} is now available. View the <4>release notes</4> and <7>upgrade instructions</7> for more information.",
"error": "Coder update check failed."
}
},
"licenseFieldTextHelper": "You need an enterprise license to use it.",
"learnMore": "Learn more"
}
19 changes: 15 additions & 4 deletions site/src/i18n/en/createTemplatePage.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@
"displayName": "Display name",
"description": "Description",
"icon": "Icon",
"autoStop": "Auto-stop default",
"maxTTL": "Maximum lifetime of workspaces",
"autoStop": "Default auto-stop",
"maxTTL": "Max. Lifetime",
"allowUsersToCancel": "Allow users to cancel in-progress workspace jobs"
},
"helperText": {
"autoStop": "Time in hours",
"maxTTL": "Time in hours",
"defaultTTLHelperText_zero": "Workspaces will run until stopped manually.",
"defaultTTLHelperText_one": "Workspaces will default to stopping after {{count}} hour.",
"defaultTTLHelperText_other": "Workspaces will default to stopping after {{count}} hours.",
"maxTTLHelperText_zero": "Workspaces may run indefinitely.",
"maxTTLHelperText_one": "Workspaces must stop within 1 hour of starting.",
"maxTTLHelperText_other": "Workspaces must stop within {{count}} hours of starting.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

"allowUsersToCancel": "If checked, users may be able to corrupt their workspace."
},
"upload": {
Expand All @@ -41,6 +45,13 @@
},
"tooltip": {
"allowUsersToCancel": "Depending on your template, canceling builds may leave workspaces in an unhealthy state. This option isn't recommended for most use cases."
},
"error": {
"descriptionMax": "Please enter a description that is less than or equal to 128 characters.",
"defaultTTLMax": "Please enter a limit that is less than or equal to 168 hours (7 days).",
"defaultTTLMin": "Default time until auto-stop must not be less than 0.",
"maxTTLMax": "Please enter a limit that is less than or equal to 168 hours (7 days).",
"maxTTLMin": "Maximum time until auto-stop must not be less than 0."
}
}
}
16 changes: 8 additions & 8 deletions site/src/i18n/en/templateSettingsPage.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@
"displayNameLabel": "Display name",
"descriptionLabel": "Description",
"descriptionMaxError": "Please enter a description that is less than or equal to 128 characters.",
"defaultTtlLabel": "Auto-stop default",
"maxTtlLabel": "Maximum lifetime for started workspaces",
"defaultTtlLabel": "Default auto-stop",
"maxTtlLabel": "Max. Lifetime",
"iconLabel": "Icon",
"formAriaLabel": "Template settings form",
"selectEmoji": "Select emoji",
"defaultTTLMaxError": "Please enter a limit that is less than or equal to 168 hours (7 days).",
"defaultTTLMinError": "Default time until auto-stop must not be less than 0.",
"defaultTTLHelperText_zero": "Workspaces created from this template will run until stopped manually.",
"defaultTTLHelperText_one": "Workspaces created from this template will default to stopping after {{count}} hour.",
"defaultTTLHelperText_other": "Workspaces created from this template will default to stopping after {{count}} hours.",
"defaultTTLHelperText_zero": "Workspaces will run until stopped manually.",
"defaultTTLHelperText_one": "Workspaces will default to stopping after {{count}} hour.",
"defaultTTLHelperText_other": "Workspaces will default to stopping after {{count}} hours.",
"maxTTLMaxError": "Please enter a limit that is less than or equal to 168 hours (7 days).",
"maxTTLMinError": "Maximum time until auto-stop must not be less than 0.",
"maxTTLHelperText_zero": "Workspaces created from this template may run indefinitely.",
"maxTTLHelperText_one": "Workspaces created from this template must stop within {{count}} hour of starting.",
"maxTTLHelperText_other": "Workspaces created from this template must stop within {{count}} hours of starting.",
"maxTTLHelperText_zero": "Workspaces may run indefinitely.",
"maxTTLHelperText_one": "Workspaces must stop within 1 hour of starting.",
"maxTTLHelperText_other": "Workspaces must stop within {{count}} hours of starting.",
"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.",
Expand Down
115 changes: 94 additions & 21 deletions site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,73 @@ import { useFormik } from "formik"
import { SelectedTemplate } from "pages/CreateWorkspacePage/SelectedTemplate"
import { FC } from "react"
import { useTranslation } from "react-i18next"
import { nameValidator, getFormHelpers, onChangeTrimmed } from "util/formUtils"
import {
nameValidator,
getFormHelpers,
onChangeTrimmed,
templateDisplayNameValidator,
} from "util/formUtils"
import { CreateTemplateData } from "xServices/createTemplate/createTemplateXService"
import * as Yup from "yup"
import { WorkspaceBuildLogs } from "components/WorkspaceBuildLogs/WorkspaceBuildLogs"
import { HelpTooltip, HelpTooltipText } from "components/Tooltips/HelpTooltip"
import { LazyIconField } from "components/IconField/LazyIconField"
import { Maybe } from "components/Conditionals/Maybe"
import { useDashboard } from "components/Dashboard/DashboardProvider"
import i18next from "i18next"
import Link from "@material-ui/core/Link"

const MAX_DESCRIPTION_CHAR_LIMIT = 128
const MAX_TTL_DAYS = 7

const TTLHelperText = ({
ttl,
translationName,
}: {
ttl?: number
translationName: string
}) => {
const { t } = useTranslation("createTemplatePage")
const count = typeof ttl !== "number" ? 0 : ttl
return (
// no helper text if ttl is negative - error will show once field is considered touched
<Maybe condition={count >= 0}>
<span>{t(translationName, { count })}</span>
</Maybe>
)
}

const validationSchema = Yup.object({
name: nameValidator("Name"),
display_name: Yup.string().optional(),
description: Yup.string().optional(),
name: nameValidator(
i18next.t("form.fields.name", { ns: "createTemplatePage" }),
),
display_name: templateDisplayNameValidator(
i18next.t("form.fields.displayName", {
ns: "createTemplatePage",
}),
),
description: Yup.string().max(
MAX_DESCRIPTION_CHAR_LIMIT,
i18next.t("form.error.descriptionMax", { ns: "createTemplatePage" }),
),
icon: Yup.string().optional(),
default_ttl_hours: Yup.number(),
max_ttl_hours: Yup.number(),
default_ttl_hours: Yup.number()
.integer()
.min(
0,
i18next.t("form.error.defaultTTLMin", { ns: "templateSettingsPage" }),
)
.max(
24 * MAX_TTL_DAYS /* 7 days in hours */,
i18next.t("form.error.defaultTTLMax", { ns: "templateSettingsPage" }),
),
max_ttl_hours: Yup.number()
.integer()
.min(0, i18next.t("form.error.maxTTLMin", { ns: "templateSettingsPage" }))
.max(
24 * MAX_TTL_DAYS /* 7 days in hours */,
i18next.t("form.error.maxTTLMax", { ns: "templateSettingsPage" }),
),
allow_user_cancel_workspace_jobs: Yup.boolean(),
parameter_values_by_name: Yup.object().optional(),
})
Expand Down Expand Up @@ -110,6 +161,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
})
const getFieldHelpers = getFormHelpers<CreateTemplateData>(form, error)
const { t } = useTranslation("createTemplatePage")
const { t: commonT } = useTranslation("common")

return (
<form onSubmit={form.handleSubmit}>
Expand Down Expand Up @@ -197,30 +249,47 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
</p>
</div>

<Stack direction="column" className={styles.formSectionFields}>
<Stack direction="row" className={styles.ttlFields}>
<TextField
{...getFieldHelpers("default_ttl_hours")}
{...getFieldHelpers(
"default_ttl_hours",
<TTLHelperText
translationName="form.helperText.defaultTTLHelperText"
ttl={form.values.default_ttl_hours}
/>,
)}
disabled={isSubmitting}
onChange={onChangeTrimmed(form)}
fullWidth
label={t("form.fields.autoStop")}
variant="outlined"
type="number"
helperText={t("form.helperText.autoStop")}
/>

<Maybe condition={canSetMaxTTL}>
<TextField
{...getFieldHelpers("max_ttl_hours")}
disabled={isSubmitting}
onChange={onChangeTrimmed(form)}
fullWidth
label={t("form.fields.maxTTL")}
variant="outlined"
type="number"
helperText={t("form.helperText.maxTTL")}
/>
</Maybe>
<TextField
{...getFieldHelpers(
"max_ttl_hours",
canSetMaxTTL ? (
<TTLHelperText
translationName="form.helperText.maxTTLHelperText"
ttl={form.values.max_ttl_hours}
/>
) : (
<>
{commonT("licenseFieldTextHelper")}{" "}
<Link href="https://coder.com/docs/v2/latest/enterprise">
{commonT("learnMore")}
</Link>
.
</>
),
)}
disabled={isSubmitting || !canSetMaxTTL}
fullWidth
label={t("form.fields.maxTTL")}
variant="outlined"
type="number"
/>
</Stack>
</div>

Expand Down Expand Up @@ -346,6 +415,10 @@ const useStyles = makeStyles((theme) => ({
},
},

ttlFields: {
width: "100%",
},

formSectionInfo: {
width: 312,
flexShrink: 0,
Expand Down
63 changes: 38 additions & 25 deletions site/src/pages/TemplateSettingsPage/TemplateSettingsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { Stack } from "components/Stack/Stack"
import Checkbox from "@material-ui/core/Checkbox"
import { HelpTooltip, HelpTooltipText } from "components/Tooltips/HelpTooltip"
import { makeStyles } from "@material-ui/core/styles"
import { useDashboard } from "components/Dashboard/DashboardProvider"
import Link from "@material-ui/core/Link"

const TTLHelperText = ({
ttl,
Expand Down Expand Up @@ -81,6 +81,7 @@ export interface TemplateSettingsForm {
onCancel: () => void
isSubmitting: boolean
error?: unknown
canSetMaxTTL: boolean
// Helpful to show field errors on Storybook
initialTouched?: FormikTouched<UpdateTemplateMeta>
}
Expand All @@ -90,13 +91,11 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
onSubmit,
onCancel,
error,
canSetMaxTTL,
isSubmitting,
initialTouched,
}) => {
const { entitlements } = useDashboard()
const canSetMaxTTL =
entitlements.features["advanced_template_scheduling"].enabled

const { t: commonT } = useTranslation("common")
const validationSchema = getValidationSchema()
const form: FormikContextType<UpdateTemplateMeta> =
useFormik<UpdateTemplateMeta>({
Expand Down Expand Up @@ -193,39 +192,49 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
title={t("schedule.title")}
description={t("schedule.description")}
>
<TextField
{...getFieldHelpers(
"default_ttl_ms",
<TTLHelperText
translationName="defaultTTLHelperText"
ttl={form.values.default_ttl_ms}
/>,
)}
disabled={isSubmitting}
fullWidth
inputProps={{ min: 0, step: 1 }}
label={t("defaultTtlLabel")}
variant="outlined"
type="number"
/>

<Maybe condition={canSetMaxTTL}>
<Stack direction="row" className={styles.ttlFields}>
<TextField
{...getFieldHelpers(
"max_ttl_ms",
"default_ttl_ms",
<TTLHelperText
translationName="maxTTLHelperText"
translationName="defaultTTLHelperText"
ttl={form.values.default_ttl_ms}
/>,
)}
disabled={isSubmitting}
fullWidth
inputProps={{ min: 0, step: 1 }}
label={t("defaultTtlLabel")}
variant="outlined"
type="number"
/>

<TextField
{...getFieldHelpers(
"max_ttl_ms",
canSetMaxTTL ? (
<TTLHelperText
translationName="maxTTLHelperText"
ttl={form.values.max_ttl_ms}
/>
) : (
<>
{commonT("licenseFieldTextHelper")}{" "}
<Link href="https://coder.com/docs/v2/latest/enterprise">
{commonT("learnMore")}
</Link>
.
</>
),
)}
disabled={isSubmitting || !canSetMaxTTL}
fullWidth
inputProps={{ min: 0, step: 1 }}
label={t("maxTtlLabel")}
variant="outlined"
type="number"
/>
</Maybe>
</Stack>
</FormSection>

<FormSection
Expand Down Expand Up @@ -281,4 +290,8 @@ const useStyles = makeStyles((theme) => ({
fontSize: theme.spacing(1.5),
color: theme.palette.text.secondary,
},

ttlFields: {
width: "100%",
},
}))
5 changes: 5 additions & 0 deletions site/src/pages/TemplateSettingsPage/TemplateSettingsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useMachine } from "@xstate/react"
import { useDashboard } from "components/Dashboard/DashboardProvider"
import { useOrganizationId } from "hooks/useOrganizationId"
import { FC } from "react"
import { Helmet } from "react-helmet-async"
Expand Down Expand Up @@ -27,13 +28,17 @@ export const TemplateSettingsPage: FC = () => {
saveTemplateSettingsError,
getTemplateError,
} = state.context
const { entitlements } = useDashboard()
const canSetMaxTTL =
entitlements.features["advanced_template_scheduling"].enabled

return (
<>
<Helmet>
<title>{pageTitle(t("title"))}</title>
</Helmet>
<TemplateSettingsPageView
canSetMaxTTL={canSetMaxTTL}
isSubmitting={state.hasTag("submitting")}
template={template}
errors={{
Expand Down
Loading