feat(site): add advanced schedule settings to UI (#7061)

* feat: add advanced schedule settings

* Update site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx

Co-authored-by: Dean Sheather <dean@deansheather.com>

* rename variable

* fix optionText css

---------

Co-authored-by: Dean Sheather <dean@deansheather.com>
This commit is contained in:
Rodrigo Maia
2023-04-12 17:47:48 +00:00
committed by GitHub
co-authored by Dean Sheather
parent 264093a874
commit d1d459cb79
7 changed files with 166 additions and 46 deletions
@@ -105,6 +105,8 @@ const defaultInitialValues: CreateTemplateData = {
// you are not licensed. We hide the form value based on entitlements.
max_ttl_hours: 24 * 7,
allow_user_cancel_workspace_jobs: false,
allow_user_autostart: false,
allow_user_autostop: false,
}
type GetInitialValuesParams = {
@@ -112,19 +114,19 @@ type GetInitialValuesParams = {
fromCopy?: Template
parameters?: ParameterSchema[]
variables?: TemplateVersionVariable[]
canSetMaxTTL: boolean
allowAdvancedScheduling: boolean
}
const getInitialValues = ({
fromExample,
fromCopy,
canSetMaxTTL,
allowAdvancedScheduling,
variables,
parameters,
}: GetInitialValuesParams) => {
let initialValues = defaultInitialValues
if (!canSetMaxTTL) {
if (!allowAdvancedScheduling) {
initialValues = {
...initialValues,
max_ttl_hours: 0,
@@ -188,7 +190,7 @@ export interface CreateTemplateFormProps {
error?: unknown
jobError?: string
logs?: ProvisionerJobLog[]
canSetMaxTTL: boolean
allowAdvancedScheduling: boolean
copiedTemplate?: Template
}
@@ -204,12 +206,12 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
error,
jobError,
logs,
canSetMaxTTL,
allowAdvancedScheduling,
}) => {
const styles = useStyles()
const form = useFormik<CreateTemplateData>({
initialValues: getInitialValues({
canSetMaxTTL,
allowAdvancedScheduling,
fromExample: starterTemplate,
fromCopy: copiedTemplate,
variables,
@@ -319,7 +321,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
<TextField
{...getFieldHelpers(
"max_ttl_hours",
canSetMaxTTL ? (
allowAdvancedScheduling ? (
<TTLHelperText
translationName="form.helperText.maxTTLHelperText"
ttl={form.values.max_ttl_hours}
@@ -334,13 +336,62 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
</>
),
)}
disabled={isSubmitting || !canSetMaxTTL}
disabled={isSubmitting || !allowAdvancedScheduling}
fullWidth
label={t("form.fields.maxTTL")}
variant="outlined"
type="number"
/>
</Stack>
<Stack direction="column">
<Stack direction="row" alignItems="center">
<Checkbox
id="allow_user_autostart"
size="small"
color="primary"
disabled={isSubmitting || !allowAdvancedScheduling}
onChange={async () => {
await form.setFieldValue(
"allow_user_autostart",
!form.values.allow_user_autostart,
)
}}
name="allow_user_autostart"
checked={form.values.allow_user_autostart}
/>
<Stack spacing={0.5}>
<strong>
Allow users to autostart workspaces on a schedule.
</strong>
</Stack>
</Stack>
<Stack direction="row" alignItems="center">
<Checkbox
id="allow-user-autostop"
size="small"
color="primary"
disabled={isSubmitting || !allowAdvancedScheduling}
onChange={async () => {
await form.setFieldValue(
"allow_user_autostop",
!form.values.allow_user_autostop,
)
}}
name="allow-user-autostop"
checked={form.values.allow_user_autostop}
/>
<Stack spacing={0.5}>
<strong>
Allow users to customize autostop duration for workspaces.
</strong>
<span className={styles.optionHelperText}>
Workspaces will always use the default TTL if this is set.
Regardless of this setting, workspaces can only stay on for
the max TTL.
</span>
</Stack>
</Stack>
</Stack>
</FormFields>
</FormSection>
@@ -44,7 +44,7 @@ const CreateTemplatePage: FC = () => {
} = state.context
const shouldDisplayForm = !state.hasTag("loading")
const { entitlements } = useDashboard()
const canSetMaxTTL =
const allowAdvancedScheduling =
entitlements.features["advanced_template_scheduling"].enabled
const onCancel = () => {
@@ -70,7 +70,7 @@ const CreateTemplatePage: FC = () => {
{shouldDisplayForm && (
<CreateTemplateForm
copiedTemplate={state.context.copiedTemplate}
canSetMaxTTL={canSetMaxTTL}
allowAdvancedScheduling={allowAdvancedScheduling}
error={error}
starterTemplate={starterTemplate}
isSubmitting={state.hasTag("submitting")}
@@ -1,6 +1,6 @@
import TextField from "@material-ui/core/TextField"
import { Template, UpdateTemplateMeta } from "api/typesGenerated"
import { FormikContextType, FormikTouched, useFormik } from "formik"
import { FormikTouched, useFormik } from "formik"
import { FC } from "react"
import { getFormHelpers } from "util/formUtils"
import * as Yup from "yup"
@@ -11,6 +11,7 @@ import { FormSection, HorizontalForm, FormFooter } from "components/Form/Form"
import { Stack } from "components/Stack/Stack"
import { makeStyles } from "@material-ui/core/styles"
import Link from "@material-ui/core/Link"
import Checkbox from "@material-ui/core/Checkbox"
const TTLHelperText = ({
ttl,
@@ -48,6 +49,8 @@ export const getValidationSchema = (): Yup.AnyObjectSchema =>
24 * MAX_TTL_DAYS /* 7 days in hours */,
i18next.t("maxTTLMaxError", { ns: "templateSettingsPage" }),
),
allow_user_autostart: Yup.boolean(),
allow_user_autostop: Yup.boolean(),
})
export interface TemplateScheduleForm {
@@ -56,7 +59,7 @@ export interface TemplateScheduleForm {
onCancel: () => void
isSubmitting: boolean
error?: unknown
canSetMaxTTL: boolean
allowAdvancedScheduling: boolean
// Helpful to show field errors on Storybook
initialTouched?: FormikTouched<UpdateTemplateMeta>
}
@@ -66,35 +69,40 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
onSubmit,
onCancel,
error,
canSetMaxTTL,
allowAdvancedScheduling,
isSubmitting,
initialTouched,
}) => {
const { t: commonT } = useTranslation("common")
const validationSchema = getValidationSchema()
const form: FormikContextType<UpdateTemplateMeta> =
useFormik<UpdateTemplateMeta>({
initialValues: {
// on display, convert from ms => hours
default_ttl_ms: template.default_ttl_ms / MS_HOUR_CONVERSION,
// the API ignores this value, but to avoid tripping up validation set
// it to zero if the user can't set the field.
max_ttl_ms: canSetMaxTTL ? template.max_ttl_ms / MS_HOUR_CONVERSION : 0,
},
validationSchema,
onSubmit: (formData) => {
// on submit, convert from hours => ms
onSubmit({
default_ttl_ms: formData.default_ttl_ms
? formData.default_ttl_ms * MS_HOUR_CONVERSION
: undefined,
max_ttl_ms: formData.max_ttl_ms
? formData.max_ttl_ms * MS_HOUR_CONVERSION
: undefined,
})
},
initialTouched,
})
const form = useFormik<UpdateTemplateMeta>({
initialValues: {
// on display, convert from ms => hours
default_ttl_ms: template.default_ttl_ms / MS_HOUR_CONVERSION,
// the API ignores this value, but to avoid tripping up validation set
// it to zero if the user can't set the field.
max_ttl_ms: allowAdvancedScheduling
? template.max_ttl_ms / MS_HOUR_CONVERSION
: 0,
allow_user_autostart: template.allow_user_autostart,
allow_user_autostop: template.allow_user_autostop,
},
validationSchema,
onSubmit: (formData) => {
// on submit, convert from hours => ms
onSubmit({
default_ttl_ms: formData.default_ttl_ms
? formData.default_ttl_ms * MS_HOUR_CONVERSION
: undefined,
max_ttl_ms: formData.max_ttl_ms
? formData.max_ttl_ms * MS_HOUR_CONVERSION
: undefined,
allow_user_autostart: formData.allow_user_autostart,
allow_user_autostop: formData.allow_user_autostop,
})
},
initialTouched,
})
const getFieldHelpers = getFormHelpers<UpdateTemplateMeta>(form, error)
const { t } = useTranslation("templateSettingsPage")
const styles = useStyles()
@@ -128,7 +136,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
<TextField
{...getFieldHelpers(
"max_ttl_ms",
canSetMaxTTL ? (
allowAdvancedScheduling ? (
<TTLHelperText
translationName="maxTTLHelperText"
ttl={form.values.max_ttl_ms}
@@ -143,7 +151,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
</>
),
)}
disabled={isSubmitting || !canSetMaxTTL}
disabled={isSubmitting || !allowAdvancedScheduling}
fullWidth
inputProps={{ min: 0, step: 1 }}
label={t("maxTtlLabel")}
@@ -153,13 +161,72 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
</Stack>
</FormSection>
<FormSection
title="Allow users scheduling"
description="Allow users to set custom autostart and autostop scheduling options for workspaces created from this template."
>
<Stack direction="column">
<Stack direction="row" alignItems="center">
<Checkbox
id="allow_user_autostart"
size="small"
color="primary"
disabled={isSubmitting || !allowAdvancedScheduling}
onChange={async () => {
await form.setFieldValue(
"allow_user_autostart",
!form.values.allow_user_autostart,
)
}}
name="allow_user_autostart"
checked={form.values.allow_user_autostart}
/>
<Stack spacing={0.5}>
<strong>
Allow users to autostart workspaces on a schedule.
</strong>
</Stack>
</Stack>
<Stack direction="row" alignItems="center">
<Checkbox
id="allow-user-autostop"
size="small"
color="primary"
disabled={isSubmitting || !allowAdvancedScheduling}
onChange={async () => {
await form.setFieldValue(
"allow_user_autostop",
!form.values.allow_user_autostop,
)
}}
name="allow_user_autostop"
checked={form.values.allow_user_autostop}
/>
<Stack spacing={0.5}>
<strong>
Allow users to customize autostop duration for workspaces.
</strong>
<span className={styles.optionDescription}>
Workspaces will always use the default TTL if this is set.
Regardless of this setting, workspaces can only stay on for the
max lifetime.
</span>
</Stack>
</Stack>
</Stack>
</FormSection>
<FormFooter onCancel={onCancel} isLoading={isSubmitting} />
</HorizontalForm>
)
}
const useStyles = makeStyles(() => ({
const useStyles = makeStyles((theme) => ({
ttlFields: {
width: "100%",
},
optionDescription: {
fontSize: 12,
color: theme.palette.text.secondary,
},
}))
@@ -15,7 +15,7 @@ const TemplateSchedulePage: FC = () => {
const navigate = useNavigate()
const { template } = useTemplateSettingsContext()
const { entitlements } = useDashboard()
const canSetMaxTTL =
const allowAdvancedScheduling =
entitlements.features["advanced_template_scheduling"].enabled
const {
mutate: updateTemplate,
@@ -36,7 +36,7 @@ const TemplateSchedulePage: FC = () => {
<title>{pageTitle([template.name, "Schedule"])}</title>
</Helmet>
<TemplateSchedulePageView
canSetMaxTTL={canSetMaxTTL}
allowAdvancedScheduling={allowAdvancedScheduling}
isSubmitting={isSubmitting}
template={template}
submitError={submitError}
@@ -10,7 +10,7 @@ export default {
title: "pages/TemplateSchedulePageView",
component: TemplateSchedulePageView,
args: {
canSetMaxTTL: true,
allowAdvancedScheduling: true,
template: MockTemplate,
onSubmit: action("onSubmit"),
onCancel: action("cancel"),
@@ -26,5 +26,5 @@ Example.args = {}
export const CantSetMaxTTL = Template.bind({})
CantSetMaxTTL.args = {
canSetMaxTTL: false,
allowAdvancedScheduling: false,
}
@@ -11,7 +11,7 @@ export interface TemplateSchedulePageViewProps {
isSubmitting: boolean
submitError?: unknown
initialTouched?: ComponentProps<typeof TemplateScheduleForm>["initialTouched"]
canSetMaxTTL: boolean
allowAdvancedScheduling: boolean
}
export const TemplateSchedulePageView: FC<TemplateSchedulePageViewProps> = ({
@@ -19,7 +19,7 @@ export const TemplateSchedulePageView: FC<TemplateSchedulePageViewProps> = ({
onCancel,
onSubmit,
isSubmitting,
canSetMaxTTL,
allowAdvancedScheduling,
submitError,
initialTouched,
}) => {
@@ -32,7 +32,7 @@ export const TemplateSchedulePageView: FC<TemplateSchedulePageViewProps> = ({
</PageHeader>
<TemplateScheduleForm
canSetMaxTTL={canSetMaxTTL}
allowAdvancedScheduling={allowAdvancedScheduling}
initialTouched={initialTouched}
isSubmitting={isSubmitting}
template={template}
@@ -44,6 +44,8 @@ export interface CreateTemplateData {
icon: string
default_ttl_hours: number
max_ttl_hours: number
allow_user_autostart: boolean
allow_user_autostop: boolean
allow_user_cancel_workspace_jobs: boolean
parameter_values_by_name?: Record<string, string>
user_variable_values?: VariableValue[]